从两款开源工具学习 Java_Instrumentation 技术
2019-11-06 10:15:34 Author: xz.aliyun.com(查看原文) 阅读量:186 收藏

最近看了一下JavaProbe(0Kee-Team)和[OpenRasp(Baidu)][https://github.com/baidu/openrasp]的源码,两者都使用了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.processImpljava.lang.UNIXProcess

com.baidu.openrasp.hook.system.ProcessBuilderHook#hookMethod

这里将ProcessBuilderHook的checkCommand方法插入到构造函数之前,这样就可以在构造时进行检查传入的command。

getInvokeStaticSrc 可以用于获取执行指定类的指定方法的源代码字符串,然后通过insertBefore来插入到目标class的<init>方法中。</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程序,从而进行监控或者保护。而这种无侵入式的防护和监控是安全产品比较理想的形态。

参考文章

OpenRasp官方文档

Java SE 6 新特性Instrumentation 新功能

JVM 源码分析之 javaagent 原理完全解读

java agent简介


文章来源: http://xz.aliyun.com/t/6688
如有侵权请联系:admin#unsafe.sh