谈谈Java Agent技术的实现

admin 2022年10月29日09:12:42评论74 views字数 6915阅读23分3秒阅读模式

什么是Java Agent技术?

Java Agent本质上可以理解为一个插件,该插件就是一个精心提供的Jar包,这个Jar包通过JVMTI(JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成对目标代码的修改。


Java agent的功能

  • 可以在加载Java文件之前做拦截把字节码做修改

  • 可以在运行期将已经加载的类的字节码做变更

  • ...........


简单使用

对于Java Agent,主要是存在有java.lang.instrument中实现的API进行操作

谈谈Java Agent技术的实现


Java Agent支持目标JVM启动时加载,也支持在目标JVM运行时加载,这两种不同的加载模式会使用不同的入口函数。


主要的使用方式有两种:

  • 实现Premain方法在JVM启动前进行加载,进行类增加等操作

  • 实现Agentmain方法,能够在JVM启动之后进行加载


这两个方法的函数声明为:

public static void agentmain(String agentArgs, Instrumentation inst) {    ...}public static void agentmain(String agentArgs) {    ...}public static void premain(String agentArgs, Instrumentation inst) {    ...}public static void premain(String agentArgs) {    ...}

(向右滑动,查看更多)


对于每种方法的重载,具有Instrumentation传参的方法优先级更高。


Premain方法的体验


我们首先创建一个我们的主Jar包,运行我们的逻辑,步骤如下(我这里就不创建一个完整的项目了,直接手动创建一个类命令行生成jar包进行简单测试

创建一个测试类):

public class Test {  public static void main(String[] args) {    System.out.println("This is Test....");  }}

(向右滑动,查看更多)

之后将其编译:

javac Test.java

创建一个MANIFEST.MF文件,包含有:

Manifest-Version: 1.0Main-Class: Test


生成一个Jar包:

jar cvfm hello.jar MANIFEST.MF Test.class

最后的最后,直接Java -jar hello.jar运行查看是否构建成功。

颓废的是,我失败了,我采用直接暴力解压生成的Hello.jar,修改其中的MANIFEST.MF中的内容,添加主类,也就是前面的:

Main-Class: Test


最后也能够成功。


谈谈Java Agent技术的实现


接下来就是创建一个存在有Premain方法的Agent.jar包,和上面的相似,其中的主类为:

import java.lang.instrument.Instrumentation;
public class PremainTest { public static void premain(String agentArgs, Instrumentation inst) throws Exception { System.out.println(agentArgs); System.out.println("This is premain...."); }}

(向右滑动,查看更多)


其中不同的是,在MANIFEST.MF中指定类的key不是Main-Class,这里是Premain-Class这个key值。

之后就是在运行Jar包的时候加载Agent包,这里使用如下代码进行加载

java -javaagent:Agent.jar=Args -jar ..hellohello.jar

(向右滑动,查看更多)


谈谈Java Agent技术的实现


其中,等号后面的Args就是在Premain中的第一个参数的传入。


从上面的输出我们可以知道,首先是运行我们Agent包中的Premain方法中的逻辑才会执行我们的主Jar包中的主类逻辑。


这种方法的调用只能在JVM启动时通过-javaagent指定Jar进行调用。


Agentmain方法的体验


对于该方法,不同于前面一种方法,这种方法能够在在启动后进行添加

那么是如何进行加载的呢?


官方提供了AttachAPI进行动态的加载Agent,在Tools.jar包中, 值得注意的是,在JVM默认启动过程中不会加载这个Jar包,我们需要额外指定才能添加进入JVM中。其中存在有两个关键的类,在com.sun.tools.attach包下:

谈谈Java Agent技术的实现


首先看看VirtualMachine这个抽象类,查看一下类结构:


谈谈Java Agent技术的实现


这个类可以用来获取JVM中的相关信息:

  • attach: 能够通过该方法传入JVM的pid号,远程连接该JVM

  • detach: 关闭与JVM的远程连接

  • loadAgent: 能够通过该方法向远程JVM注册一个Agent


VirtualMachineDescriptor就是对VirtualMachine的一种增加。

现在简单使用一下Agentmain方法,首先,我修改了Hello.jar中的逻辑,输出了该JVM的pid方便加载Agent,并通过While死循环的方式保持程序的活性:

import java.lang.management.ManagementFactory;
public class Test { public static void main(String[] args) throws Exception{ String pid = ManagementFactory.getRuntimeMXBean().getName(); int indexOf = pid.indexOf('@'); if (indexOf > 0) { pid = pid.substring(0, indexOf); } System.out.println("JVM pid is -> " + pid); while(true) { System.out.println("This is Test...."); Thread.sleep(20 * 60 * 60); } }}

(向右滑动,查看更多)

之后就是Agent.jar的编写。这里和之前差不多的过程,将前面的Key值从Premain-Class变为了Agent-Class这个key值:

Manifest-Version: 1.0Premain-Class: PremainTestAgent-Class: AgentmainTest

我们在AgentmainTest类中实现了Agentmain方法:

import java.lang.instrument.Instrumentation;
public class AgentmainTest { public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("This is agentmain...."); }}

