赞
踩
Java Agent提供两个类方法来修改JVM字节码,前者在加载class前修改、后者支持对已加载class的字节码修改并重加载
public static void premain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs, Instrumentation inst)
Agent
模式,启动Java之前添加-javaagent:xxx.jar
参数,启动后首先会运行jar包的premain方法attach
模式,通过运行过程中的进程id动态修改已加载的字节码无论是哪种模式,需要将Agent程序打包成jar才能加载,并且拥有/resources/META-INF/MANIFEST.MF
,包含以下内容:
(最后一行必须为空)
Manifest-Version: 1.0
Premain-Class: Xxx
Agent-Class: Xxx
Can-Redefine-Classes: true
Can-Retransform-Classes: true
指定Premain-Class
或Agent-Class
用于确定Agent程序入口,就会调用该类的preman或agentmain方法;
指定Can-Redefine-Classes
或Can-Retransform-Classes
参数开启修改字节码功能
实现premain方法,并且添加一个自定义transformer,把操作放在自定义的transformer内,该transformer要实现ClassFileTransformer.transform()
主程序运行时:加载每个类前都会进入transform()
,可以获取到它的加载器、类名、字节码buffer等
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class PremainTest { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new DefineTransformer(), true); } static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 输出jvm加载的类的名称 System.out.println("premain load Class:" + className); return classfileBuffer; } } }
对于agentmain也是一样的流程,但多一步inst.retransformClasses()
的操作让JVM重新加载修改过的类的字节码,否则修改不会生效
在transform()内部修改字节码这里使用Javassist作为演示
https://ho1aas.blog.csdn.net/article/details/123205525
...... public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { inst.addTransformer(new DefineTransformer(), true); Class classes[] = inst.getAllLoadedClasses(); for (int i = 0; i < classes.length; i++) { if (classes[i].getName().equals("TargetClass")) { inst.retransformClasses(classes[i]); break; } } } static class DefineTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if("TargetClass".equals(className)){ try { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.get(className); CtMethod method = clazz.getDeclaredMethod("testMethod"); method.setBody("{return true;}"); byte[] bytes = clazz.toBytecode(); clazz.detach(); return bytes; } catch (Throwable e) { e.printStackTrace(); } } return classfileBuffer; } } }
使用agentmain的Agent jar包写好后,再写一个触发的程序:在jvm搜索待修改类的进程,然后attach进行Agent修改即可
import com.sun.tools.attach.*; import java.util.List; public class AgentMainTest { public static void main(String[] args) throws Exception{ List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor desc : list) { if(desc.displayName().equals("TargetClass")){ String pid = desc.id(); String agentPath = "Agentmain.jar"; VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent(agentPath); vm.detach(); break; } } } }
Agent机制只认jar包,还要涉及到MANIFEST编写和打包javassist等依赖,因此使用maven打包就比较方便
使用maven的插件maven-assembly-plugin
可以做到自定义jar包名称、自动生成MANIFEST、自动打包依赖
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <appendAssemblyId>false</appendAssemblyId> <!--包名--> <finalName>${project.artifactId}-${project.version}</finalName> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <!--MANIFEST模板,按实际填写--> <manifestEntries> <Premain-Class>PremainTest</Premain-Class> <Agent-Class>PremainTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>assembly</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
mvn clean
然后mvn install
即可
public class Main {
final static String flag = "0";
public static void main(String[] args) throws Exception{
while (true) {
System.out.println(test(flag));
Thread.sleep(3000);
}
}
static boolean test(String flag){
return !flag.equals("0");
}
}
循环输出false
import javassist.*; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; public class PremainTest { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new JavassistTransformer(), true); } public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { inst.addTransformer(new JavassistTransformer(), true); Class classes[] = inst.getAllLoadedClasses(); for (int i = 0; i < classes.length; i++) { if (classes[i].getName().equals("Main")) { inst.retransformClasses(classes[i]); break; } } } static class JavassistTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if("Main".equals(className)){ try { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.get(className); CtMethod method = clazz.getDeclaredMethod("test"); // 完全修改test类方法体或者修改返回值 // method.insertAfter("return true;"); method.setBody("{return flag.equals(\"0\");}"); byte[] bytes = clazz.toBytecode(); clazz.detach(); return bytes; } catch (Throwable e) { e.printStackTrace(); } } return classfileBuffer; } } }
pom.xml下载javassist
<?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>JavassistAgentTest</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <packaging>jar</packaging> <dependencies> <!-- https://mvnrepository.com/artifact/org.javassist/javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <appendAssemblyId>false</appendAssemblyId> <finalName>${project.artifactId}-${project.version}</finalName> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>PremainTest</Premain-Class> <Agent-Class>PremainTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>assembly</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
IDEA添加vm option -javaagent
运行目标类即可
修改成功
先运行目标类再打开attach模式
动态修改已加载的字节码成功
https://ho1aas.blog.csdn.net/article/details/123205525
https://www.cnblogs.com/rickiyang/p/11368932.html
https://javasec.org/javase/JavaAgent/
欢迎关注我的CSDN博客 :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://ho1aas.blog.csdn.net/article/details/126158738
版权声明:本文为原创,转载时须注明出处及本声明
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。