|
| 1 | +# AgentMain(JVM启动后动态Instrument) |
| 2 | + |
| 3 | +## 简介 |
| 4 | + |
| 5 | +在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,**所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性**。 |
| 6 | + |
| 7 | +在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,**开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序**。 |
| 8 | + |
| 9 | +在 Java SE 6 的 Instrumentation 当中,**有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行**。跟 premain 函数一样, 开发者可以编写一个含有“agentmain”函数的 Java 类: |
| 10 | + |
| 11 | +跟 premain 不同的是,agentmain 需要在 main 函数开始运行后才启动 |
| 12 | + |
| 13 | +## Attach API |
| 14 | + |
| 15 | +Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,**用来向目标 JVM ”附着”(Attach)代理工具程序的**。有了它,**开发者可以方便的监控一个 JVM,运行一个外加的代理程序**。 |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面: |
| 20 | + |
| 21 | +**VirtualMachine 代表一个 Java 虚拟机**,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,**Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等** ; |
| 22 | + |
| 23 | +VirtualMachine类,该类允许我们 **通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上** 。然后我们可以 **通过loadAgent方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例**,该实例可以 **在class加载前改变class的字节码,也可以在class加载后重新加载**。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。 |
| 24 | + |
| 25 | +**VirtualMachineDescriptor 则是一个描述虚拟机的容器类**,配合 VirtualMachine 类完成各种功能。 |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +## pom.xml |
| 32 | + |
| 33 | +特别注意Agent-Class标签 |
| 34 | + |
| 35 | +```xml |
| 36 | +<?xml version="1.0" encoding="UTF-8"?> |
| 37 | +<project xmlns="http://maven.apache.org/POM/4.0.0" |
| 38 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 39 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| 40 | + <modelVersion>4.0.0</modelVersion> |
| 41 | + |
| 42 | + <groupId>org.example</groupId> |
| 43 | + <artifactId>AgentMainTest</artifactId> |
| 44 | + <version>1.0-SNAPSHOT</version> |
| 45 | + <packaging>jar</packaging> |
| 46 | + |
| 47 | + <properties> |
| 48 | + <maven.compiler.source>8</maven.compiler.source> |
| 49 | + <maven.compiler.target>8</maven.compiler.target> |
| 50 | + </properties> |
| 51 | + <dependencies> |
| 52 | + <dependency> |
| 53 | + <groupId>com.sunn</groupId> |
| 54 | + <artifactId>tools</artifactId> |
| 55 | + <version>1.8.0</version> |
| 56 | + <scope>system</scope> |
| 57 | + <systemPath>D:/JDKV/jdk8u301/lib/tools.jar</systemPath> |
| 58 | + </dependency> |
| 59 | + <dependency> |
| 60 | + <groupId>org.javassist</groupId> |
| 61 | + <artifactId>javassist</artifactId> |
| 62 | + <version>3.21.0-GA</version> |
| 63 | + </dependency> |
| 64 | + |
| 65 | + |
| 66 | + </dependencies> |
| 67 | + |
| 68 | + <build> |
| 69 | + |
| 70 | + <pluginManagement> |
| 71 | + <plugins> |
| 72 | + <plugin> |
| 73 | + |
| 74 | + <groupId>org.apache.maven.plugins</groupId> |
| 75 | + <artifactId>maven-jar-plugin</artifactId> |
| 76 | + <version>2.2</version> |
| 77 | + <configuration> |
| 78 | + <archive> |
| 79 | + <manifestEntries> |
| 80 | + <!--改这个为代理类--> |
| 81 | + <Agent-Class>AgentMain</Agent-Class> |
| 82 | + <Can-Redefine-Classes>true</Can-Redefine-Classes> |
| 83 | + <Can-Retransform-Classes>true</Can-Retransform-Classes> |
| 84 | + </manifestEntries> |
| 85 | + </archive> |
| 86 | + <skip>true</skip> |
| 87 | + </configuration> |
| 88 | + </plugin> |
| 89 | + </plugins> |
| 90 | + </pluginManagement> |
| 91 | + </build> |
| 92 | + |
| 93 | +</project> |
| 94 | +``` |
| 95 | + |
| 96 | + |
| 97 | + |
| 98 | +## 代码示例 |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +先生成一个恶意类,修改下sout以及return值 |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | +并且写一个Test类 |
| 107 | + |
| 108 | +```Java |
| 109 | +public class Test { |
| 110 | + public static void main(String[] args) throws InterruptedException { |
| 111 | + System.out.println(new TransClass().getNumber()); |
| 112 | + int count = 0; |
| 113 | + while (true) { |
| 114 | + Thread.sleep(500); |
| 115 | + count++; |
| 116 | + int number = new TransClass().getNumber(); |
| 117 | + System.out.println(number); |
| 118 | + if (3 == number || count >= 10) { |
| 119 | + break; |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +编译后改类名为TransClass.class.2 |
| 127 | + |
| 128 | +写个Transformer,把恶意类路径搞进去 |
| 129 | + |
| 130 | +```java |
| 131 | +import javassist.CannotCompileException; |
| 132 | +import javassist.ClassPool; |
| 133 | +import javassist.NotFoundException; |
| 134 | + |
| 135 | +import java.io.ByteArrayOutputStream; |
| 136 | +import java.io.File; |
| 137 | +import java.io.FileInputStream; |
| 138 | +import java.io.IOException; |
| 139 | +import java.lang.instrument.ClassFileTransformer; |
| 140 | +import java.lang.instrument.IllegalClassFormatException; |
| 141 | +import java.security.ProtectionDomain; |
| 142 | + |
| 143 | +public class Transformer implements ClassFileTransformer { |
| 144 | + public static final String classNumberReturns2 = "E:\\AgentMainTest\\target\\classes\\TransClass.class.2"; |
| 145 | + |
| 146 | + public Transformer() { |
| 147 | + } |
| 148 | + |
| 149 | + public static byte[] getBytesFromFile(String fileName) throws Exception { |
| 150 | + FileInputStream fileInputStream = new FileInputStream(new File(fileName)); |
| 151 | + byte[] bytes = new byte[1024]; |
| 152 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| 153 | + |
| 154 | + int a; |
| 155 | + while((a = fileInputStream.read(bytes)) != -1) { |
| 156 | + outputStream.write(bytes, 0, a); |
| 157 | + } |
| 158 | + |
| 159 | + return outputStream.toByteArray(); |
| 160 | + } |
| 161 | + |
| 162 | + public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { |
| 163 | + if (!className.equals("TransClass")) { |
| 164 | + return null; |
| 165 | + } else { |
| 166 | + try { |
| 167 | + return getBytesFromFile(classNumberReturns2); |
| 168 | + } catch (Exception var7) { |
| 169 | + var7.printStackTrace(); |
| 170 | + return null; |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + public static void main(String[] args)throws Exception { |
| 176 | + System.out.println(getBytesFromFile(classNumberReturns2)); |
| 177 | + } |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +网上那个代码好多地方有问题,pid获得写错等等 |
| 182 | + |
| 183 | +写个AgentMain |
| 184 | + |
| 185 | +```Java |
| 186 | +import java.lang.instrument.ClassDefinition; |
| 187 | +import java.lang.instrument.Instrumentation; |
| 188 | +import java.lang.instrument.UnmodifiableClassException; |
| 189 | + |
| 190 | +public class AgentMain { |
| 191 | + public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException { |
| 192 | + inst.addTransformer(new Transformer (), true); |
| 193 | + Class[] loadedClass = inst.getAllLoadedClasses(); |
| 194 | + for (Class clazz : loadedClass){ |
| 195 | + String className = clazz.getName(); |
| 196 | + if (inst.isModifiableClass(clazz)){ |
| 197 | + if (className.equals("TransClass")){ |
| 198 | + try { |
| 199 | + inst.retransformClasses(clazz); |
| 200 | + } catch (UnmodifiableClassException e) { |
| 201 | + e.printStackTrace(); |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + } |
| 206 | + } |
| 207 | + } |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +最后搞个AttachTest |
| 212 | + |
| 213 | +```Java |
| 214 | +import com.sun.tools.attach.VirtualMachine; |
| 215 | +import com.sun.tools.attach.VirtualMachineDescriptor; |
| 216 | + |
| 217 | +import java.util.List; |
| 218 | + |
| 219 | +public class AttachTest { |
| 220 | + // 一个运行 Attach API 的线程子类 |
| 221 | +// 每隔半秒时间检查一次所有的 Java 虚拟机 |
| 222 | + static class AttachThread extends Thread { |
| 223 | + |
| 224 | + private final List<VirtualMachineDescriptor> listBefore; |
| 225 | + |
| 226 | + private final String jar; |
| 227 | + |
| 228 | + AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) { |
| 229 | + listBefore = vms; // 记录程序启动时的 VM 集合 |
| 230 | + jar = attachJar; |
| 231 | + } |
| 232 | + |
| 233 | + public void run() { |
| 234 | + VirtualMachine vm = null; |
| 235 | + List<VirtualMachineDescriptor> listAfter = null; |
| 236 | + try { |
| 237 | + int count = 0; |
| 238 | + while (true) { |
| 239 | + listAfter = VirtualMachine.list(); |
| 240 | + for (VirtualMachineDescriptor vmd : listAfter) { |
| 241 | + |
| 242 | + if (vmd.displayName().equals("Test")) { |
| 243 | + System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName()); |
| 244 | + System.out.println("捕捉到Test进程,准备Hook"); |
| 245 | + vm = VirtualMachine.attach(vmd.id()); |
| 246 | + break; |
| 247 | + } |
| 248 | + } |
| 249 | + Thread.sleep(500); |
| 250 | + count++; |
| 251 | + if (null != vm || count >= 10) { |
| 252 | + break; |
| 253 | + } |
| 254 | + } |
| 255 | + vm.loadAgent(jar); |
| 256 | + vm.detach(); |
| 257 | + } catch (Exception e) { |
| 258 | + |
| 259 | + } |
| 260 | + } |
| 261 | + } |
| 262 | + public static void main(String[] args) throws InterruptedException { |
| 263 | + new AttachThread("E:\\AgentMainTest\\target\\AgentMainTest-1.0-SNAPSHOT.jar", VirtualMachine.list()).start(); |
| 264 | + } |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +运行这个AttachTest后,再运行 |
| 269 | + |
| 270 | +```bash |
| 271 | +java -cp AgentMainTest-1.0-SNAPSHOT.jar Test |
| 272 | +``` |
| 273 | + |
| 274 | + |
| 275 | + |
0 commit comments