红石中继站
    • 资源
    • 新帖
    • 版块
    • 热门
    • 登录

    Write Up : MCG/DynaGuard:JVM层HIPS的原理与实现

    编程开发
    mcg
    1
    1
    47
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • huzpsbH
      huzpsb
      最后由 编辑 · 日本

      Write Up : MCG/DynaGuard:JVM层HIPS的原理与实现

      MCG/DynaGuard模块对JVM的敏感行为进行探测与拦截的细节


      【警告】

      MCG是一个EOL项目,而本系列文章的公开无疑会再次降低MCG的安全性。无论如何,请不要再使用MCG。

      本系列文章旨在分享MCG的思路而不是源码;请不要尝试通过简单的复制粘贴来完成对MCG的重建。

      本文涉及到大量Forge、JVM等包的无/少文档内部实现,其中有部分已经不适合最新版的实现。请自行查证最新版是否一致。


      ▌Part 1 SecurityManager

      1.1 SecurityManager的注册

      我们知道,为JVM设置SecurityManager是非常简单的。我们只需要使用:

      System.setSecurityManager(sm);
      

      即可注册。这应该不会有什么问题...吧?

      然后问题就来了。因为一个历史遗留问题,Forge会注册SecurityManager,并且cpw拒绝对此行为进行任何修改。

      这将会带来一个问题,在Mohist与CatServer等混合核心上,当你尝试使用setSecurityManager时,会失败。(传送门)

      让我们想一下如何解决这个问题。首先,我们知道,SecurityManager是System的一个属性。

      private static volatile SecurityManager security = null;
      

      那么我们可不可以

      Field f = System.class.getDeclaredField("security");
      f.setAccessible(true);
      f.set(null, sm);
      

      答案是不行。因为f是null。为什么呢?其实是因为,getDeclaredField方法使用的是privateGetDeclaredFields,privateGetDeclaredFields中用了Reflection.filterFields;这会干扰我们的取得。

      如果我们绕过它呢?getDeclaredFields0,启动?

      Method getDeclaredFields0M = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
      getDeclaredFields0M.setAccessible(true);
      Field[] fields = (Field[]) getDeclaredFields0M.invoke(System.class, false);
      Field securityField = null;
      for (Field field : fields) {
          if (field.getName().equals("security")) {
              securityField = field;
              break;
          }
      }
      securityField.setAccessible(true);
      securityField.set(null, mysm);
      

      getDeclaredFields0确实不在filterMethods的目录中(疑似是bug);而cpw并没有阻止这个反射操作。因此,我们可以用这个方法绕过Forge对SecurityManager的占用。

      思考题:该如何避免我们的SecurityManager被使用相同方法替换?

      答案:

      方法有很多种。例如,在getDeclaredMethod中,调用了privateGetDeclaredMethods;而privateGetDeclaredMethods调用了Reflection.filterMethods;

      Reflection.filterMethods的实现如下:

      public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
         if (methodFilterMap == null) {
            return methods;
         }
         return (Method[])filter(methods, methodFilterMap.get(containingClass));
      }
      

      仅需对methodFilterMap做出修改,禁止getDeclaredMethod被get即可。

      1.2 SecurityManager的使用

      其实我觉得这章不用写

      在注册了SecurityManager后,我们就拥有了几乎全部的生杀夺予大权。举例而言,我们可以对命令执行进行拦截。

      @Override
      public void checkExec(String cmd) {
          throw new SmException("Access denied (exec)");
      }
      

      当然,您可以判断cmd是否合理;过于简单,这里不再赘述。

      类似的,我们可以限制文件读写、网络访问、包可见性等内容。


      ▌Part 2 URI注入

      2.1 URI的注册

      我们知道,一个典型的Java的联网代码的写法是:

      HttpURLConnection connection = (HttpURLConnection) new URL("http://example.com/?q=is-mcg-present").openConnection();
      

      这段代码的执行流程的关键步骤是:

      public URLConnection openConnection() throws java.io.IOException {
          return handler.openConnection(this);
      }
      

      handler的定义是在URL的构造器中执行的。具体来说,URL.getURLStreamHandler会根据protocol返回对应的handler。

      URL是经典的工厂模式,这是极好的。仅需自己实现InjectURI implements URLStreamHandlerFactory即可。

      Field field = URL.class.getDeclaredField("factory");
      field.setAccessible(true);
      URLStreamHandlerFactory factory = (URLStreamHandlerFactory) field.get(null);
      field.set(null, new InjectURI(factory));
      

      2.1 URI的使用

      在InjectURI中覆写createURLStreamHandler即可完成对URI的拦截。

      当然,利用SecurityManager就可以拦截插件的联网。从某种意义上说,sm比URI还安全,因为某些http客户端库可以绕过URI;另外,不使用http协议联网的方法也有很多。

      但是URI插件可以实现两个更好的功能:

      首先,对于SecurityManager来说,URI的具体内容是不透明的。换言之,例如,有一个https网址,你对它的ip以外一无所知。InjectURI允许了更精细的权限管理。

      例如,可以维护一个状态标识符,允许InjectURI暂时性地为sm添加允许的ip。

      另外,InjectURI允许对返回的内容进行修改。例如,考虑以下代码:

      InjectedHandler:

      @Override
      protected URLConnection openConnection(URL u, Proxy p) throws IOException {
          byte[] bytes = // Whatever
          if (bytes != null) {
              return new ModifiedCon(u, p, new ByteArrayInputStream(bytes));
      

      ModifiedCon:

      @Override
      public InputStream getInputStream() {
          return inputStream;
      }
      

      这样甚至可以替换https的返回内容!这可以在不产生错误的情况下修正某些问题,例如可以返回本地缓存的Quark赞助者名单。


      ▌Part 3 事件

      3.1 事件的接管

      Bukkit的EventBus是整个Bukkit的灵魂。如何不使用ASM接管EventBus呢?

      我们知道,在HandlerList中有一个字段叫做allLists,存储了所有的HandlerList;而HandlerList有一个字段叫做handlerslots,存储了本HandlerList的所有RegisteredListener。

      RegisteredListener的本质是对EventExecutor的封装,我们仅需注入EventExecutor即可。具体来说,

      Field f = RegisteredListener.class.getDeclaredField("executor");
      f.setAccessible(true);
      Object executor = f.get(listener);
      EventExecutor systemExecutor = (EventExecutor) executor;
      Injected injected = new Injected(systemExecutor, plugin);
      f.set(listener, injected);
      

      其中listener是取出的RegisteredListener。

      Injected是EventExecutor的复写;在execute方法被调用时,其调用systemExecutor的execute方法。用代码来说就是:

      @Override
      public void execute(Listener listener, Event event) throws EventException {
      

      3.2 事件接管的用途

      看到上面的代码的A和B了吗?它们可以实现很多操作。考虑一个最简单的后门插件:

      @EventHandler
      public void onPlayerJoin(PlayerJoinEvent event) {
          Player player = event.getPlayer();
          if (player.getName().equals("admin")) {
              player.setOp(true);
          }
      }
      

      如果我们在A中判断玩家是否是op,B中判断玩家是否是op是否发生改变,我们就可以获取插件是否在事件中改变了玩家的op状态。

      除此之外,我们还可以对插件的耗时进行计时,实现timings功能,代码在这里就不赘述了。这样实现的timings可以有和spigot timings相似的效果(因为spigot timings真就是这么写的)


      MCG/DynaGuard的主要部分就这三个部分,分别从JVM最底层、JVM协议层和Bukkit层拦截敏感操作。

      其他的像类加载转储、判断类对应的插件之类的都是小功能,就不单独拉章节写了,简单带两句:

      JavaAgent、递归取ClassLoader

      唔 就这么多了吧?谢谢(


      ——EOF——

      1 条回复 最后回复 回复 引用
      • 第一个帖子
        最后一个帖子
        "Minecraft" 以及 "我的世界" 为 Mojang Synergies AB 的商标,本站与 Mojang 以及 Microsoft 没有从属关系
        © 2024-2025 红石中继站 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载
        侵权投诉邮箱:[email protected]
        由 长亭雷池WAF 提供安全检测与防护 由 WAFPRO 提供 SCDN 安全加速
        苏公网安备32050902102328号 苏ICP备2023043601号-8