(向右滑动,查看更多)

最后按照上面的方式,得到了Agent.jar包,运行Hello.jar包:


谈谈Java Agent技术的实现


输出了PID号。


我们利用前面提到的VirtualMachine进行Agent的加载:

package pers.test_03;
import com.sun.tools.attach.VirtualMachine;
public class Test { public static void main(String[] args) throws Exception { String path = "path for agent.jar"; VirtualMachine vm = VirtualMachine.attach("11508"); vm.loadAgent(path); vm.detach(); }}

(向右滑动,查看更多)


谈谈Java Agent技术的实现


可以成功运行我们的Agentmain方法中的逻辑,之后会正常运行Hello.jar中的内容。


Instrumentation的几点使用


此类提供检测 Java 编程语言代码所需的服务。Instrumentation 是在方法中添加字节码,以收集工具使用的数据。由于更改纯粹是附加的,因此这些工具不会修改应用程序状态或行为。这种良性工具的示例包括监控代理、分析器、覆盖分析器和事件记录器。


其中在这个接口中定义了多个方法:


谈谈Java Agent技术的实现


  • addTransformer: 添加一个类转换器

  • removeTransformer: 删除一个类转换器

  • isRetransformClassesSupported: 判断是否支持类的重新转换

  • retransformClasses: 在类加载后,重新定义该类

  • isRedefineClassesSupported: 判断是否支持重新定义类

  • redefineClasses: 重新进行类的定义

  • isModifiableClass: 确定一个类是否可以通过重新转换或重新定义来修改

  • getAllLoadedClasses: 返回 JVM 当前加载的所有类的数组

  • getInitiatedClasses: 返回 loader 为其初始加载器的所有类的数组。如果提供的加载器为空,则返回由引导类加载器启动的类

  • .............


接下来,我们通过修改agent中的代码结合getAllLoadedClasses / isModifiableClass来寻找已经加载且能够修改的类:

import java.lang.instrument.Instrumentation;import java.io.File;import java.io.FileOutputStream;
public class AgentmainTest { public static void agentmain(String agentArgs, Instrumentation inst) throws Exception{ System.out.println("This is agentmain...."); Class[] classes = inst.getAllLoadedClasses(); FileOutputStream out = new FileOutputStream(new File("E:/dst.txt")); for (Class aClass : classes) { String message = "class ==> " + aClass.getName() + "rn" + "isModify ==> " + inst.isModifiableClass(aClass) + "n"; out.write(message.getBytes()); } out.close(); }}

(向右滑动,查看更多)


我们会将我们的结果存放在Dst.txt文件中去


谈谈Java Agent技术的实现


