作者:evilpan
原文鏈接:https://evilpan.com/2020/10/07/jni-helper/
國慶幾天在家寫了個用于輔助 JNI 接口逆向分析的工具,同時支持 Ghidra、IDA 以及 Radare2。本文即是對這個工具的簡單介紹,以及一些編寫插件的體驗記錄。
前言
平時進行安卓逆向時,一個常見的場景就是目標把關鍵邏輯放到 Native 代碼中,使用 JNI 接口進行實現。進行分析一般是把對應的動態庫so拖進逆向工具中,然后定位具體的 Native 實現,再對參數類型、JNI 調用等邏輯進行一些優化方便對反匯編/反編譯代碼的理解。比如對于大家常用的 IDA-Pro 就是 Parse C Header 加載 jni_all.h,然后修改 JNI 接口的函數原型和變量結構。
這對于少量代碼來說不是大問題,但顯然是一種重復性的勞動,因此我們可以對這個過程進行一定的自動化。目前已經有了一些優秀的項目,比如 aryx 的 JNIAnalyzer,這是一個 Ghidra 插件,支持從 apk 中提取 JNI 接口并批量修改動態庫的函數原型。但是這些項目都存在一些問題,而且缺乏拓展性,所以為了方便自己使用就構建了 JNI Helper 項目,支持各類日常使用的逆向工具。
JNI Helper
該項目的詳細介紹可以參考 Github,其主要特性有下面這些:
- 基于 Jadx api 提供一個獨立的 Java 可執行程序
JadxFindJNI.jar,用來分析 apk 并提取其中的 JNI 接口信息,并產出一個 JSON 文件; - 同時支持 C 和 C++ 類型的 JNI 接口實現;
- 支持重載的 JNI 函數;
- 分別實現了 Ghidra、IDA 和 Radare2 的插件用來加載
JadxFindJNI.jar生成的 JSON 文件;
JadxFindJNI.jar
得益于 Jadx 項目整潔的函數接口,我們可以很方便在其基礎上實現自己的功能,如下所示:
JadxArgs jadxArgs = new JadxArgs(); jadxArgs.setDebugInfo(false); jadxArgs.setSkipResources(true); jadxArgs.getInputFiles().add(new File(args[0])); JadxDecompiler jadx = new JadxDecompiler(jadxArgs); jadx.load();
我們需要實現的主要功能就是調用 Jadx 分析目標 apk,然后迭代每個類和其中的 native 方法。只是有一些需要注意的點,比如對于重載的 JNI 函數。根據 Oracle 的文檔,JNI native 函數的命名由以下方式組成:
- 以
Java_為前綴; - 格式化的類名完整路徑;
- 下劃線分隔符
_; - 格式化的函數名稱;
- 對于重載函數,需要加雙下劃線
__并跟著參數簽名; - 對于特殊的字符需要進行轉義,比如
_、;和]需要分別轉義成_1、_2和_3等;
對于使用者而言,無需關心內部細節:
$ java -jar JadxFindJNI/JadxFindJNI.jar Usage: JadxFindJNI.jar <file.apk> <output.json>
我在倉庫中上傳了一個編譯好的 demo/app-debug.apk,所生成的 JNI 簽名信息如下:
{ "Java_com_evilpan_demojni_MainActivity_testOverload__I": { "argumentSignature": "I", "argumentTypes": [ "jint" ], "returnType": "jint", "isStatic": false }, "Java_com_evilpan_demojni_MainActivity_testStatic": { "argumentSignature": "I", "argumentTypes": [ "jint" ], "returnType": "jint", "isStatic": true }, "Java_com_evilpan_demojni_MainActivity_stringFromJNI": { "argumentSignature": "", "argumentTypes": [], "returnType": "jstring", "isStatic": false }, "Java_com_evilpan_demojni_MainActivity_c_1testArray": { "argumentSignature": "[I", "argumentTypes": [ "jintArray" ], "returnType": "void", "isStatic": false }, ... }
該 JSON 函數簽名文件以 JNI 函數的 native 名稱為鍵,可以方便地在各種語言中反序列化為哈希表,從而方便函數的查找。
JadxFindJNI.jar可以自己編譯,也可以使用打包好的版本。
實現效果
只要有了函數簽名信息,就很方便在各種逆向工具中進行自動化處理了,這里選取的是我比較常用的幾個逆向工具,Ghidra、IDA 和 Radare2。這幾個工具的插件都是使用 Python 編寫的,感興趣可以直接查看源碼,優化前后的反編譯代碼如下圖所示。
Ghidra 優化前:

