从两款开源工具学习 Java_Instrumentation 技术
作者:Litch1@Dubhe
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送!
投稿邮箱:paper@seebug.org
最近看了一下JavaProbe(0Kee-Team)和OpenRasp(Baidu)的源码,两者都使用了instrumentation agent技术,但是由于场景不同,所以使用的差异也比较大,这篇笔记对于java instrumentation两种加载方式以及进行学习。由于个人水平有限,写的地方肯定有很多错误,还请各位师傅指出。
Instrumentation
对于instrumentation agent来说有两种代理方式,一种是在类加载之前进行代理,可以进行字节码的修改即所谓的agent on load。另一种是在运行时动态进行加载,这种方法对于字节码的修改有较大的限制,但是利用运行时动态加载可以获得JVM的一些运行时信息,这种方式为agent on attach 。
OpenRasp
关于RASP网上有不少分析的文章,这里就不多赘述了。
其基本的检测思路:Hook住程序的一些敏感类或敏感方法,通过检查传入的参数和上下文信息来判断请求是否恶意。
想要Hook住程序的一些敏感类或敏感方法(通常都是一些JDK中的类或方法),添加我们的检查方法,就需要动态的修改类定义时的字节码。OpenRasp采用Java Instrumentation来实现。
OpenRasp的入口在agent的premain方法,premain方法会在main方法运行之前先运行。大概梳理了一下与instrumentation有关的调用的流程
关键函数分析:
com.baidu.openrasp.Agent#premain
public static void premain(String agentArg, Instrumentation inst) { init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
}
在agent的pom里面定义好了MANIFEST.MF文件的premain-class选项
<Premain-Class>com.baidu.openrasp.Agent</Premain-Class>
为目标程序添加我们的agent需要在目标程序启动的时候添加-javaagent参数
在JVM初始化完成之后会创建InstrumentationImpl对象,监听ClassFileLoadHook事件,接着会去调用javaagent里MANIFEST.MF里指定的Premain-Class类的premain方法
premain第二个参数中的Instrumentation是我们字节码转换的工具,因为监听了ClassFileLoadHook事件,一旦有类加载的事件发生,便会触发Instrumentation去调用其已经注册的Transformer的transform方法去进行字节码的更改。
在OpenRasp中,为Instrumentation注册了com.baidu.openrasp.transformer.CustomClassTransformer
public CustomClassTransformer(Instrumentation inst) { this.inst = inst;
inst.addTransformer(this, true);
addAnnotationHook();
}
来看一下CustomClassTransform的transform方法
com.baidu.openrasp.transformer.CustomClassTransformer#transform
我们随便挑一个注册的Hook来分析看看,这里具体的如何进行转化transform主要是由每一个继承了AbstractClassHook这个抽象类的hookMethod方法来决定的
com.baidu.openrasp.hook.system.ProcessBuilderHook
这个类主要是用来检测采用ProcessBuilder来执行系统命令的恶意请求,在最后调用ProcessBuilder.start()时,恶意系统命令会传递到ProcessImpl或者UNIXProcess的构造函数中
所以Match的class是java.lang.processImpl
或java.lang.UNIXProcess
com.baidu.openrasp.hook.system.ProcessBuilderHook#hookMethod
这里将ProcessBuilderHook的checkCommand方法插入到构造函数之前,这样就可以在构造时进行检查传入的command。
getInvokeStaticSrc 可以用于获取执行指定类的指定方法的源代码字符串,然后通过insertBefore来插入到目标class的\<init>方法中。
insertBefore方法的核心是javassist.CtBehavior#insertBefore(java.lang.String)
,所以最后就是利用javassist提供的一些封装方法来操作字节码进行目标方法的插入。
注:getInvokeStaticSrc参数中paramString之所以是1,2这种形式,是因为源代码在传入javassit的insertBefore方法时,会将1,2这种形式解析为目标方法的第一个参数,第二个参数,以此类推
总结:
OpenRasp使用agent在jvm初始化后进入premain方法,将自定义的ClassTransformer注册到instrumentation中,在有类加载时会触发其的transform方法,其根据匹配的class去调用具体hook的transform方法,在里面使用了javassit来操作字节码来改变被hook的class类定义时的字节码。
JavaProbe
JavaProbe使用agentmain来收集运行中的JVM的信息。
agentmain和premain比较相似,区别是agentmain允许在main函数启动后再运行我们的agentmain方法。并且其不需要在目标程序启动的时候添加-javaagent,目标程序独立运行,没有侵入性,更加灵活,但是同时其对于字节码的修改限制比较大。其和premain函数一样需要在MANIFEST.MF
中指定参数
Agent-Class: newagent.HookMain
JavaProbe有多用户模式和单用户模式,多用户模式是利用runuser来切换用户,本质也是执行单用户模式,方便分析,我们直接分析JavaProbe的单用户模式。
newagent.NewAgentMain#hookIng
的关键部分截取:
利用com.sun.tools.attach.VirtualMachine#list
来获取该用户正在运行的所有JVM List,对这个List进行遍历,通过虚拟机的id attach到指定的虚拟机上,接着使用loadAgent方法来加载我们的代理agent,这样就可以在指定的虚拟机上运行我们想要运行的附加程序。loadagent方法的第二个参数会作为参数传入agentmain方法,接着便会去执行agentmain方法。
agentmain的逻辑也比较简单,关键主要就是利用java.lang.instrument.Instrumentation#getAllLoadedClasses
来获得JVM已经加载的类,以及使用System.getProperty来获得JVM实际运行的一些关键信息
注:使用agentmain也可以获得获取所有已经初始化过的类,或者类的大小等等。
总结
JavaProbe为了无侵入地获得所有JVM的运行时信息,采用instrumentation的agentmain,独立于其他目标JVM,可以动态将代理attach到指定的JVM上去获取有关的信息。
Instrumentation总结
无论是premain或是agentmain,使用instrumentation技术地优点都在于无侵入以及深入性,可以在运行时动态深入到JVM层面去获取信息,或是在字节码层面改变运行时的Java程序,从而进行监控或者保护。而这种无侵入式的防护和监控是安全产品比较理想的形态。
参考文章
Java SE 6 新特性Instrumentation 新功能
以上是 从两款开源工具学习 Java_Instrumentation 技术 的全部内容, 来源链接: utcz.com/p/199466.html