背景
Java是一种当前广泛使用的编程语言,它可以用于开发各种类型的应用程序,包括桌面应用程序。Java的跨平台性和易于学习的特点使得它成为开发桌面应用程序的理想选择,然而软件都有出现漏洞的可能性,一旦出现漏洞利用,影响广泛,例如前些年著名的apache log4j2漏洞。
在预研一种对用户侧Java程序进行监控和防护的方式过程中,遇到的问题是Java编译的代码是字节码,在jvm中运行,不是可以执行的CPU指令,无法以常规windows hook方式对程序进行注入和拦截。最终经过探讨,以asm+ JavaAgent方式来实现,后续本文将针对JavaAgent技术进行说明。
技术简介
JavaAgent 是 JDK1.5 之后引入的新特性。
JavaAgent 是一种能够在不影响正常编译的情况下,修改字节码的技术。Java作为一种强类型的语言,不通过编译就不能够进行jar包的生成。有了JavaAgent技术,就可以在字节码这个层面对类和方法进行修改,可以把JavaAgent理解成一种代码注入的方式,或者可以说Java Agent就是JVM层面的代理程序。
使用场景
JavaAgent可以动态修改 Java 字节码,它能够:
①.在加载 Java 字节码之前进行拦截并对字节码进行修改
②.在 Jvm 运行期间修改已经加载的字节码
通过对字节码的修改我们就可以实现对JAVA底层源码的重写,在代码生成、代码修改、代码监控、代码分析四个过程中都有重要的应用。可以以AOP面向切面编程方式实现性能优化,插件化,热修复等一些安全方面的功能。
技术原理
1.关于底层JVMTI
JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的了一套代理程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM。JVMTI 的功能非常丰富,包括虚拟机中线程、内存/堆/栈,类/方法/变量,事件/定时器处理等等。
JavaAgent是依赖java底层提供的一个叫instrument的JVMTI Agent,可以将JavaAgent理解为JVM的一个“插件”。
JVM TI可以在Java启动随Java进程,自动载入共享库。或在Java进程运行时,动态加载一个外部基于JVM TI编写的dynamic module到Java进程内,然后触发JVM源生线程Attach Listener来执行这个dynamic module的回调函数。
2.JavaAgent启动时机
①.在程序启动时通过-javaagent参数启动代理程序。
②.在程序运行期间通过Java Tool API中的Attach API动态启动代理程序。
3.JavaAgent的运行流程
静态加载:
对于JVM启动时加载的Agent模块代码,Instrumentation会通过premain方法传入代理程序。
premain方法会在调用程序main方法之前被调用,仅限于应用程序的启动时,即main函数执行前。premain方法用于在启动时,在类加载前定义类的TransFormer(转化器),在类加载的时候更新对应的类的字节码。
动态加载:
关于JVM启动后动态加载Agent的方法,Instrumentation会通过agentmain方法传入程序。
agentmain方法在main函数开始运行后才被调用,其最大优势是可以在程序运行期间进行字节码的替换。agentmain方法用于在运行时进行类的字节码的修改,步骤分为注册类的TransFormer调用和retransformClasses函数进行类的重加载。
JavaAgent使用流程
静态加载
在Java运行命令中 JavaAgent是一个参数,用来指定Agent。
java -javaagent:myagent.jar -cp . examples.Main
①.创建InstrumentationImpl对象
②.监听ClassFileLoadHook事件
③.调用InstrumentationImpl的loadClassAndCallPremain方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Premain-Class类的premain方法
动态加载
运行时修改主要是通过jvm的attach机制来请求目标jvm加载对应的Agent,执行native函数的Agent_OnAttach方法,在方法执行时,执行如下步骤:
①.创建InstrumentationImpl对象
②.监听ClassFileLoadHook事件
③.调用InstrumentationImpl的loadClassAndCallAgentmain方法在这个方法里会去调用javaagent里MANIFEST.MF里指定的Agentmain-Class类的agentmain方法
配合使用比如ASM,javassist,cglib等等来改写实现类
JavaAgent测试过程
在完成了代码开发阶段后,自然还需要对此机制来进行测试验证,以确保它是否能满足我们的功能要求,同时有良好的稳定性和易用性。
在测试过程中,我们重点关注以下几点:
①.jar文件是否成功进行了静态/动态注入
②.不同jar文件之间的依赖关系
③.注入后的jar文件是否成功对漏洞进行了热修复
④.不同模块之前的通信是否正常
⑤.jar文件与运行环境jre之间的版本兼容性
⑥.热修复逻辑是否会对同一jvm内的其他非目标模块产生影响
⑦.功能可对不同模块进行单独细化控制
优点总结
在不重新定义类加载器的情况下, 对于已经加载的类重新加载,它是完全独立于应用程序的。通过Java Agent,可以以一种对应用程序几乎无感知、无侵入的方式添加一些监测能力。对业务透明,可以比较完美的实现热修复的功能。
分享给第一个想到的人