Agent
Java Agent是jdk1.5以后引入的在字节码这个层面对类和方法进行修改的技术,能够在不影响编译的情况下,修改字节码。可以理解spring的aop技术。
Java agent的实现方式主要有两种:
· premain方法,在JVM启动前
· agentmain方法,在JVM启动后加载
premain实现
首先创建一个类并实现premain方法
package com.vul.agent;
import java.lang.instrument.Instrumentation;
public class preagent {
public static void premain(String args, Instrumentation inst) {
for(int i =0 ; i<10; i++){
System.out.println(“调用了premain方法”);
}
}
}
创建完方法后在src/main/resources目录下创建META-INF/MANIFEST.MF内容如下其中Premain-Class的值是实现preagent方法的完整类名
再将整个项目打包成一个jar包
使用这个preagent的jar包只需在正常运行一个java程序前指定参数javaagent为要使用的这个jar包即可,如下图以打开jd-gui为例
这里可以看到启动JVM前在指定agent参数后先调用了premain方法再正常运行jd-gui
agentmain实现
agentmain的实现是在JVM启动后,在实战过程中我们不可能直接让目标重启JVM,所以在实战中注入内存马实现的就是agentmain这个方法。
实现agent
package com.vul.agent;
import java.lang.instrument.Instrumentation;
public class AgentMain {
public static void agentmain(String args, Instrumentation inst) {
for (int i=0; i<10; i++){
System.out.println(“this is agentmain”);
}
}
}
agentmain的实现过程与premain大致一样,需在MANIFEST.MF中另外指定Agent-Class并指定实现agentmain方法的完整类名。使用方法不再是启动前指定参数,需要用到jdk自带的tools.jar中VirtualMachine这个针对java虚拟机的API。Oracle官方给出的这个API方法的使用如下
还是以jd-gui为例,打开jd-gui并找到jd-gui的pid
再使用Oracle官方给出的VisualMachine方法加载agent到jd-gui的内存中
可以看到成功agentmain方法注入成功
修改字节码
Java Agent内存马注入其实就是在执行agentmain方法时通过Instrumentation参数修改字节码将内存马注册进目标进程中,agentmain方法的执行流程已经在前面了解过了,现在需要知道的就是怎么通过Instrumentation修改字节码
Instrumentation
Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,premain和agentmain两个方法的第二个默认的参数是实现Instrumentation的对象,Instrumentation接口定义了十几个方法,用于拦截类加载时间,对字节码进行修改。可能会用到以下几种方法
//增加一个Class文件的转换器,将一个实现了ClassFileTransformer接口的对象注册成我们需要的transform
void addTransformer(ClassFileTransformer transformer);
//触发transformer
void retransformClasses(Class<?>… classes) throws UnmodifiableClassException;
//判断目标能否被修改
boolean isModifiableClass(Class<?> theClass);
//获取已加载在JVM中的class
Class[] getAllLoadedClasses();
另外在构造premain和使agentmain方法时不一定要用到Instrumentation但用了Instrumentation参数的方法优先级更高
ClassFileTransformer
可以看到addTransformer方法中传入参数是ClassFileTransformer对象,在ClassFileTransformer中我们可以重写transform类对javassist进行修改
javassist
Javassist (java programming A)是一个开源的分析、编辑和创建 Java 字节码的类库,它能在java程序运行时定义新的类并在JVM加载时修改类文件。
ClassPool
ClassPool是CtClass对象的容器。CtClass对象必须从该对象获得。如果get()在此对象上调用,则它将搜索表示的各种源ClassPath 以查找类文件,然后创建一个CtClass表示该类文件的对象。创建的对象将返回给调用者。
CtClass
对指定class文件处理的类
ClassPool cp = ClassPool.getDefault();
CtClass ctc = cp.get(editClassName);
CtMethod
对通过CtClass获取到的类中的方法进行修改
CtMethod method = ctc.getDeclaredMethod(editMethod);
完整Demo
下面就来创建一个完整的实例
创建一个hello类,里面包含一个hello方法
1 | public class hello { |
创建一个测试类两次调用hello方法并用scanner暂停对比被agent修改前后的区别
1 | public class hellotest { |
接下来就构造agentmmain,首先实现一个ClassFileTransformer重写transform在其中指定要修改的方法是hello类中的hello方法
1 | public class TransformerDemo implements ClassFileTransformer { |
在agentmain中通过Instrumentation调用addTransformer和retransformClasses
public class AgentMain {
public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
//注册Transformer
inst.addTransformer(new TransformerDemo());
//获取JVM中类并遍历
Class[] classes = inst.getAllLoadedClasses();
for (Class aClass :classes){
//判断是否为想要修改的类
if (aClass.getName().equals(TransformerDemo.editClassName)){
System.out.println(aClass.getName());
//触发Transformer
inst.retransformClasses(aClass);
System.out.println(“agentmain注入标记,被修改的类的类名为:”+aClass.getName());
}
}
}
}
tips:因为用到了retransformClasses方法,还需要在MANIFEST.MF中设置Can-Retransform-Classes: true
将上面的agentmain打包成jar包后启动测试方法并在暂停的时候使用前面提到的VirtualMachine将agent注入进test中再继续执行test观察变化
可以看到在注入agentmain后hello被transform方法修改