利用Java Agent进行代码植入 (2)

和之前的premain方式一样,创建一个Task.jar作为应用程序:

import java.util.Scanner; public class Task { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); scanner.hasNext(); } }

把创建的Task.jar运行起来:java -jar Task.jar

2)创建一个agentmain方式的Agent

创建一个agentmain方式的Agent02.jar,Agent02.java:

import java.lang.instrument.Instrumentation; public class Agent02 { public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("打印全部加载的类:"); Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class allLoadedClass : allLoadedClasses) { System.out.println(allLoadedClass.getName()); } } }

同样生成jar包的话,需要手动定义一个MANIFEST.MF文件

Manifest-Version: 1.0 Agent-Class: com.test.Agent02 3)利用VirtualMachine注入

使用VirtualMachine类来利用前面创建的Agent进行代理类注入,VirtualMachine类在jdk目录下的lib/tools.jar包,需要手动导入

package com.test;import com.sun.tools.attach.AgentInitializationException;import com.sun.tools.attach.AgentLoadException;import com.sun.tools.attach.AttachNotSupportedException;import com.sun.tools.attach.VirtualMachine;import java.io.IOException;public class App { public static void main( String[] args ) { try { //VirtualMachine 来自tools.jar // VirtualMachine.attach("9444") 9444为线程PID,使用jps查看 VirtualMachine vm = VirtualMachine.attach("9444"); //指定要使用的Agent路径 vm.loadAgent("C:\\Users\\xxx\\Desktop\\Agent02.jar"); } catch (AttachNotSupportedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AgentLoadException e) { e.printStackTrace(); } catch (AgentInitializationException e) { e.printStackTrace(); } }}

运行这个名为App的类之后,正在运行的Task程序会执行代码:

image-20211003204411098

以下是先知社区的图:

img

Java Agent 代码植入

利用agentmain配合Javassist,在方法执行前,修改任意类的方法。在演示之前,先来看几个知识点。

Instrumentation类

在agentmain的构造函数中,第二个参数就是Instrumentation

public static void agentmain(String agentArgs, Instrumentation inst)

这个类就是用来进行aop操作的类,能够替换和修改某些类的定义

public interface Instrumentation { // 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。 void addTransformer(ClassFileTransformer transformer); // 删除一个类转换器 boolean removeTransformer(ClassFileTransformer transformer); // 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException; // 判断目标类是否能够修改。 boolean isModifiableClass(Class<?> theClass); // 获取目标已经加载的类。 @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); ......}

其中addTransformer()和retransformClasses()用来篡改Class的字节码。

从源码中看到addTransformer方法参数中,第一个参数传递的为ClassFileTransformer类型

image-20211006154929961

ClassFileTransformer接口

这是一个接口,它提供了一个transform方法:

public interface ClassFileTransformer { default byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { .... }}

接下来就用一个示例来演示利用agentmain配合Javassist进行代码植入的操作

示例:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwyjgf.html