Ghidra 優化后:

IDA-Pro 優化前:

IDA-Pro 優化后:

詳見 https://github.com/evilpan/jni_helper
插件編寫體驗
在實現 JNI Helper 的過程中,摸索了一遍不同逆向工具的拓展功能,所以這里談談編寫過程中的一些感受,正好也可以作為一次橫向對比。
Ghidra
Ghidra 作為 NSA 出品的工具,擁有豐富的內部資料,開發文檔非常整潔規范。雖然說是機緣巧合之下泄露出來才開源的版本,但其質量可以和許多商業工具相媲美,甚至在很多方面還稍勝一籌。使用下來發現 Ghidra 有很多在線文檔和資料,可以很方便地實現指定功能,這個跟后面的兩個工具可以說形成鮮明對比。
由于 Ghidra 是使用 Java 進行開發的,因此一個明顯的問題是運行分析速度相對較慢。這個問題我的一個解決辦法是通過 headlessAnalyzer 在性能較好的云服務器后臺對目標先進行分析,導出 .gzf 項目文件再在本地導入。本地的 Ghidra 插件通常使用 Java 進行開發,但可以通過 JPython 使用 Python 腳本編寫。
得益于開源軟件的特性,開源社區中對于 Ghidra 插件和資料的貢獻一直呈爆發式增長。當然這也是因為軟件本身投入了很多國家經費被打磨過很長時間,另外一個開源逆向工具 Radare2 就相形見絀了,后面會說到。
一些比較有用的 Ghidra 相關參考資料如下:
- https://ghidra.re/ghidra_docs/api/
- https://ghidra.re/online-courses/
- https://github.com/cetfor/GhidraSnippets
- https://github.com/AllsafeCyberSecurity/awesome-ghidra
IDA Pro
老牌逆向工具,在 Ghidra 出來之前已經制霸了十幾年,因此也擁有豐富的插件生態。從使用者的角度來說,IDA 無疑是一個優秀的選擇,其反編譯器可以給出非常接近源碼的高層偽代碼,極大減少了逆向工程師的工作量。缺點也就一個: 太貴。每年幾千美刀的授權費用對于個人逆向愛好者而言并不是一個小數目,而且還分別針對不同的指令集架構進行收費。之前還滿心期待家庭版的 IDA Home,365美元一年,但是沒有 decompiler、沒有 SDK、還不能商業使用,于是我又默默關閉頁面并掏出了 Binary Ninja。
因此要有一個舒適的逆向體驗,還是需要一個 IDA Pro。從插件開發者的角度來說,IDA Pro 本身確實提供了接口文檔,而且由于年代久遠也積累了許多插件生態,但是實際使用下來還是有些卡殼。舉例來說,在寫插件的過程中遇到一個需求,比如判斷jni_all.h這個頭文件是否已經加載過,即某個結構體是否已經定義,在文檔中說是使用下面的 idapython 接口:
idaapi.get_struc_id('JNIInvokeInterface_') != idaapi.BADADDR
可是實際上這樣即便 JNI 接口已經定義,返回也是 BADADDR 。最終實現還是通過一種比較取巧的方法,即根據下面的語句是否拋出異常來判斷:
idc.parse_decl('JNIInvokeInterface_ *if', idc.PT_SILENT)
類似的問題還有不少,而且很多技巧需要通過閱讀其他人的插件代碼去了解,對于不熟悉的開發者來說體驗并不是太好。下面是一些 IDA 開發相關的資料:
- https://www.hex-rays.com/products/ida/support/
- https://www.hex-rays.com/products/ida/support/tutorials/
- https://www.hex-rays.com/products/ida/support/sdkdoc/index.html
- https://gist.github.com/icecr4ck/7a7af3277787c794c66965517199fc9c
Radare2
作為一個骨灰級命令行愛好者,接觸 Radare2(r2) 也是順應自然的選擇。最初使用 Radare2 的一個原因是因為其開源,可以在各個平臺中使用;另外一個原因是支持 VI 快捷鍵,省去了我很大的學習成本。雖然一開始以命令行逆向工具為賣點,但也可以配合 Cutter 使用。
雖然每次我都狂熱推薦使用 r2,但實際上他也有明顯的局限性,其中一個是 decompiler 的缺乏。這可以使用一些三方的 decompiler 實現,比如:
- Ghidra Decompiler
- RetDec Decompiler
- r2dec
作為插件開發者,使用的主要是 r2pipe 接口,這個接口實際上是 r2 的命令行參數的管道,所以寫插件本質上還是需要通過 r2 的命令實現。說到 r2 的命令,內置的幫助信息可以方便日常使用的查詢,用戶只需要記得大致的命令類目,比如分析類命令是a開頭,可以使用a?查看所有分析相關的命令,進而可以使用af?查看所有和函數分析相關的命令。
雖然和 Ghidra 一樣是開源工具,但由于 r2 主要是愛好者去維護,因此投入上的差距也導致了開發進度的差距。在 Github 中可以看到開發者們經常在努力修 bug 以及更新優化代碼,但關閉 issue 的速度遠跟不上新開 issue 的速度。舉一個遇到的例子,我想要修改某個地址對應函數的簽名為對應 JNI 函數簽名,搜索后發現有幾種方法: 一是使用 afvr/avfn/afvt 修改變量;二是使用 tl (type link)去連接類型;三是使用 afs 去直接修改簽名信息。可測試下來這幾種方式都有問題。其中 afs 是最接近我需求的命令了,但是卻不支持自定義的類型,而解決這個問題還需要等 r2 項目組將內置 parser 從 Tcc遷移到 tree-sitter (見 issue#17432)。諸如此類的問題也是比較打擊插件開發者積極性的,畢竟誰也不想為了一個簡單的功能找半天文檔發現無法實現。
盡管如此,我還是非常看好 Radare2 的未來,他年歲尚淺,但有很大的拓展性,值得持續保持關注。同時也應該給開發者更多的耐心和支持,如果可以的話,為其添磚加瓦。下面是一些 r2 相關的資料:
- https://github.com/radareorg/radare2/blob/master/doc/intro.md
- https://radare.gitbooks.io/radare2book/content/
- https://book.rada.re/
后記
本文主要是分享 JNI Helper 這個輔助自動化靜態逆向分析 JNI 接口的工具,可以在日常安卓逆向時候減少一些重復的勞動。俗話說工欲善其事,必先利其器,在日常中打磨自己的武器庫值得投入必要時間,但也要避免陷入盲目造輪子的沖動。另一方面,也記錄一下在編寫各個逆向工具插件過程中的一些感受。總的來說 Ghidra 的插件編寫體驗最好,文檔豐富且完善,不愧為 NSA 的產品;其次是 IDAPython,雖然有部分文檔,但很多都只是一句話帶過,想查找某些特定功能需要花費額外時間;而對于 Radare2,雖然是使用管道來進行批處理,但基本的交互都可以支持,只是部分功能實現尚不完善,需要給開發者更多的支持和耐心,畢竟大家都是花費著自己的業余時間和精力去維護和貢獻。希望能有更多人投入到 Radare2 的社區中,哪怕只是貢獻一點文檔,寫一寫 writeup,對開源社區的幫助也是很大的。
參考鏈接
- JNI 文檔
- skylot/jadx
- Ayrx/JNIAnalyzer
- https://github.com/evilpan/jni_helper
- https://evilpan.com/2020/10/07/jni-helper/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1365/
暫無評論