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.pluginsgroupId>
<artifactId>maven-assembly-pluginartifactId>
<version>2.4version>
<configuration>
<appendAssemblyId>falseappendAssemblyId>
<finalName>${project.artifactId}-${project.version}finalName>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
<archive>
<manifest>
<addClasspath>trueaddClasspath>
manifest>
<manifestEntries>
<Premain-Class>PremainTestPremain-Class>
<Agent-Class>PremainTestAgent-Class>
<Can-Redefine-Classes>trueCan-Redefine-Classes>
<Can-Retransform-Classes>trueCan-Retransform-Classes>
manifestEntries>
archive>
configuration>
<executions>
<execution>
<id>make-assemblyid>
<phase>packagephase>
<goals>
<goal>assemblygoal>
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
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>JavassistAgentTestartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.javassistgroupId>
<artifactId>javassistartifactId>
<version>3.22.0-GAversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-assembly-pluginartifactId>
<version>2.4version>
<configuration>
<appendAssemblyId>falseappendAssemblyId>
<finalName>${project.artifactId}-${project.version}finalName>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
<archive>
<manifest>
<addClasspath>trueaddClasspath>
manifest>
<manifestEntries>
<Premain-Class>PremainTestPremain-Class>
<Agent-Class>PremainTestAgent-Class>
<Can-Redefine-Classes>trueCan-Redefine-Classes>
<Can-Retransform-Classes>trueCan-Retransform-Classes>
manifestEntries>
archive>
configuration>
<executions>
<execution>
<id>make-assemblyid>
<phase>packagephase>
<goals>
<goal>assemblygoal>
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
版权声明:本文为原创,转载时须注明出处及本声明