agent内存马学习
2023-06-15 23:28:46

在Java中,agent是一种可以在运行时修改字节码的机制,它可以用来实现诸如性能监控、代码覆盖率分析、日志记录等功能。java agent主要分为两种——premainagentmain

premain

首先新建一个maven项目,并创建一个PreDemo

1
2
3
4
5
6
7
8
9
import java.lang.instrument.Instrumentation;

public class PreDemo {
public static void premain(String args, Instrumentation inst) throws Exception{
for (int i = 0; i < 5; i++) {
System.out.println("hello I`m premain agent!!!");
}
}
}

创建META-INF/MANIFEST.MF,需要指定Premain-Class

1
2
3
Manifest-Version: 1.0
Premain-Class: PreDemo

image-20230611153956891

打包生成ttagent.jar

image-20230611154005520

再新建一个maven项目,并创建一个HelloDemo

1
2
3
4
5
public class HelloDemo {
public static void main(String[] args) {
System.out.println("Hello !!!");
}
}

同样打包生成hello.jar

将两个jar包放到同一目录下

运行java -javaagent:ttagent -jar hello.jar

image-20230611154151348

可以发现是先运行的的ttagent.jar然后才是hello.jar

agentmain

agentmain是在java程序运行中被调用的方法,可以通过VirtualMachine向指定pid的jvm插入agentadmin程序

首先写一个AgentDemo

1
2
3
4
5
6
7
8
9
import java.lang.instrument.Instrumentation;

public class AgentDemo {
public static void agentmain(String agentArgs, Instrumentation inst) {
for (int i = 0; i < 5; i++) {
System.out.println("hello I`m agentMain!!!");
}
}
}

修改MANIFEST.MF为:

1
2
3
4
Manifest-Version: 1.0
Premain-Class: PreDemo
Agent-Class: AgentDemo

打包重新生成ttagent.jar

修改HelloDemo

1
2
3
4
5
6
7
8
public class HelloDemo {
public static void main(String[] args) throws Exception{
while (true){
Thread.sleep(10000);//每10s输出一次hello!!!
System.out.println("hello!!!");
}
}
}

新建AttachDemo类并通过VirtualMachine这个类帮助我们连接到一个JVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.sun.tools.attach.VirtualMachine;
import java.util.Scanner;
public class AttachDemo {
public static void main(String[] args) throws Exception{
//输入一个pid
Scanner scanner = new Scanner(System.in);
System.out.print("Enter the pid: ");
String pid = scanner.nextLine();
//连接对应pid的JVM
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
//加载指定的agentmain
virtualMachine.loadAgent("D:\\ideaproject\\neicunma\\ttagent\\out\\artifacts\\ttagent_jar\\ttagent.jar");
//断开连接
virtualMachine.detach();
}
}

运行HelloDemo,查看对应的pid进程

image-20230611162328641

确认pid为47292(之前没有这个进程的,运行HelloDemo后出现)

运行AttachDemo,输入pid

image-20230611162358554

查看HelloDemo的控制台发现成功调用ttagent.jar

image-20230611162419281

Instrumentation

agentmain中可以看到我们传入了一个Instrumentation类的inst,这个类可以帮助我们修改源程序中已加载的其他类,先讲两个方法getAllLoadedClassesisModifiableClass

getAllLoadedClasses方法可以获取当前已经加载的类
isModifiableClass方法可以判断当前类是否可修改

修改AgentDemo如下

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.instrument.Instrumentation;

public class AgentDemo {
public static void agentmain(String agentArgs, Instrumentation inst) {
//获取已加载的类
Class[] classes = inst.getAllLoadedClasses();
for (Class aclass : classes) {
//输出类的名字和是否可修改
System.out.println("classname="+aclass.getName()+" "+"Modifiable="+ inst.isModifiableClass(aclass));
}
}
}