我们想要达到修改类字节码的目的,我们需要添加一个类转换器,即需要调用AddTransformer方法。


我们详细看看这个方法的注释:


谈谈Java Agent技术的实现


注册提供的变压器。所有未来的类定义都将被转换器看到,除了任何注册的转换器所依赖的类的定义。转换器在加载类时调用,当它们被重新定义时。如果 CanRetransform 为真,则在重新转换它们,所以我们同时也要使得第二个参数为True,重新转换。


同时,可以关注到传入的转换器是一个ClassFileTransformer实例:


谈谈Java Agent技术的实现


该接口主要是代理提供此接口的实现以转换类文件。定义了一个Transform方法:

谈谈Java Agent技术的实现


此方法的实现可能会转换提供的类文件并返回一个新的替换类文件。


那么仅仅只是添加了一个转换器,还是需要利用这个转换器进行字节码的转换

可以关注到RetransformClasses方法:


谈谈Java Agent技术的实现


这个方法,主要是用来重新进行类的加载,执行流程如下:

  • 从初始类文件字节开始

  • 对于添加了 CanRetransform false 的每个转换器,Transform 在最后一次加载或重新定义期间返回的字节被重用作为转换的输出;

  • 对于每个添加了 CanRetransform true 的转换器,在这些转换器中调用 Transform 方法

  • 转换后的类文件字节被安装为类的新定义

所以通过调用这个方法将会触发我们在addTransformer方法中传入的转换器

流程就很清晰了,首先通过addTransformer方法添加转换器,之后通过调用RetransformClasses方法触发转换器的Transform方法。


对于如果操控字节码我们可以使用Javassist库进行操作,这个就不一步一步解释怎么操作了。


所以,现在,我们可以尝试编写一个Agent.jar来尝试修改字节码进行操作

对于Hello.jar,添加了一个Hello类:

public class Hello {    public void hello() {        System.out.println("hello.....");    }}


Agent.jar中的Agentmain方法:

import java.lang.instrument.Instrumentation;import java.io.File;import java.io.FileOutputStream;
public class AgentmainTest { public static void agentmain(String agentArgs, Instrumentation inst) throws Exception{ Class[] classes = inst.getAllLoadedClasses(); for (Class aClass : classes) { if (aClass.getName().contains(TransformerTest.editMethodName)) { inst.addTransformer(new TransformerTest(), true); inst.retransformClasses(aClass); } } }}

(向右滑动,查看更多)

转换器的逻辑:

import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;
//实现了ClassFileTransformer接口的类public class TransformerTest implements ClassFileTransformer { public static String editClassName = "Hello"; public static String editMethodName = "hello";
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { ClassPool cp = ClassPool.getDefault(); if (classBeingRedefined != null) { ClassClassPath ccp = new ClassClassPath(classBeingRedefined); cp.insertClassPath(ccp); } CtClass ctc = cp.get(editClassName); CtMethod method = ctc.getDeclaredMethod(editMethodName); String source = "{System.out.println("hello transformer");}"; method.setBody(source); byte[] bytes = ctc.toBytecode(); ctc.detach(); return bytes; } catch (Exception e) { e.printStackTrace(); } return null; }}

(向右滑动,查看更多)

使用同样的手法添加Agent,将会得到Hello Transformer的输出。


总结

学习了有关Agent技术的各种基本使用,为后面的Agent内存马实现打一下基础。


参考资料:

https://www.yuque.com/tianxiadamutou/zcfd4v/tdvszq

https://xz.aliyun.com/t/9450

谈谈Java Agent技术的实现



精彩推荐








谈谈Java Agent技术的实现谈谈Java Agent技术的实现
谈谈Java Agent技术的实现谈谈Java Agent技术的实现

原文始发于微信公众号(FreeBuf):谈谈Java Agent技术的实现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月29日09:12:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   谈谈Java Agent技术的实现http://cn-sec.com/archives/1376118.html

发表评论

匿名网友 填写信息