基於FRIDA的工具,可在Android應用中追踪JNI API的使用。
Android應用中包含的本機庫通常使用JNI API利用Android運行時。通過手動反向工程跟踪這些呼叫可能是一個緩慢而痛苦的過程。 jnitrace是一種動態分析跟踪工具,類似於Frida-Trace或Strace,但對於JNI。
使用jnitrace運行的最簡單方法是使用PIP安裝:
pip install jnitrace
PIP安裝後,很容易運行jnitrace :
jnitrace -l libnative-lib.so com.example.myapplication
jnitrace需要至少兩個參數才能運行跟踪:
-l libnative-lib.so用於指定要跟踪的庫。該參數可以多次使用, *可以用於跟踪所有庫。例如, -l libnative-lib.so -l libanother-lib.so或-l * 。com.example.myapplication是要跟踪的Android軟件包。該軟件包必須已經安裝在設備上。可選論點如下:
-R <host>:<port> - 用於指定遠程Frida服務器的網絡位置。如果A:未指定,則Localhost:27042由DeAfult使用。-m <spawn|attach> - 用於指定用於使用的Frida附加機構。它可以產生或附著。 Spawn是默認和推薦選項。-b <fuzzy|accurate|none> - 用於控制返回跟踪輸出。默認情況下, jnitrace將以accurate模式運行迴避器。可以將此選項更改為fuzzy模式,也可以通過使用none選項來阻止返回。有關差異的解釋,請參見Frida文檔。-i <regex> - 用於指定應追踪的方法名稱。這可能有助於減少特別大的JNI應用程序中的噪聲。該選項可以多次提供。例如, -i Get -i RegisterNatives只包括以其名稱包含獲取或登記劑的JNI方法。-e <regex> - 用於指定跟踪中應忽略的方法名稱。這可能有助於減少特別大的JNI應用程序中的噪聲。該選項可以多次提供。例如, -e ^Find -e GetEnv將從結果中排除所有開始查找或包含getEnv的JNI方法名稱。-I <string> - 用於指定應追踪的庫中的導出。這對於您只想追踪少量方法的庫很有用。 jnitrace認為導出的功能是直接從Java側呼叫的任何功能,包括使用登記劑限制的方法。該選項可以多次提供。例如, -I stringFromJNI -I nativeMethod([B)V可用於包括來自名為Java_com_nativetest_MainActivity_stringFromJNI的庫中的導出,並使用寄存器名稱和使用nativeMethod([B)V的簽名的寄存器名稱綁定的方法。-E <string>用於指定不應追踪的庫中的導出。這對於您想要忽略的一組繁忙的本機通話的庫很有用。 jnitrace認為導出的功能是直接從Java側呼叫的任何功能,包括使用登記劑限制的方法。該選項可以多次提供。例如, -E JNI_OnLoad -E nativeMethod將從跟踪中排除JNI_OnLoad函數調用和任何具有名稱nativeMethod的方法。-o path/output.json用於指定jnitrace將存儲所有跟踪數據的輸出路徑。該信息以JSON格式存儲,以允許以後的跟踪數據進行後處理。-p path/to/script.js提供的路徑用於在加載jnitrace腳本之前將FRIDA腳本加載到目標過程中。這可用於在jnitrace開始之前擊敗抗Frida或反欺騙代碼。-a path/to/script.js加載jnitrace後,提供的路徑用於將FRIDA腳本加載到目標過程中。--hide-data - 用於減少控制台中顯示的輸出量。此選項將隱藏顯示為hexdumps或字符串去引用的其他數據。--ignore-env使用此選項將隱藏該應用程序使用JNienv struct進行的所有調用。--ignore-vm使用此選項將隱藏使用Javavm struct進行的所有調用。--aux <name=(string|bool|int)value> - 用於傳遞應用程序時傳遞自定義參數。例如--aux='uid=(int)10'將催生用戶10的應用程序,而不是默認用戶0。筆記
請記住,Frida服務器必須在運行jnitrace之前運行。如果已遵循安裝FRIDA的默認說明,則以下命令將啟動服務器的jnitrace :
adb shell /data/local/tmp/frida-server
為jnitrace提供動力的引擎可作為一個單獨的項目提供。該項目允許您以一種熟悉使用Frida Interceptor將功能和地址附加的方法導入JNItrace來跟踪單個JNI API調用。
import { JNIInterceptor } from "jnitrace-engine" ;
JNIInterceptor . attach ( "FindClass" , {
onEnter ( args ) {
console . log ( "FindClass method called" ) ;
this . className = Memory . readCString ( args [ 1 ] ) ;
} ,
onLeave ( retval ) {
console . log ( "tLoading Class:" , this . className ) ;
console . log ( "tClass ID:" , retval . get ( ) ) ;
}
} ) ;更多信息:https://github.com/chame1eon/jnitrace-engine
從源構建jnitrace要求首先安裝node 。安裝node後,需要運行以下命令:
npm installnpm run watch npm run watch將在背景中運行frida-compile將源彙編為輸出文件, build/jnitrace.js 。默認情況下,從build/jnitrace.js加載jnitrace.py ,因此不需要其他更改即可運行更新。
像Frida-trace一樣,輸出是根據API調用線程進行著色的。
顯示屏上的線程ID正下方是JNI API方法名稱。方法名稱與jni.h標頭文件中看到的名稱完全匹配。
隨後的行包含|-指示的參數列表。 |-字符是參數類型之後,然後是參數值。對於Jmethods,Jfields和Jclasses Java類型將顯示在捲曲括號中。這取決於jnitrace看到了原始方法,字段或類查找。對於傳遞緩衝區的任何方法, jnitrace將從參數中提取緩衝區,並將其顯示為參數值以下的六十振。
返回值在列表的底部顯示為|=並且對於void方法,則不會存在。
如果啟用了回溯,則將在方法調用下方顯示FRIDA回溯。請注意,根據Frida文檔,模糊的回溯並不總是準確的,準確的回溯可能會提供有限的結果。
該項目的目的是創建一個工具,該工具可以為大多數Android應用程序有效地追踪JNI API調用。
不幸的是,在JNIENV結構中將所有功能指針附加到所有功能指針的最簡單方法是超載該應用程序。它會導致基於其他無關libart.so進行的大量功能調用數量的崩潰。
為了應對這種性能障礙, jnitrace創建了一個陰影JNienv,它可以向其想要跟踪的庫提供。 JNIENV包含一系列函數蹦床,通過一些自定義的Frida NativeCallbacks向JNI API彈跳,以跟踪這些功能的輸入和輸出。
通用的Frida API在提供一個平台上做得很好,以最少的努力來構建這些功能蹦床。但是,這種簡單的方法對所有JNIENV API都不起作用。追踪所有方法的關鍵問題是在API中使用variadic參數。不可能提前為這些功能創建NativeCallback,因為事先未知所有不同的Java方法組合。
解決方案是監視呼叫的過程,以獲取GetMethodID或GetStaticMethodID ,用於從運行時查找方法標識符。一旦jnitrace看到jmethodID查找,它就會具有已知的ID映射到方法簽名。稍後,當進行JNI Java方法調用時,使用初始的NativeCallback來提取呼叫中的方法ID。然後將該方法簽名解析以提取方法參數。一旦jnitrace在該方法中提取了參數,它就可以動態創建該方法的NativeCallback。返回了新的NativeCallback,並且一點點架構特定的ShellCode處理了設置堆棧和寄存器,以允許該調用成功運行。這些特定方法的NativeCallbacks被緩存,以使回調在多次調用的方法時更有效地運行。
一個簡單的NativeCallback不足以從方法調用提取參數的另一個地方是使用VA_ARGS指針作為最終參數。在這種情況下, jnitrace使用一些代碼從提供的指針中提取參數。同樣,這是特定於架構的。
這些功能調用中追踪的所有數據都發送到格式化並顯示給用戶的Python控制台應用程序。
該工具的大多數測試都是在運行棉花糖的Android X86_64模擬器上進行的。在另一台設備上運行的任何問題,請提交問題,但如果可能的話,建議嘗試在類似的模擬器上運行。
對於運行jnitrace的任何問題,請在Github上創建一個問題。請在提交的問題中包含以下信息: