認識 JavaAgent-獲取目標程序已載入的所有類

作者:

Longofo@知道創宇404實驗室

時間:

2019年12月10日

之前在一個應用中搜索到一個類,但是在反序列化測試時出錯,錯誤不是,是其他這樣的錯誤,透過搜尋,這個錯誤大概是類沒有被載入。最近剛好看到了JavaAgent,初步學習了下,能進行攔截,主要透過Instrument Agent來進行位元組碼增強,可以進行位元組碼插樁,bTrace,Arthas 等操作,結合ASM,javassist,cglib框架能實現更強大的功能。Java RASP也是基於JavaAgent實現的。趁熱記錄下JavaAgent基礎概念,以及簡單使用JavaAgent實現一個獲取目標程序已載入的類的測試。

JVMTI 與 Java Instrument

Java平臺偵錯程式架構(Java Platform Debugger Architecture,JPDA)是一組用於除錯Java程式碼的API(摘自維基百科):

Java偵錯程式介面(Java Debugger Interface,JDI)——定義了一個高層次Java介面,開發人員可以利用JDI輕鬆編寫遠端除錯工具 Java虛擬機器工具介面(Java Virtual Machine Tools Interface,JVMTI)——定義了一個原生(native)介面,可以對執行在Java虛擬機器的應用程式檢查狀態、控制執行 Java虛擬機器除錯介面(JVMDI)——JVMDI在J2SE 5中被JVMTI取代,並在Java SE 6中被移除 Java除錯線協議(JDWP)——定義了除錯物件(一個 Java 應用程式)和偵錯程式程序之間的通訊協議

JVMTI 提供了一套“代理”程式機制,可以支援第三方工具程式以代理的方式連線和訪問 JVM,並利用 JVMTI 提供的豐富的程式設計介面,完成很多跟 JVM 相關的功能。JVMTI是基於事件驅動的,JVM每執行到一定的邏輯就會呼叫一些事件的回撥介面(如果有的話),這些介面可以供開發者去擴充套件自己的邏輯。

JVMTIAgent是一個利用JVMTI暴露出來的介面提供了代理啟動時載入(agent on load)、代理透過attach形式載入(agent on attach)和代理解除安裝(agent on unload)功能的動態庫。Instrument Agent可以理解為一類JVMTIAgent動態庫,別名是JPLISAgent(Java Programming Language Instrumentation Services Agent),是專門為java語言編寫的插樁服務提供支援的代理。

Instrumentation 介面

以下介面是Java SE 8 API文件中[1]提供的(不同版本可能介面有變化):

redefineClasses與redefineClasses:

重新定義功能在Java SE 5中進行了介紹,重新轉換功能在Java SE 6中進行了介紹,一種猜測是將重新轉換作為更通用的功能引入,但是必須保留重新定義以實現向後相容,並且重新轉換操作也更加方便。

Instrument Agent 兩種載入方式

在官方API文件[1]中提到,有兩種獲取Instrumentation介面例項的方法 :

1。JVM在指定代理的方式下啟動,此時Instrumentation例項會傳遞到代理類的premain方法。2。JVM提供一種在啟動之後的某個時刻啟動代理的機制,此時Instrumentation例項會傳遞到代理類程式碼的agentmain方法。

premain對應的就是VM啟動時的Instrument Agent載入,即,agentmain對應的是VM執行時的Instrument Agent載入,即。兩種載入形式所載入的都關注同一個事件 – 事件,這個事件是在讀取位元組碼檔案之後回撥時用,也就是說premain和agentmain方式的回撥時機都是類檔案位元組碼讀取之後(或者說是類載入之後),之後對位元組碼進行重定義或重轉換,不過修改的位元組碼也需要滿足一些要求,在最後的侷限性有說明。

premain 與 agentmaiin 的區別

和兩種方式最終的目的都是為了回撥例項並激活(InstrumentationImpl是Instrumentation的實現類)從而回調註冊到中的實現位元組碼修改,本質功能上沒有很大區別。兩者的非本質功能的區別如下:

