1,frida-server
下面是frida官網給出的Android環境下hook的使用demo:
其中中間綠色部分是js代碼,上下部分是python語言;
python語言部分只是單純的為了將js代碼發送到設備而已,核心功能還是在js部分實現;
1,js語言是弱語言,不對變量類型做強檢查,所以我們可以都用var
表示;
2,java中的類都用java.use
獲取;
3,js代碼function大括號內部是用於hook的主要代碼,其余部分基本不變
import frida, sys def on_message(message, data): if message['type'] == 'send': print("[*] {0}".format(message['payload'])) else: print(message) jscode = """ Java.perform(function () { // Function to hook is defined here var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); // Whenever button is clicked var onClick = MainActivity.onClick; onClick.implementation = function (v) { // Show a message to know that the function got called send('onClick'); // Call the original onClick handler onClick.call(this, v); // Set our values after running the original onClick handler this.m.value = 0; this.n.value = 1; this.cnt.value = 999; // Log to the console that it's done, and we should have the flag! console.log('Done:' + JSON.stringify(this.cnt)); }; }); """ process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors') script = process.create_script(jscode) script.on('message', on_message) print('[*] Running CTF') script.load() sys.stdin.read()
下面以姜維在CSDN上給出的幾個例子來講解記錄一下這部分js代碼的用法:
java層hook:
1,hook構造方法
原方法:

hook代碼:

首先腳本中使用Java.use方法通過類名獲取類類型,然后構造方法是固定寫法:
$init
;這個要記住,然后因為需要重載所以用overload(……)形式即可,參數和參數之間用逗號隔開即可,這里用
return
重新調用了原來方法。
2,hook普通方法(static、private、public等)
原方法:

hook代碼:

這里的用法和構造方法基本一致,就是把
$init
換成了方法名而已;
3,修改參數和返回值
參數和返回值的修改在上面內容很容易看出來,就是在方法攔截后通過arguments參數去獲取傳入參數,然后修改,返回值的話直接修改return函數就好了,這里重點講的是自定義類型的參數:

構造一個新對象的方法有很多,這里選擇最簡單的
$new
即可,上面那行注釋也是可以用的,這里的返回值直接修改成了我們構造出來的這個對象coinObj,但是如果要修改一個對象的內部值的話,直接用對象名加參數是不行的,那么就需要用反射了:

其中
java.cast(Object.getClass(),).getDeclaredField("")
的固定用法記住即可;
4,打印方法的堆棧信息
可以用來查看方法的調用鏈關系,這里提供兩種思路,一種是自己編寫一個打印堆棧的方法,弄成dex注入到程序中,需要打印的時候直接調用;第二種就是簡單粗暴的直接在打印的地方構造一個異常對象然后拋出,但是這種方法會因為異常直接導致程序崩潰,不過無所謂了,我們可以看到調用關系就好了;
Native層hook:
1,hook未導出的函數

首先計算出目標函數實際地址=函數偏移+so基址+1,+1是因為要標識arm和thumb指令區別;
然后通過實際地址構造NativePointer
對象;
通過攔截器模式attach到目標開始hook,這里的onEnter和onLeave與xposed中的before和after很像,其實功能也一樣,都是在函數開始前和結束后;
var nativePointer = new NativePointer(實際地址)
這里讀取到的參數其實一個指針,也就是一個地址,想要獲取內容需要用到Memory.readCString(地址)
來打印參數;讀取返回值的時候,因為C語言中是多數情況是通過參數來返回函數結果的,我們就把參數地址里的內容打印出來,如果有多個參數就全打印出來再分析,這里是將返回結果的字節數組按字節打印,然后拼接在一起在打印出來的;
2,hook導出的函數

