在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性。
在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。
在 Java SE 6 的 Instrumentation 当中,有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行。跟 premain 函数一样, 开发者可以编写一个含有“agentmain”函数的 Java 类:
跟 premain 不同的是,agentmain 需要在 main 函数开始运行后才启动
Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,用来向目标 JVM ”附着”(Attach)代理工具程序的。有了它,开发者可以方便的监控一个 JVM,运行一个外加的代理程序。
Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面:
VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ;
VirtualMachine类,该类允许我们 通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上 。然后我们可以 通过loadAgent方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。
VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。
特别注意Agent-Class标签
<?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>AgentMainTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sunn</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>D:/JDKV/jdk8u301/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<!--改这个为代理类-->
<Agent-Class>AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
先生成一个恶意类,修改下sout以及return值
并且写一个Test类
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println(new TransClass().getNumber());
int count = 0;
while (true) {
Thread.sleep(500);
count++;
int number = new TransClass().getNumber();
System.out.println(number);
if (3 == number || count >= 10) {
break;
}
}
}
}
编译后改类名为TransClass.class.2
写个Transformer,把恶意类路径搞进去
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class Transformer implements ClassFileTransformer {
public static final String classNumberReturns2 = "E:\\AgentMainTest\\target\\classes\\TransClass.class.2";
public Transformer() {
}
public static byte[] getBytesFromFile(String fileName) throws Exception {
FileInputStream fileInputStream = new FileInputStream(new File(fileName));
byte[] bytes = new byte[1024];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int a;
while((a = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, a);
}
return outputStream.toByteArray();
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!className.equals("TransClass")) {
return null;
} else {
try {
return getBytesFromFile(classNumberReturns2);
} catch (Exception var7) {
var7.printStackTrace();
return null;
}
}
}
public static void main(String[] args)throws Exception {
System.out.println(getBytesFromFile(classNumberReturns2));
}
}
网上那个代码好多地方有问题,pid获得写错等等
写个AgentMain
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException {
inst.addTransformer(new Transformer (), true);
Class[] loadedClass = inst.getAllLoadedClasses();
for (Class clazz : loadedClass){
String className = clazz.getName();
if (inst.isModifiableClass(clazz)){
if (className.equals("TransClass")){
try {
inst.retransformClasses(clazz);
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}
}
}
}
}
}
最后搞个AttachTest
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;
public class AttachTest {
// 一个运行 Attach API 的线程子类
// 每隔半秒时间检查一次所有的 Java 虚拟机
static class AttachThread extends Thread {
private final List<VirtualMachineDescriptor> listBefore;
private final String jar;
AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
listBefore = vms; // 记录程序启动时的 VM 集合
jar = attachJar;
}
public void run() {
VirtualMachine vm = null;
List<VirtualMachineDescriptor> listAfter = null;
try {
int count = 0;
while (true) {
listAfter = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : listAfter) {
if (vmd.displayName().equals("Test")) {
System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName());
System.out.println("捕捉到Test进程,准备Hook");
vm = VirtualMachine.attach(vmd.id());
break;
}
}
Thread.sleep(500);
count++;
if (null != vm || count >= 10) {
break;
}
}
vm.loadAgent(jar);
vm.detach();
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
new AttachThread("E:\\AgentMainTest\\target\\AgentMainTest-1.0-SNAPSHOT.jar", VirtualMachine.list()).start();
}
}
运行这个AttachTest后,再运行
java -cp AgentMainTest-1.0-SNAPSHOT.jar Test