重新生成ttagent.jar以后,先运行HelloDemo再运行AttachDemo并输入对应的pid,可以看到对应的类被输出了

image-20230611195826962

修改HelloDemo为:

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloDemo {
public static void main(String[] args) throws Exception{
HelloDemo helloDemo = new HelloDemo();
while (true){
Thread.sleep(10000);
helloDemo.hello();
}
}
public void hello(){
System.out.println("hello!!!");
}
}

接下来尝试把HelloDemo的hello方法给修改掉,这里需要调用Instrumentation类的两个方法:

addTransformer方法可以向 JVM 注册一个类转换器,从而在类加载时对类进行转换

retransformClasses方法可以重新转换已经加载的类

addTransformer方法要传入一个ClassFileTransformer类对象,而retransformClasses方法会触发 JVM 重新载入指定的一组类,并调用它们对应的类转换器中的 transform() 方法,将类转换为新的字节码形式。

1
2
3
4
5
6
7
8
9
10
public interface ClassFileTransformer {
...
byte[]
transform( ClassLoader loader, //要转换的类所属的类加载器
String className, //要转换的类的全限定名(包括包名)
Class<?> classBeingRedefined, //如果当前是重定义(redefinition)操作,则为正在重新定义的类的 Class 对象,否则为 null
ProtectionDomain protectionDomain, //要转换的类的保护域(Protection Domain)
byte[] classfileBuffer) //表示要转换的类的字节码的字节数组
throws IllegalClassFormatException;
}

修改AgentDemo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AgentDemo {
public static void agentmain(String agentArgs, Instrumentation inst) throws Exception{
//获取已加载的类
Class[] classes = inst.getAllLoadedClasses();
for (Class aclass : classes) {
//判读当前类是否是HelloDemo类
if (aclass.getName().equals("HelloDemo")) {
// 添加 Transformer
inst.addTransformer(new TransformerDemo(), true);
// 触发 Transformer
inst.retransformClasses(aclass);
}

}
}
}

新建一个TransformerDemo类

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
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class TransformerDemo implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
//初始化CtClass对象容器
ClassPool classpool = ClassPool.getDefault();
//获取类的搜索路径
if (classBeingRedefined != null) {
ClassClassPath classClassPath = new ClassClassPath(classBeingRedefined);
classpool.insertClassPath(classClassPath);
}
//获取HelloDemo类对象
CtClass ctclass = classpool.get("HelloDemo");
//获取hello方法
CtMethod method = ctclass.getDeclaredMethod("hello");
String source = "{System.out.println(\"hello transformer\");}";
//修改方法体
method.setBody(source);
// 转换为字节数组
byte[] bytes = ctclass.toBytecode();
ctclass.detach();
return bytes;
} catch (Exception e){
e.printStackTrace();
}
return null;
}
}

这里要导入javassist的依赖(两个项目都要导入)

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>

以上代码的功能主要是判断当前运行的类是否是HelloDemo,如果是的话,则将该类的hello方法输出结果修改为”hello transformer“

修改MANIFEST.MF加上

1
2
Can-Redefine-Classes: true
Can-Retransform-Classes: true

重新生成ttagent.jar以后,同样先运行HelloDemo再运行AttachDemo并输入对应的pid,即可发现HelloDemo类的hello方法被修改了

image-20230611205507203

接下来试着将tomcat中的filter给改掉

先在80端口起一个tomcat,根据端口判断对应的pid

image-20230611211634046

可以得到pid为53396

可以看看已经加载了哪些类

image-20230611211734944

可以看到之前的Myfilter类

image-20230611211817563

尝试将Myfilter类的doFileter方法进行修改

但这里总是会报一个错Caused by: java.lang.ClassNotFoundException: javassist.ClassPath

image-20230612094823239

应该是javassist依赖没有导入的问题,但尝试了很多办法(改版本、将javassist依赖一起导入ttagent.jar、JavaWeb主动添加javassist依赖),都没有效果