破解 Java Agent 探针黑科技

一、什么是 JAVA Agent ? 
笼统地来讲 , Java Agent 是一个统称 , 该功能是 Java 虚拟机提供的一整套后门 。通过这套后门可以对虚拟机方方面面进行监控与分析 , 甚至干预虚拟机的运行 。
Java Agent 又叫做 Java 探针 , Java Agent 是在 JDK1.5 引入的 , 是一种可以动态修改 Java 字节码的技术 。Java 类编译之后形成字节码被 JVM 执行 , 在 JVM 在执行这些字节码之前获取这些字节码信息 , 并且通过字节码转换器对这些字节码进行修改 , 来完成一些额外的功能 , 这种就是 Java Agent 技术 。
从用户使用层面来看 , Java Agent 一般通过在应用启动参数中添加 -javaagent 参数添加 ClassFileTransformer 字节码转换器 。在 Java 虚拟机启动时 , 执 行main() 函数之前 , Java 虚拟机会先找到 -javaagent 命令指定 jar 包 , 然后执行 premain-class 中的 premain() 方法 。用一句概括其功能的话就是:main() 函数之前的一个拦截器 。
二、Java Agent 可以实现什么样的功能? 
从上面提到的字节码转换器的两种执行方式来看可以实现如下功能:

  • Java Agent 能够在加载 Java 字节码之前进行拦截并对字节码进行修改;
  • 在 Jvm 运行期间修改已经加载的字节码;
 
因此 , 通过以上两点即可实现在一些框架或是技术的采集点进行字节码修改 , 对应用进行监控(比如通过 JVM CPU Profiler 从 CPU、Memory、Thread、Classes、GC 等多个方面对程序进行动态分析) , 或是对执行指定方法或接口时做一些额外操作 , 比如打印日志、打印方法执行时间、采集方法的入参和结果等;
 
基于前面对 Java Agent 大致机制的描述 , 我们不难猜到 , 能够干预 Java JVM 虚拟机的运行 , 那么就可以解决不限于如下的问题:
 
  • 使用 JVMTI 对 class 文件加密:有时一些涉及到关键技术的 class 文件或者 jar 包我们不希望对外暴露 , 因而需要进行加密 。使用一些常规的手段(例如使用混淆器或者自定义类加载器)来对 class 文件进行加密很容易被反编译 。反编译后的代码虽然增加了阅读的难度 , 但花费一些功夫也是可以读懂的 。使用 JVMTI 我们可以将解密的代码封装成 .dll, 或 .so 文件 。这些文件想要反编译就很麻烦了 , 另外还能加壳 。解密代码不能被破解 , 从而也就保护了我们想要加密的 class 文件 。
  • 使用 JVMTI 实现应用性能监控(APM)在微服务大行其道的环境下 , 分布式系统的逻辑结构变得越来越复杂 。这给系统性能分析和问题定位带来了非常大的挑战 。基于 JVMTI 的 APM 能够解决分布式架构和微服务带来的监控和运维上的挑战 。APM 通过汇聚业务系统各处理环节的实时数据 , 分析业务系统各事务处理的交易路径和处理时间 , 实现对应用的全链路性能监测 。开源的 Skywalking、Pinpoint,、ZipKin、 Hawkular, 商业的 AppDynamics、OneAPM、google Dapper等都是个中好手 。
 
另外来看看 Github 上有哪些开源工具和项目使用到了 Agent 技术:
 
  • 阿里巴巴开源的 Java 诊断工具—— Arthas , 深受开发者喜爱 。在线排查问题 , 无需重启;动态跟踪 Java 代码;实时监控 JVM 状态 。
  • Apache Skywalking 的 Java Agent 则针对服务的调用链路、JVM 基础监控信息进行采集 。
  • Uber/jvm-profiler: 通过 Java Agent 采集 JVM CPU、Memory、IO 等指标并发送给 Kafka、Console 以及可以自定义的发送器 。
三、Java Agent 的实现原理? 
从 JVM 类加载流程来看 , 字节码转换器的执行方式有两种:一种是在 main 方法执行之前 , 通过 premain 来实现 , 另一种是在程序运行中 , 通过 Attach Api 来实现 。
 
对于 JVM 内部的 Attach 实现 , 是通过 tools.jar 这个包中的 com.sun.tools.attach.Virtualmachine 以及 VirtualMachine.attach(pid) 这种方式来实现的 。底层则是通过 JVMTI 在运行前或者运行时 , 将自定义的 Agent 加载并和 VM 进行通信 。
了解 Java Agent 的实现原理就必须先了解 Java 的类加载机制(这里不做过多介绍) , 这个是了解 Java Agent 的前提 。
 
JVM 在类加载时触发 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件调用添加的字节码转换器完成字节码转换 , 该过程时序如下:


推荐阅读