作者: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之所以是2這種形式,是因為源代碼在傳入javassit的insertBefore方法時,會將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 新功能
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1071/
暫無評論