premain方式是JDK1。5引入的,agentmain方式是JDK1。6引入的,JDK1。6之後可以自行選擇使用或者。 需要透過命令列使用外部代理jar包,即;則可以透過機制直接附著到目標VM中載入代理,也就是使用方式下,操作的程式和被代理的程式可以是完全不同的兩個程式。 方式回撥到中的類是虛擬機器載入的所有類,這個是由於代理載入的順序比較靠前決定的,在開發者邏輯看來就是:所有類首次載入並且進入程式方法之前,方法會被啟用,然後所有被載入的類都會執行列表中的回撥。 方式由於是採用機制,被代理的目標程式VM有可能很早之前已經啟動,當然其所有類已經被載入完成,這個時候需要藉助讓對應的類可以重新轉換,從而啟用重新轉換的類執行列表中的回撥。 透過premain方式的代理Jar包進行了更新的話,需要重啟伺服器,而agentmain方式的Jar包如果進行了更新的話,需要重新attach,但是agentmain重新attach還會導致重複的位元組碼插入問題,不過也有和方式來避免。

透過下面的測試也能看到它們之間的一些區別。

premain 載入方式

premain方式編寫步驟簡單如下:

1。編寫premain函式,包含下面兩個方法的其中之一:

如果兩個方法都被實現了,那麼帶Instrumentation引數的優先順序高一些,會被優先呼叫。是函式得到的程式引數,透過命令列引數傳入

2。定義一個 MANIFEST。MF 檔案,必須包含 Premain-Class 選項,通常也會加入Can-Redefine-Classes 和 Can-Retransform-Classes 選項

3。將 premain 的類和 MANIFEST。MF 檔案打成 jar 包

4。使用引數 -javaagent: jar包路徑啟動代理

premain載入過程如下:

1。建立並初始化 JPLISAgent

2。MANIFEST。MF 檔案的引數,並根據這些引數來設定 JPLISAgent 裡的一些內容

3。監聽 事件,在 JVM 初始化完成之後做下面的事情:

(1)建立 InstrumentationImpl 物件 ;

(2)監聽 ClassFileLoadHook 事件 ;

(3)呼叫 InstrumentationImpl 的方法,在這個方法裡會去呼叫 javaagent 中 MANIFEST。MF 裡指定的Premain-Class 類的 premain 方法

下面是一個簡單的例子(在JDK1。8。0_181進行了測試):

PreMainAgent

MANIFEST。MF:

Testmain

將PreMainAgent打包為Jar包(可以直接用idea打包,也可以使用maven外掛打包),在idea可以像下面這樣啟動:

認識 JavaAgent-獲取目標程序已載入的所有類

命令列的話可以用形如啟動

結果如下:

可以看到在PreMainAgent之前已經載入了一些必要的類,即PreMainAgent get loaded class:xxx部分,這些類沒有經過transform。然後在main之前有一些類經過了transform,在main啟動之後還有類經過transform,main結束之後也還有類經過transform,可以和agentmain的結果對比下。

agentmain 載入方式

agentmain方式編寫步驟簡單如下:

1。編寫agentmain函式,包含下面兩個方法的其中之一:

如果兩個方法都被實現了,那麼帶Instrumentation引數的優先順序高一些,會被優先呼叫。是函式得到的程式引數,透過命令列引數傳入

2。定義一個 MANIFEST。MF 檔案,必須包含 Agent-Class 選項,通常也會加入Can-Redefine-Classes 和 Can-Retransform-Classes 選項

3。將 agentmain 的類和 MANIFEST。MF 檔案打成 jar 包

4。透過attach工具直接載入Agent,執行attach的程式和需要被代理的程式可以是兩個完全不同的程式:

agentmain方式載入過程類似:

1。建立並初始化JPLISAgent

2。解析MANIFEST。MF 裡的引數,並根據這些引數來設定 JPLISAgent 裡的一些內容

3。監聽 事件,在 JVM 初始化完成之後做下面的事情:

(1)建立 InstrumentationImpl 物件 ;

(2)監聽 ClassFileLoadHook 事件 ;

(3)呼叫 InstrumentationImpl 的方法,在這個方法裡會去呼叫javaagent裡 MANIFEST。MF 裡指定的類的方法。

下面是一個簡單的例子(在JDK 1。8。0_181上進行了測試):

SufMainAgent

MANIFEST。MF

TestSufMainAgent

Testmain

將SufMainAgent和TestSufMainAgent打包為Jar包(可以直接用idea打包,也可以使用maven外掛打包),首先啟動Testmain,然後先列下當前有哪些Java程式:

attach SufMainAgent到Testmain:

在Testmain中的結果如下:

和前面premain對比下就能看出,在agentmain中直接getloadedclasses的類數目比在premain直接getloadedclasses的數量多,而且premain getloadedclasses的類+premain transform的類和agentmain getloadedclasses基本吻合(只針對這個測試,如果程式中間還有其他通訊,可能會不一樣)。也就是說某個類之前沒有載入過,那麼都會透過兩者設定的transform,這可以從最後的java/lang/Shutdown看出來。

測試 Weblogic 的某個類是否被載入

這裡使用weblogic進行測試,代理方式使用agentmain方式(在jdk1。6。0_29上進行了測試):

WeblogicSufMainAgent

WeblogicTestSufMainAgent:

列出正在執行的Java應用程式:

進行attach:

Weblogic輸出:

認識 JavaAgent-獲取目標程序已載入的所有類

假如在進行Weblogic t3反序列化利用時,如果某個類之前沒有被載入,但是能夠被Weblogic找到,那麼利用時對應的類會透過Agent的transform,但是有些類雖然在Weblogic目錄下的某些Jar包中,但是weblogic不會去載入,需要一些特殊的配置Weblogic才會去尋找並載入。

Instrumentation 侷限性

大多數情況下,使用Instrumentation都是使用其位元組碼插樁的功能,籠統說是類重轉換的功能,但是有以下的侷限性:

1。premain和agentmain兩種方式修改位元組碼的時機都是類檔案載入之後,就是說必須要帶有Class型別的引數,不能透過位元組碼檔案和自定義的類名重新定義一個本來不存在的類。這裡需要注意的就是上面提到過的重新定義,剛才這裡說的不能重新定義是指不能重新換一個類名,位元組碼內容依然能重新定義和修改,不過位元組碼內容修改後也要滿足第二點的要求。2。類轉換其實最終都回歸到類重定義Instrumentation#retransformClasses()方法,此方法有以下限制:

1。新類和老類的父類必須相同;

2。新類和老類實現的介面數也要相同,並且是相同的介面;

3。新類和老類訪問符必須一致。新類和老類欄位數和欄位名要一致;

4。新類和老類新增或刪除的方法必須是private static/final修飾的;

5。可以刪除修改方法體。

實際中遇到的限制可能不止這些,遇到了再去解決吧。如果想要重新定義一全新類(類名在已載入類中不存在),可以考慮基於類載入器隔離的方式:建立一個新的自定義類載入器去透過新的位元組碼去定義一個全新的類,不過只能透過反射呼叫該全新類的侷限性。

小結

文中只是描述了JavaAgent相關的一些基礎的概念,目的只是知道有這個東西,然後驗證下之前遇到的一個問題。寫的時候也借鑑了其他大佬寫的幾篇文章[4]&[5] 在寫文章的過程中看了一些如一類PHP-RASP實現的漏洞檢測的思路[6],利用了汙點跟蹤、hook、語法樹分析等技術,也看了幾篇大佬們整理的Java RASP相關文章[2]&[3],如果自己要寫基於RASP的漏洞檢測/利用工具的話也可以借鑑到這些思路

程式碼放到了github[7]上,有興趣的可以去測試下,注意pom。xml檔案中的jdk版本,在切換JDK測試如果出現錯誤,記得修改pom。xml裡面的JDK版本。

參考

1。https://docs。oracle。com/javase/8/docs/api/java/lang/instrument/Instrumentation。html

2。https://paper。seebug。org/513/#0x01-rasp

3。https://paper。seebug。org/1041/#31-java-agent

4。http://www。throwable。club/2019/06/29/java-understand-instrument-first/#Instrumentation%E6%8E%A5%E5%8F%A3%E8%AF%A6%E8%A7%A3

5。https://www。cnblogs。com/rickiyang/p/11368932。html

6。https://c0d3p1ut0s。github。io/%E4%B8%80%E7%B1%BBPHP-RASP%E7%9A%84%E5%AE%9E%E7%8E%B0/7。https://github。com/longofo/learn-javaagent

7。https://github。com/longofo/learn-javaagent

往 期 熱 門

認識 JavaAgent-獲取目標程序已載入的所有類

認識 JavaAgent-獲取目標程序已載入的所有類

認識 JavaAgent-獲取目標程序已載入的所有類

覺得不錯點個“在看”哦