這里省去了手動計算地址的過程,只需要傳入so名字和函數名字即可,如果是c++的話則需要注意,因為C++支持重載,所以導出函數名會不同;
這里通過函數名可以知道就是一個native函數了,那么他第一個參數肯定是JNIEnv指針,第二個參數是jclass類型,這個是標准的如果是靜態方法第二個參數沒啥用,后面的參數就是真的傳遞到native層的值了,比如這里Java層的方法:

放到native層就變成4個參數了:

只有后面兩個才是我們需要的,然后查看hook返回結果:

這里可以看到傳入參數都返回了,但是函數return值是空的,這是因為這里的返回值是一個jString類型,看過jni.h文件的同學應該知道,jni里有一套自己的數據類型定義,雖然格式都和java一致,但是他屬於自定義類型;

這里new一個NativeFunction的用法可以查看api:

至於獲取jString的方法官網也有說明,很簡單:
var env =Java.vm.getEnv(); var jstring = env.newStringUtf("HelloWorld");

2,frida-Gadget
frida-gadget作為一個動態庫文件無法直接在linux/Android環境下運行,需要用過將apk反編譯成smali代碼,然后通過System.loadlibrary("frida-Gadget");
的形式將so動態庫運行起來,此時Frida-server就是以應用的權限運行起來的,在應用的沙箱中是擁有全部權限的,包括攔截、插樁等操作;
1,程序首先要打開可調試狀態開關,
比如AndroidManifest文件中的Application中增加android:debuggable="true"
標簽
2,將frida-gadget.so放到目標apk的lib文件夾下,將so的名字改成lib***.so
的格式

3,植入點通常放在Application的入口處,因為盡可能早的hook住程序就可以避免漏過方法執行;
在smali代碼下表現為Application中的構造函數處,.methods static constructor <clinit>()V
static{ // //loadlibary里 要把SO文件名的lib和后綴去掉。libfgma.so --> fgma System.loadLibrary("fgma"); }


如果一個應用有多個進程的話則不必將so的植入點放到最初application的構造函數中,可以選擇將植入點選在新進程開啟后的合適的地點,最好還是構造函數初始化過程,但不限於最早組件,可選擇適當時機啟動的組件,如下,並在加載后使進程休眠20s的時間,用來等待粘貼js代碼;

4,程序運行到so啟動時會卡住,此時是在等待客戶端像服務端發送建立連接的命令
此時在pc端命令行輸入adb shell netstat -ls
查看網絡端口會發現有27042的消息端口;
如果在pc端命令行輸入frida -U Gadget
命令即可與之建立連接,此過程相當於frida-server模式下的python注入過程,在此建立連接之后我們可以直接輸入js代碼去執行目的代碼,
例如:
//打印目的內存值 var tar = new NativePointer(0x94b778d9);send(hexdump(this.tar));
//獲取目的函數模塊 var nativePointer = Module.findExportByName(null,'SeedDecryptAndIDEncrypt'); //打印目的函數地址 send("smm native pointers:" + nativePointer); //連接目的函數進程 Interceptor.attach(nativePointer,{ onEnter: function(args){ //打印目標函數傳入參數值 send("sDIDE args: args[0]:" + args[0] + ", args[1]:" + args[1] + ", args[2]:" + args[2] + ", args[3]:" + args[3]); //打印目的函數參數作為地址指向的內存的數值 send("smm sDIDE Memory args: args[0]:" + Memory.readInt(args[0]).toString() + ", args[2]:" + Memory.readInt(args[2]).toString() + ", args[3]:" + Memory.readInt(args[3]).toString()); }, onLeave: function(retval){ //打印目標函數的返回值 send("smm SeedDecryptAndIDEncrrypt retval:" + retval.toString()); } });
注意此處的方法名使用單引號,findExportByName()
函數的第一個參數可以用so名字或者為空;
以上就是frida的主要用法,其實frida的功能還有很多,而且不限於Android平台,總之,大佬們的總結是個好東西,官網api也是個好東西。
作者:M_天河
鏈接:https://www.jianshu.com/p/51e6aef175a2
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。