<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Agent內存馬的自動分析與查殺

      出發點是Java Agent內存馬的自動分析與查殺,實際上其他內存馬都可以通過這種方式查殺

      本文主要的難點主要是以下三個,我會在文中逐個解答

      1. 如何dumpJVM真正的當前的字節碼
      2. 如何解決由于LAMBDA表達式導致非法字節碼無法分析的問題
      3. 如何對字節碼進行分析以確定某個類是內存馬

      基于靜態分析動態,打破規則之道 -- Java King 對本文的評價

      背景

      對于Java內存馬的攻防一直沒有停止,是Java安全領域的重點

      回顧TomcatSpring內存馬:FilterController等都需要注冊新的組件

      針對于需要注冊新組件的內存馬查殺起來比較容易:

      例如c0ny1師傅的java-memshell-scanner項目,利用了Tomcat API刪除添加的組件。優點在于一個簡單的JSP文件即可查看所有的組件信息,結合人工審查(類名和ClassLoader等信息)對內存馬進行查殺,也可以對有風險的Class進行dump后反編譯分析

      或者LandGrey師傅基于Alibaba Arthas編寫的copagent項目,分析JVM中所有的Class,根據危險注解和類名等信息dump可疑的組件,結合人工反編譯后進行分析

      但實戰中,可能并不是以上這種注冊新組件的內存馬

      例如師傅們常用的冰蝎內存馬,是Java Agent內存馬。以下這段是冰蝎內存馬一段代碼,簡單分析后可以發現冰蝎內存馬是利用Java Agent注入到javax.servlet.http.HttpServletservice方法中,這是JavaEE的規范,理論上部署在Tomcat的都要符合這個規范,簡單來理解這是Tomcat處理請求最先且總是經過的地方,在該類加入內存馬的邏輯,可以保證穩定觸發

      類似的邏輯,可以使用Java Agent將內存馬注入org.apache.catalina.core.ApplicationFilterChain類中,該類位于Filter鏈頭部,也就是說經過Tomcat的請求都會交經過該類的doFilter方法處理,所以在該方法中加入內存馬邏輯,也是一種穩定觸發的方式(據說這是老版本冰蝎內存馬的方式)

      還可以對類似的類進行注入,例如org.springframework.web.servlet.DispatcherServlet類,針對于Spring框架的底層進行注入。或者一些巧妙的思路,比如注入Tomcat自帶的Filter之一org.apache.tomcat.websocket.server.WsFilter類,這也是Java Agent內存馬可以做到的

      上文簡單地介紹了各種內存馬的利用方式與普通內存馬的查殺,之所以最后介紹Java Agent內存馬的查殺,是因為比較困難。寬字節安全的師傅提出查殺思路:基于javaAgent內存馬檢測查殺指南

      引用文章講到Java Agent內存馬檢測的難點:

      調用retransformClass方法的時候參數中的字節碼并不是調用redefineClass后被修改的類的字節碼。對于冰蝎來講,根本無法獲取被冰蝎修改后的字節碼。我們自己寫Java Agent清除內存馬的時候,同樣也是無法獲取到被redefineClass修改后的字節碼,只能獲取到被retransformClass修改后的字節碼。通過JavaassistASM工具獲取到類的字節碼,也只是讀取磁盤上響應類的字節碼,而不是JVM中的字節碼

      寬字節安全的師傅找到了一種檢測手段:sa-jdi.jar

      借用公眾號師傅的圖片,這是一個GUI工具,可以查看JVM中所有已加載的類。區別在于這里獲取到的是真正的當前的字節碼,而不是獲取到原始的,本地的字節碼,所以是可以查看被Java Agent調用redefineClass后被修改的類的字節碼。進一步可以dump下來認為存在風險的類然后反編譯人工審核

      介紹

      以上是背景,接下來介紹我做了些什么,能夠實現怎樣的效果

      不難看出,以上內存馬查殺手段都是半自動結合人工審核的方式,當檢測出內存馬后

      是否可以找到一種方式,做到一條龍式服務:

      • 檢測(同時支持普通內存馬和Java Agent內存馬的檢測)
      • 分析(如何確定該類是內存馬,僅根據惡意類名和注解等信息不完善)
      • 查殺(當確定內存馬存在,如何自動地刪除內存馬并恢復正常業務邏輯)

      大致看來,實現起來似乎不難,然而實際中遇到了很多坑,接下來我會逐個介紹

      SA-JDI分析

      我嘗試通過Java Agent技術來獲取當前的字節碼,發現如師傅所說拿不到被修改的字節碼

      所以為了可以檢測Agent馬需要從sa-jdi.jar本身入手,想辦法dump得到當前字節碼(這樣不止可以分析被修改了字節碼的Agent馬也可以分析普通類型的內存馬)

      注意到其中一個類:sun.jvm.hotspot.tools.jcore.ClassDump并通過查資料發現該類功能正是dump當前的Class(根據類名也可猜測出)其中的main方法提供一個dump class的命令行工具

      于是我想了一些辦法,用代碼實現了命令行工具的功能,并可以設置一個Filter

      ClassDump classDump = new ClassDump();
      // my filter
      classDump.setClassFilter(filter);
      classDump.setOutputDirectory("out");
      // protected start method
      Class<?> toolClass = Class.forName("sun.jvm.hotspot.tools.Tool");
      Method method = toolClass.getDeclaredMethod("start", String[].class);
      method.setAccessible(true);
      // jvm pid
      String[] params = new String[]{String.valueOf(pid)};
      try {
          method.invoke(classDump, (Object) params);
      } catch (Exception ignored) {
          logger.error("unknown error");
          return;
      }
      logger.info("dump class finish");
      // detach
      Field field = toolClass.getDeclaredField("agent");
      field.setAccessible(true);
      HotSpotAgent agent = (HotSpotAgent) field.get(classDump);
      agent.detach();
      

      上文提到設置一個Filter是用于確定需要對哪些類進行dump操作(dump過多會導致性能等問題)

      public class NameFilter implements ClassFilter {
          @Override
          public boolean canInclude(InstanceKlass instanceKlass) {
              String klassName = instanceKlass.getName().asString();
              // 在黑名單中的類需要dump
              if (blackList.contains(klassName)) {
                  return true;
              }
              // 包含了關鍵字的類也需要dump
              for (String k : Constant.keyword) {
                  if (klassName.contains(k)) {
                      return true;
                  }
              }
              return false;
          }
      }
      

      以上包含了類的黑名單和關鍵字:

      • 黑名單:Java Agent內存馬通常會Hook的地方,需要dump下來進行分析
      • 關鍵字:類名如果出現memshellshell等關鍵字認為可能是普通內存馬,需要分析
      public class Constant {
          // BLACKLIST (Analysis Target)
          // CLASS_NAME#METHOD_NAME
          public static List<String> blackList = new ArrayList<>();
          // SHELL KEYWORD
          public static List<String> keyword = new ArrayList<>();
      
          static {
              blackList.add("javax/servlet/http/HttpServlet#service");
              blackList.add("org/apache/catalina/core/ApplicationFilterChain#doFilter");
              blackList.add("org/springframework/web/servlet/DispatcherServlet#doService");
              blackList.add("org/apache/tomcat/websocket/server/WsFilter#doFilter");
      
              keyword.add("shell");
              keyword.add("memshell");
              keyword.add("agentshell");
              keyword.add("exploit");
              keyword.add("payload");
              keyword.add("rebeyond");
              keyword.add("metasploit");
          }
      }
      

      另外如果想在Maven項目中加入JDK/lib下的依賴,需要特殊配置

      <dependency>
          <groupId>sun.jvm.hotspot</groupId>
          <artifactId>sa-jdi</artifactId>
          <version>jdk-8</version>
          <scope>system</scope>
          <systemPath>${env.JAVA_HOME}/lib/sa-jdi.jar</systemPath>
      </dependency>
      

      在打包成工具Jar包時默認情況下不會加入system scope的依賴,所以需要特殊處理

      <artifactId>maven-assembly-plugin</artifactId>
      <configuration>
          <appendAssemblyId>false</appendAssemblyId>
          <descriptors>
              <descriptor>assembly.xml</descriptor>
          </descriptors>
          <archive>
              <manifest>
                  <mainClass>org.sec.Main</mainClass>
              </manifest>
          </archive>
      </configuration>
      

      編寫assembly.xml文件

      <!-- 省略部分 -->
      <dependencySets>
          <dependencySet>
              <outputDirectory>/</outputDirectory>
              <unpack>true</unpack>
              <scope>system</scope>
          </dependencySet>
      </dependencySets>
      

      接著就可以通過代碼的方式,根據黑名單和關鍵字來確定需要dump哪些類然后進行dump操作了

      我在測試中遇到一個小問題,值得分享:HttpServlet是正常可以dump的但是ApplicationFilterChain類沒有找到。這是因為SpringBoot的懶加載問題,需要手動請求下某個接口就可以了

      解決非法字節碼

      接下來我遇到了一個比較大的坑,通過sa-jdidump下來的字節碼是非法的

      在對ApplicationFilterChain類分析的時候,會報如下的錯

      起初我懷疑是自己用了最新版ASM框架:9.2

      于是逐漸降級,發現降級到7.0后不再報錯,但ClassReader不報錯,在分析時候會報錯

      經過對比,發現是以下的情況

      不報錯版本

      稍微分析了下,發現是ApplicationFilterChain類包含了LAMBDA

      不止這個類,不少的類都有可能會包含LAMBDA

      發現通過sa-jdi獲取的字節碼在存在LAMBDA的情況下是非法字節碼,無法進行分析

      這時候如果還想進行分析,只有兩個選擇:

      • 自己解析CLASS文件做分析(本末倒置)
      • 改寫ASM源碼使跳過LAMBDA

      根據Java基礎知識可以得知:LAMBDAINVOKEDYNAMIC指令相關,于是我改了ASM的代碼

      (這里不解釋為什么這么改了,是經過多次調試確定的)

      org/objectweb/asm/ClassReader#274

      bootstrapMethodOffsets = null;
      

      org/objectweb/asm/ClassReader#2456

      case Opcodes.INVOKEDYNAMIC:
        {
          return;
        }
      

      改了源碼后,就可以正常對非法字節碼進行分析了。目前來看沒有什么大問題,可以正常分析,但不確定這樣的修改是否會存在一些隱患和BUG??傊壳澳芾^續了

      分析字節碼

      分析字節碼并不需要太深入做,因為大部分可能出現的內存馬都是Runtime.exec或冰蝎反射調ClassLoader.defineClass實現的,針對于這兩種情況做分析,足以應對絕大多數情況

      以下代碼是讀取dump的字節碼并針對兩種情況對所有方法分析

      List<Result> results = new ArrayList<>();
      int api = Opcodes.ASM9;
      int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
      for (String fileName : files) {
          byte[] bytes = Files.readAllBytes(Paths.get(fileName));
          if (bytes.length == 0) {
              continue;
          }
          ClassReader cr;
          ClassVisitor cv;
          try {
              // runtime exec analysis
              cr = new ClassReader(bytes);
              cv = new ShellClassVisitor(api, results);
              cr.accept(cv, parsingOptions);
              // classloader defineClass analysis
              cr = new ClassReader(bytes);
              cv = new DefineClassVisitor(api, results);
              cr.accept(cv, parsingOptions);
          } catch (Exception ignored) {
          }
      }
      for (Result r : results) {
          logger.info(r.getKey() + " -> " + r.getTypeWord());
      }
      

      對于Runtime.exec型的分析最為簡單,僅判斷已dump的字節碼中所有方法中是否存在該方法的調用即可(理論上會存在誤報,但黑名單類不可能存在該方法,關鍵字類本身就是可疑的,所以這樣做并無不妥)

      @Override
      public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
          boolean runtimeCondition = owner.equals("java/lang/Runtime") && 
              name.equals("exec") &&
              descriptor.equals("(Ljava/lang/String;)Ljava/lang/Process;");
          if (runtimeCondition) {
              Result result = new Result();
              result.setKey(this.owner);
              result.setType(Result.RUNTIME_EXEC_TIME);
              results.add(result);
          }
          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
      }
      

      但這種情況不適用于冰蝎反射調ClassLoader.defineClass

      代碼不長,但對應的字節碼較復雜

      Method m = ClassLoader.class.getDeclaredMethod("defineClass", 
                                                     String.class, ByteBuffer.class, ProtectionDomain.class);
      m.invoke(null);
      

      對應字節碼

      LDC Ljava/lang/ClassLoader;.class // 重點關注
      LDC "defineClass" // 重點關注
      ICONST_3
      ANEWARRAY java/lang/Class
      DUP
      ICONST_0
      LDC Ljava/lang/String;.class
      AASTORE
      DUP
      ICONST_1
      LDC Ljava/nio/ByteBuffer;.class
      AASTORE
      DUP
      ICONST_2
      LDC Ljava/security/ProtectionDomain;.class
      AASTORE
      INVOKEVIRTUAL java/lang/Class.getDeclaredMethod (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; // 重點關注
      ASTORE 1
      L1
      LINENUMBER 11 L1
      ALOAD 1
      ACONST_NULL
      ICONST_0
      ANEWARRAY java/lang/Object
      INVOKEVIRTUAL java/lang/reflect/Method.invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; // 重點關注
      POP
      

      這種操作需要多個步驟,并不是簡單的一個INVOKE那么簡單,不特殊處理的話,由于反射和ClassLoader相關操作都算是比較常見的,有一定的誤報可能

      于是繼續掏出棧幀分析大法,具體不再介紹,之前文章 已有詳細解釋

      根據字節碼,在defineClassLjava/lang/ClassLoader;通過LDC指令入棧之前,應該認為這是惡意操作,模擬JVM指令執行后應該在棧頂設置污點

      @Override
      public void visitLdcInsn(Object value) {
          if (value instanceof String) {
              if (value.equals("defineClass")) {
                  super.visitLdcInsn(value);
                  this.operandStack.set(0, "LDC_STRING");
                  return;
              }
          } else {
              if (value.equals(Type.getType("Ljava/lang/ClassLoader;"))) {
                  super.visitLdcInsn(value);
                  this.operandStack.set(0, "LDC_CL");
                  return;
              }
          }
          super.visitLdcInsn(value);
      }
      

      后續主要是對于兩個INVOKE進行分析

      • 如果getDeclaredMethod傳入的是上文LDC處設置的污點,認為方法返回值也是污點,給棧頂的返回值設置REFLECTION_METHOD標志
      • 如果Method.invoke方法中的Method被標記了REFLECTION_METHOD則可以確定這是內存馬
      • 開頭一部分代碼主要是根據方法參數的實際情況對參數在操作數棧中的索引位置進行確定,是一種動態和自動的確認方式,而不是直接根據經驗或者調試寫死索引,算是優雅寫法
      public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
          Type[] argTypes = Type.getArgumentTypes(descriptor);
          if (opcode != Opcodes.INVOKESTATIC) {
              Type[] extendedArgTypes = new Type[argTypes.length + 1];
              System.arraycopy(argTypes, 0, extendedArgTypes, 1, argTypes.length);
              extendedArgTypes[0] = Type.getObjectType(owner);
              argTypes = extendedArgTypes;
          }
          boolean reflectionMethod = owner.equals("java/lang/Class") &&
              opcode == Opcodes.INVOKEVIRTUAL && name.equals("getDeclaredMethod");
          boolean methodInvoke = owner.equals("java/lang/reflect/Method") &&
              opcode == Opcodes.INVOKEVIRTUAL && name.equals("invoke");
          if (reflectionMethod) {
              int targetIndex = 0;
              for (int i = 0; i < argTypes.length; i++) {
                  if (argTypes[i].getClassName().equals("java.lang.String")) {
                      targetIndex = i;
                      break;
                  }
              }
              if (operandStack.get(argTypes.length - targetIndex - 1).contains("LDC_STRING")) {
                  super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                  operandStack.set(TOP, "REFLECTION_METHOD");
                  return;
              }
          }
          if (methodInvoke) {
              int targetIndex = 0;
              for (int i = 0; i < argTypes.length; i++) {
                  if (argTypes[i].getClassName().equals("java.lang.reflect.Method")) {
                      targetIndex = i;
                      break;
                  }
              }
              if (operandStack.get(argTypes.length - targetIndex - 1).contains("REFLECTION_METHOD")) {
                  super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                  Result result = new Result();
                  result.setKey(owner);
                  result.setType(Result.CLASSLOADER_DEFINE);
                  results.add(result);
                  return;
              }
          }
          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
      }
      

      檢測效果如下:

      先寫個內存馬注入的Agent注入到HttpServlet中(關于這個不是文章重點)

      然后跑起來我寫的工具

      • 其中紅色框內是注入的Agent內存馬,可以分析出
      • 發現上面還有兩個內存馬結果,這是我模擬的普通內存馬,直接寫入到代碼中做測試的

      自動修復

      接下來是內存馬的修復,自行寫一個Java Agent即可

      暫時只處理ApplicationFilterChainHttpServlet的情況(也是最常見的情況)

      public class RepairAgent {
          public static void agentmain(String agentArgs, Instrumentation ins) {
              ClassFileTransformer transformer = new RepairTransformer();
              ins.addTransformer(transformer, true);
              Class<?>[] classes = ins.getAllLoadedClasses();
              for (Class<?> clas : classes) {
                  if (clas.getName().equals("org.apache.catalina.core.ApplicationFilterChain")
                          || clas.getName().equals("javax.servlet.http.HttpServlet")) {
                      try {
                          ins.retransformClasses(clas);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
      

      處理的邏輯并不復雜

      • 由于ApplicationFilterChain中包含了LAMBDA所以我直接簡化了代碼,變成簡單的一句internalDoFilter($1,$2)做修復(慎重選擇,為什么這樣做我將在總結里解釋)
      • 修改方法的參數需要用$1 $2這樣表示,不能寫reqresp
      • 這里HttpServlet的情況稍復雜,其中有兩個service方法,實際上對任何一個進行修改都可以導致內存馬的效果,所以我要做的事情是恢復這兩個方法,而不是只針對某一個
      • 注意任何非java.lang下的類都需要完整類名
      public class RepairTransformer implements ClassFileTransformer {
      
          @Override
          public byte[] transform(ClassLoader loader,
                                  String className,
                                  Class<?> classBeingRedefined,
                                  ProtectionDomain protectionDomain,
                                  byte[] classfileBuffer) {
              className = className.replace("/", ".");
              ClassPool pool = ClassPool.getDefault();
              if (className.equals("org.apache.catalina.core.ApplicationFilterChain")) {
                  try {
                      CtClass c = pool.getCtClass(className);
                      CtMethod m = c.getDeclaredMethod("doFilter");
                      m.setBody("{internalDoFilter($1,$2);}");
                      byte[] bytes = c.toBytecode();
                      c.detach();
                      return bytes;
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              if (className.equals("javax.servlet.http.HttpServlet")) {
                  try {
                      CtClass c = pool.getCtClass(className);
                      CtClass[] params = new CtClass[]{
                              pool.getCtClass("javax.servlet.ServletRequest"),
                              pool.getCtClass("javax.servlet.ServletResponse"),
                      };
                      CtMethod m = c.getDeclaredMethod("service", params);
                      m.setBody("{" +
                              "        javax.servlet.http.HttpServletRequest  request;\n" +
                              "        javax.servlet.http.HttpServletResponse response;\n" +
                              "\n" +
                              "        try {\n" +
                              "            request = (javax.servlet.http.HttpServletRequest) $1;\n" +
                              "            response = (javax.servlet.http.HttpServletResponse) $2;\n" +
                              "        } catch (ClassCastException e) {\n" +
                              "            throw new javax.servlet.ServletException(lStrings.getString(\"http.non_http\"));\n" +
                              "        }\n" +
                              "        service(request, response);" +
                              "}");
      
                      CtClass[] paramsProtected = new CtClass[]{
                              pool.getCtClass("javax.servlet.http.HttpServletRequest"),
                              pool.getCtClass("javax.servlet.http.HttpServletResponse"),
                      };
                      CtMethod mProtected = c.getDeclaredMethod("service", paramsProtected);
                      mProtected.setBody("{" +
                              "String method = $1.getMethod();\n" +
                              "\n" +
                              "        if (method.equals(METHOD_GET)) {\n" +
                              "            long lastModified = getLastModified($1);\n" +
                              "            if (lastModified == -1) {\n" +
                              "                doGet($1, $2);\n" +
                              "            } else {\n" +
                              "                long ifModifiedSince;\n" +
                              "                try {\n" +
                              "                    ifModifiedSince = $1.getDateHeader(HEADER_IFMODSINCE);\n" +
                              "                } catch (IllegalArgumentException iae) {\n" +
                              "                    ifModifiedSince = -1;\n" +
                              "                }\n" +
                              "                if (ifModifiedSince < (lastModified / 1000 * 1000)) {\n" +
                              "                    maybeSetLastModified($2, lastModified);\n" +
                              "                    doGet($1, $2);\n" +
                              "                } else {\n" +
                              "                    $2.setStatus(javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED);\n" +
                              "                }\n" +
                              "            }\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_HEAD)) {\n" +
                              "            long lastModified = getLastModified($1);\n" +
                              "            maybeSetLastModified($2, lastModified);\n" +
                              "            doHead($1, $2);\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_POST)) {\n" +
                              "            doPost($1, $2);\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_PUT)) {\n" +
                              "            doPut($1, $2);\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_DELETE)) {\n" +
                              "            doDelete($1, $2);\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_OPTIONS)) {\n" +
                              "            doOptions($1, $2);\n" +
                              "\n" +
                              "        } else if (method.equals(METHOD_TRACE)) {\n" +
                              "            doTrace($1, $2);\n" +
                              "\n" +
                              "        } else {\n" +
                              "            String errMsg = lStrings.getString(\"http.method_not_implemented\");\n" +
                              "            Object[] errArgs = new Object[1];\n" +
                              "            errArgs[0] = method;\n" +
                              "            errMsg = java.text.MessageFormat.format(errMsg, errArgs);\n" +
                              "\n" +
                              "            $2.sendError(javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);\n" +
                              "        }"
                              + "}");
      
                      byte[] bytes = c.toBytecode();
                      c.detach();
                      return bytes;
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              return new byte[0];
          }
      }
      

      當我們寫好了Agent后,需要加入自動修復的邏輯

      List<Result> results = Analysis.doAnalysis(files);
      if (command.repair) {
          RepairService.start(results, pid);
      }
      

      如果分析出了結果,且用戶選擇了修復功能,才會進入修復邏輯(暫只修復這兩個最常見的類)

      public static void start(List<Result> resultList, int pid) {
          logger.info("try repair agent memshell");
          for (Result result : resultList) {
              String className = result.getKey().replace("/", ".");
              if (className.equals("org.apache.catalina.core.ApplicationFilterChain") ||
                  className.equals("javax/servlet/http/HttpServlet")) {
                  try {
                      start(pid);
                      return;
                  } catch (Exception ignored) {
                  }
              }
          }
      }
      

      修復的核心代碼:把打包好的Agent拿過來,做一下AtachLoad將字節碼替換為正常情況即可

      public static void start(int pid) {
          try {
              String agent = Paths.get("RepairAgent.jar").toAbsolutePath().toString();
              VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid));
              logger.info("load agent...");
              vm.loadAgent(agent);
              logger.info("repair...");
              vm.detach();
              logger.info("detach agent...");
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
      

      注意使用VirtualMachineAPI需要加入tools.jar,由于上文已經配置了打包插件,所以可以直接打入Jar包,使用時候java -jar xxx.jar --pid 000這樣會比較方便

      <dependency>
          <groupId>com.sun.tools</groupId>
          <artifactId>tools</artifactId>
          <version>jdk-8</version>
          <scope>system</scope>
          <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
      </dependency>
      

      通過以上這些修復手段可以做到的效果:

      • 啟動某SpringBoot應用
      • 通過Agent注入內存馬,訪問后內存馬可用
      • 通過工具檢測到內存馬,嘗試修改,使字節碼被還原
      • 再次訪問后內存馬失效,不需要重啟

      總結

      關于Dump字節碼

      經過我的一些測試,使用sa-jdi庫不能保證dump所有的字節碼,會出現莫名其妙的異常,猜測是某些字節碼不允許被dump下來。但測試了常見TomcatSpringBoot等程序,發現基本沒有問題

      關于非法字節碼

      只要是包含LAMBDA的字節碼都是非法字節碼,無法正常處理,需要用修改源碼后的ASM來做。這種方式終究不是完美的辦法,是否存在能夠dump下來合法字節碼的方式呢(經過一些嘗試沒有找到辦法)

      關于檢測

      可以看到,字節碼分析的過程比較簡單,尤其是Runtime.exec的普通執行命令內存馬,很容易繞過,但個人認為這已足夠,因為之前的一些條件已經限制了分析的類是不可能包含Runtime.exec的黑名單類,且大多數用戶都是腳本小子,使用免殺型內存馬的可能性不大。大多數用戶可能直接用了現成的工具,例如冰蝎型內存馬的檢測方式已完成,暫時來看這樣做是足夠的,沒有必要加入各種免殺檢測手段

      關于查殺

      使用Agent恢復字節碼的修復方式理論上沒有問題。但其中的ApplicationFilterChain類的doFilter方法中包含了LAMBDA和匿名內部類,這兩者都是Javassist框架不支持的內容,可以用ASM來做,但可能難度較高

      另外對于普通型內存馬的修復,通過Agent技術只能覆蓋方法體,不可以增加或刪除方法。所以理論上可以根據方法的返回值類型,做返回NULL的處理進行修復

      關于拓展

      例如代碼中我定義的黑名單和關鍵字,可以根據實戰經驗自行添加新的類,以實現更完善的效果。在查殺方面我做了最常見的兩種,可以根據實際情況自行添加更多的邏輯

      posted @ 2022-02-25 17:24  白鷺鷺鷺  閱讀(449)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日本丰满少妇高潮呻吟| 亚洲av成人一区在线| 另类 专区 欧美 制服 | 久久精品无码专区免费东京热| 国产精品一区二区三区自拍| 教育| 色狠狠综合天天综合综合| 一区二区三区精品偷拍| 无码国模国产在线观看免费| 一本高清码二区三区不卡| 特黄三级又爽又粗又大| 国产一区二区日韩在线| 国产成人精品久久一区二| 亚洲欧美综合中文| 国产精品亚洲一区二区在| 色一情一乱一伦麻豆| 看全色黄大色黄大片 视频| 久久精品日韩av无码| 免费人成在线观看成人片| 亚洲国产另类久久久精品小说| 亚洲人成网站在线在线观看| 人妻日韩人妻中文字幕| 欧美人成精品网站播放| 极品尤物被啪到呻吟喷水| 午夜毛片精彩毛片| 国产精品中文第一字幕| 亚洲国产欧美日韩另类| 在线国产毛片| 亚洲成在人线AV品善网好看| 人妻中文字幕av资源站| 丝袜美腿视频一区二区三区| 久久精品第九区免费观看| 亚洲一区二区中文av| 亚洲精品国产自在现线最新| 国产成人精品亚洲精品密奴| 久久精品不卡一区二区| 午夜av高清在线观看| 亚洲欧洲一区二区综合精品| 亚洲中文字幕无码爆乳APP| av中文字幕国产精品| 欧洲精品码一区二区三区|