破解 Java Agent 探針黑科技
時間:2023-06-27 23:27:02 | 來源:網(wǎng)站運營
時間:2023-06-27 23:27:02 來源:網(wǎng)站運營
破解 Java Agent 探針黑科技:
一、什么是 Java Agent ?
籠統(tǒng)地來講,Java Agent 是一個統(tǒng)稱,該功能是 Java 虛擬機提供的一整套后門。通過這套后門可以對虛擬機方方面面進行監(jiān)控與分析。甚至干預(yù)虛擬機的運行。
Java Agent 又叫做 Java 探針,Java Agent 是在 JDK1.5 引入的,是一種可以動態(tài)修改 Java 字節(jié)碼的技術(shù)。Java 類編譯之后形成字節(jié)碼被 JVM 執(zhí)行,在 JVM 在執(zhí)行這些字節(jié)碼之前獲取這些字節(jié)碼信息,并且通過字節(jié)碼轉(zhuǎn)換器對這些字節(jié)碼進行修改,來完成一些額外的功能,這種就是 Java Agent 技術(shù)。
從用戶使用層面來看,Java Agent 一般通過在應(yīng)用啟動參數(shù)中添加 -javaagent 參數(shù)添加 ClassFileTransformer 字節(jié)碼轉(zhuǎn)換器。 在 Java 虛擬機啟動時,執(zhí) 行main() 函數(shù)之前,Java 虛擬機會先找到 -javaagent 命令指定 jar 包,然后執(zhí)行 premain-class 中的 premain() 方法。用一句概括其功能的話就是:main() 函數(shù)之前的一個攔截器。
二、Java Agent 可以實現(xiàn)什么樣的功能?
從上面提到的字節(jié)碼轉(zhuǎn)換器的兩種執(zhí)行方式來看可以實現(xiàn)如下功能:
- Java Agent 能夠在加載 Java 字節(jié)碼之前進行攔截并對字節(jié)碼進行修改;
- 在 Jvm 運行期間修改已經(jīng)加載的字節(jié)碼;
因此,通過以上兩點即可實現(xiàn)在一些框架或是技術(shù)的采集點進行字節(jié)碼修改,對應(yīng)用進行監(jiān)控(比如通過JVM CPU Profiler 從CPU、Memory、Thread、Classes、GC等多個方面對程序進行動態(tài)分析),或是對執(zhí)行指定方法或接口時做一些額外操作,比如打印日志、打印方法執(zhí)行時間、采集方法的入?yún)⒑徒Y(jié)果等;
基于前面對 Java Agent 大致機制的描述,我們不難猜到,能夠干預(yù) Java JVM 虛擬機的運行,那么就可以解決不限于如下的問題:
- 使用 JVMTI 對 class 文件加密:有時一些涉及到關(guān)鍵技術(shù)的 class 文件或者 jar 包我們不希望對外暴露,因而需要進行加密。使用一些常規(guī)的手段(例如使用混淆器或者自定義類加載器)來對 class 文件進行加密很容易被反編譯。反編譯后的代碼雖然增加了閱讀的難度,但花費一些功夫也是可以讀懂的。使用 JVMTI 我們可以將解密的代碼封裝成 .dll, 或 .so 文件。這些文件想要反編譯就很麻煩了,另外還能加殼。解密代碼不能被破解,從而也就保護了我們想要加密的 class 文件。
- 使用 JVMTI 實現(xiàn)應(yīng)用性能監(jiān)控(APM) 在微服務(wù)大行其道的環(huán)境下,分布式系統(tǒng)的邏輯結(jié)構(gòu)變得越來越復(fù)雜。這給系統(tǒng)性能分析和問題定位帶來了非常大的挑戰(zhàn)?;贘VMTI的APM能夠解決分布式架構(gòu)和微服務(wù)帶來的監(jiān)控和運維上的挑戰(zhàn)。APM通過匯聚業(yè)務(wù)系統(tǒng)各處理環(huán)節(jié)的實時數(shù)據(jù),分析業(yè)務(wù)系統(tǒng)各事務(wù)處理的交易路徑和處理時間,實現(xiàn)對應(yīng)用的全鏈路性能監(jiān)測。開源的Skywalking、Pinpoint,、ZipKin、 Hawkular, 商業(yè)的 AppDynamics、OneAPM、Google Dapper等都是個中好手。
另外來看看 Github 上有哪些開源工具、項目使用到了 Agent 技術(shù):
- 阿里巴巴開源的 Java 診斷工具—— Arthas,深受開發(fā)者喜愛。在線排查問題,無需重啟;動態(tài)跟蹤 Java 代碼;實時監(jiān)控 JVM 狀態(tài)。
- Apache Skywalking 的 Java Agent 則針對服務(wù)的調(diào)用鏈路、JVM 基礎(chǔ)監(jiān)控信息進行采集。
- Uber/jvm-profiler: 通過 Java Agent 采集 JVM CPU、Memory、IO等指標(biāo)并發(fā)送給 Kafka、Console 以及可以自定義的發(fā)送器。
三、Java Agent 的實現(xiàn)原理?
從 JVM 類加載流程來看,字節(jié)碼轉(zhuǎn)換器的執(zhí)行方式有兩種:一種是在 main 方法執(zhí)行之前,通過 premain 來實現(xiàn),另一種是在程序運行中,通過 Attach Api 來實現(xiàn)。
對于 JVM 內(nèi)部的 Attach 實現(xiàn),是通過
tools.jar
這個包中的
com.sun.tools.attach.VirtualMachine
以及
VirtualMachine.attach(pid)
這種方式來實現(xiàn)的。底層則是通過
JVMTI
在運行前或者運行時,將自定義的 Agent 加載并和 VM 進行通信。
了解 Java Agent 的實現(xiàn)原理就必須先了解 Java 的類加載機制(這里不做過多介紹),這個是了解 Java Agent 的前提。
JVM 在類加載時觸發(fā) JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件調(diào)用添加的字節(jié)碼轉(zhuǎn)換器完成字節(jié)碼轉(zhuǎn)換,該過程時序如下:
Java Agent 所使用的 Instrumentation 依賴 JVMTI 實現(xiàn),當(dāng)然也可以繞過 Instrumentation 直接使用 JVMTI 實現(xiàn) Agent。因此,JVMTI 與 JDI 組成了 Java 平臺調(diào)試體系(JPDA)的主要能力。
如果想要深入了解 Java Agent,就得需要了解 JVMTI 以及 JVMTIAgent,下面分別介紹下:
JVMTI
JVMTI 是JVM Tool Interface 的縮寫,是 JVM 暴露出來給用戶擴展使用的接口集合,JVMTI 是基于事件驅(qū)動的,JVM每執(zhí)行一定的邏輯就會調(diào)用一些事件的回調(diào)接口,這些接口可以給用戶自行擴展來實現(xiàn)自己的邏輯。JVMTI是實現(xiàn) Debugger、Profiler、Monitor、Thread Analyser 等工具的統(tǒng)一基礎(chǔ),在主流 Java 虛擬機中都有實現(xiàn)。
JVMTIAgent
JVMTI 并不一定在所有的 Java 虛擬機上都有實現(xiàn),不同的虛擬機的實現(xiàn)也不盡相同。不過在一些主流的虛擬機中,比如 Sun 和 IBM,以及一些開源的如 Apache Harmony DRLVM 中,都提供了標(biāo)準(zhǔn) JVMTI 實現(xiàn)。
JVMTI 是一套本地代碼接口,因此使用 JVMTI 需要我們與 C/C++ 以及 JNI 打交道。事實上,開發(fā)時一般采用建立一個 Agent 的方式來使用 JVMTI,它使用 JVMTI 函數(shù),設(shè)置一些回調(diào)函數(shù),并從 Java 虛擬機中得到當(dāng)前的運行態(tài)信息,并作出自己的判斷,最后還可能操作虛擬機的運行態(tài)。把 Agent 編譯成一個動態(tài)鏈接庫之后,我們就可以在 Java 程序啟動的時候來加載它(啟動加載模式),也可以在 Java 5 之后使用運行時加載(活動加載模式)。
-agentlib:agent-lib-name=options-agentpath:path-to-agent=options
JVMTIAgent主要有三個方法,
- Agent_OnLoad 方法,如果 agent 在啟動時加載,就執(zhí)行這個方法
- Agent_OnAttach方法,如果agent不是在啟動的時候加載的,是我們先attach到目標(biāo)線程上,然后對對應(yīng)的目標(biāo)進程發(fā)送load命令來加載agent,在加載過程中調(diào)用Agent_OnAttach函數(shù)
- Agent_OnUnload 方法,在 agent 做卸載掉時候調(diào)用
Instrument Agent
說到 javaagent,必須要講的是一個叫做 instrument 的 JVMTIAgent(Linux下對應(yīng)的動態(tài)庫是 libinstrument.so) instrument agent 實現(xiàn)了上面 Agent_OnLoad 方法和 Agent_OnAttach 方法,也就是即能在啟動的時候加載 agent,也可以在運行期來加動態(tài)加載 agent,運行期動態(tài)加載 agent 依賴 JVM 的 attach 機制實現(xiàn),通過發(fā)送 load 命令來加載 agent
那么什么是 JVM Attach 機制?
JVM Attach 機制
Jvm attach 機制是指 JVM 提供的一種 JVM 進程間通信的功能,能讓一個進程傳命令給另一個進程,并進行一些內(nèi)部的操作,比如進行線程 dump,那么就需要執(zhí)行 jstack 進行,然后把 pid 等參數(shù)傳遞給需要 dump 的線程來執(zhí)行,這就是一種 java attach。
四、可以實現(xiàn) Java Agent 的技術(shù)框架有哪些?
原理了解清楚了就需要實現(xiàn),Java Agent 從實現(xiàn)上來看主要涉及到字節(jié)碼增強的過程,其到過程大概是:
- 修改字節(jié)碼
- 加載新的字節(jié)碼
- 替換舊的字節(jié)碼
通過上面對 Java Agent 介紹之后,是不是發(fā)現(xiàn),我想要實現(xiàn)一個 Java Agent 還得去深入學(xué)習(xí)那么多東西嗎?
當(dāng)然不用,這里就介紹幾個常用的字節(jié)碼增強工具:
- ASM:對于需要手動操縱字節(jié)碼的需求,可以使用 ASM,它可以直接生成 .class 字節(jié)碼文件,也可以在類被加載入 JVM 之前動態(tài)修改類行為。
- Javassist: ASM 是在指令層次上操作字節(jié)碼的,我們的直觀感受是在指令層次上操作字節(jié)碼的框架實現(xiàn)起來比較晦澀。故除此之外,再簡單介紹另外一類框架:強調(diào)源代碼層次操作字節(jié)碼的框架 Javassist。利用 Javassist 實現(xiàn)字節(jié)碼增強時,可以無須關(guān)注字節(jié)碼刻板的結(jié)構(gòu),其優(yōu)點就在于編程簡單。直接使用 Java 編碼的形式,而不需要了解虛擬機指令,就能動態(tài)改變類的結(jié)構(gòu)或者動態(tài)生成類。
- Instrument:Instrument 是 JVM 提供的一個可以修改已加載類的類庫,專門為 Java 語言編寫的插樁服務(wù)提供支持。它需要依賴 JVMTI 的 Attach API 機制實現(xiàn)。
- Byte Buddy:ByteBuddy 是一個開源 Java 庫,其主要功能是幫助用戶屏蔽字節(jié)碼操作,以及復(fù)雜的 InstrumentationAPI。ByteBuddy 提供了一套類型安全的API和注解,我們可以直接使用這些 API 和注解輕松實現(xiàn)復(fù)雜的字節(jié)碼操作。另外,Byte Buddy 提供了針對 Java Agent 的額外 API,幫助開發(fā)人員在 Java Agent 場景輕松增強已有代碼。