1.安裝
pip install frida-tools
pip install frida
PS: frida安裝時需要編譯所以會卡在Running setup.py bdist_wheel for frida ...\
,需要大約20多分鐘,並且沒有相關提示,需要耐心等待
2. Hook native api
首先運行以下命令確保frida可以監控到非自己的子進程
$ sudo sysctl kernel.yama.ptrace_scope=0
基本例子
import frida
import sys
# hook邏輯腳本
jscode = """
Interceptor.attach(ptr("%s"), {
onEnter: function(args) {
send(args[0].toInt32());
}
});
"""
# 注入進程,attach傳入進程名稱(字符串)或者進程號(整數)
session = frida.attach("hello")
script = session.create_script(jscode % int(sys.argv[1], 16))
#int()函數把字符串表示的16進制數轉換成整數
#上面的jscode % int(sys.argv[1], 16)是python格式化字符串的語法
# 接收腳本信息的回調函數
# message是一個對象,type屬性爲send則表示send函數發送的信息,其內容在payload裏
# 下面這個on_message函數可以做固定用法,一般無需改動,當然也可直接打印message看看裏邊的內容
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
# 應該是設置message事件的回調函數
script.on('message', on_message)
# 加載hook腳本
script.load()
# 保持主線程不結束(也可以使用time.sleep循環)
sys.stdin.read()
hook加載的基本流程
frida.attach
attach進程創建session,使用進程名(字符串)或者進程號(整數)指定進程session.create_script
session創建hook腳本script.on
註冊用於通信的回調函數script.load
加載腳本sys.stdin.read
保持主線程運行
hook代碼(js)
1. hook函數
Interceptor.attach(ptr(''%s'), {
onEnter: function(args) {
send(args[0].toInt32());//這裏也可以直接修改args的值來改變傳入的參數
onLeave: function onLeave(retval) {
}
});
Interceptor
類用於hook操作,其attach方法有兩個參數
-
要hook的函數在內存中加載的地址
這個地址可以通過
Module.findExportByName(鏈接庫名,函數名)
來獲取鏈接庫中的導出函數地址ptr()
接收一個字符串,用於構造一個指針 -
包含回調函數的對象
onEnter
在進入該函數時調用,args
爲傳入該函數的參數;onLeave
在函數返回時被調用,retval
該函數的返回值
send
函數用於hook腳本向外部發送信息,通過在外部python腳本中用script.on
設置回調函數來接收信息,回調函數有兩個參數,第一個message
就包含send
發送的信息,第二個參數暫時沒有在官方文檔中找到用處。
詳細描述參考官方文檔Interceptor,ptr
2. 調用函數
var f = new NativeFunction(ptr("%s"), 'void', ['int']);
f(1911);
NativeFunction
構造函數接受三個參數——函數地址,返回值,參數類型(數組表示), 然後就可以調用該函數
詳細描述參考官方文檔NativeFunction
3. 分配空間
var st = Memory.allocUtf8String("TESTMEPLZ!");
Memory對象下的方法可以操作被attach的進程的內存空間 ,該方法分配的空間似乎會在函數結束後被銷燬,比如在onEnter中分配的內存空間在onEnter結束後會被釋放,使用時需注意。
詳細描述參考官方文檔Memory
4. hook printf函數的一個測試
-
編寫hook腳本
var write_address = Module.findExportByName('libc.so.6', 'write'); //js字符串中的換行符必須要寫成\\n //給我們注入的字符串分配空間 var st = Memory.allocUtf8String('你的write函數已經被劫持了\\n'); if(write_address != null){ send('write is at' + write_address) Interceptor.attach(write_address, { onEnter: function(args) { //輸出write的原始參數write(int fd, char* buff, int size) send(args[0].toString()+ ' ' + args[1].toString() + ' ' + args[2].toString()); //把第二個參數替換成我們的字符串 args[1] = st; //第三個參數改爲我們字符串的長度,utf8一個漢字爲3個字節,我們的字符串共計36字節 args[2] = ptr('36'); }, onLeave: function(retval) { } }); }else{ send('no such function'); }
-
運行(加載js腳本的過程同上文所述)
python通過write
輸出的所有值都變成我們的字符串,下邊是python在write
stdout(0x1
)和stderr(0x2)
3. 在Android系統中運行
-
下載frida-server,一般爲frida-server-12.3.5-android-arm64.xz ,根據Android系統的架構來選擇(arm,arm64,x86,x86_64)
-
用電腦連接Android手機,把frida-server放到手機裏,再通過adb啓動手機shell,運行frida-server(需要root權限)
$ adb root $ adb push frida-server /data/local/tmp/ $ adb shell "chmod 755 /data/local/tmp/frida-server" $ adb shell "/data/local/tmp/frida-server &"
-
將hook腳本中的
frida.attach()
替換成frida.get_usb_devicce().attach()
就可以hook手機上的進程了,可以hook java方法,也可用hook Android系統程序的native api
4.hook java方法
hook代碼加載流程同上文hook native api相同,只是用於hook邏輯的js代碼不同
Java.perform(function () {
// Function to hook is defined here
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
// hook MainActivity類的onClick方法
// java方法的hook採用了替換的方式,提供了調用原方法的途徑
MainActivity.onClick.implementation = function (v) {
//進入方法時的操作(操作參數)
send('onClick');
// 調用原方法
this.onClick(v);
// 退出原方法時的操作(操作返回值等)
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));
};
});
-
Java.perform()
應該是對hook java層進行包裝,其參數爲一個函數,該函數爲具體hook邏輯 -
Java.use()
參數爲需要hook的類名 -
classname.method.implementation
需要被賦值爲一個函數,該函數會替換method(不同於hook本地api時有onEnter
和onLeave
),在函數內使用this
可以訪問該類,調用其方法和屬性。調用屬性時使用this.property.value
, 而不是this.property
,遇到有屬性和方法同名時屬性前加下劃線_
,如this._property.value
-
hook有重載的方法時,使用
overload
指出參數具體類型例如
classname.method.overload('type1','type2').implementation
-
構造方法使用
$init
表示,例如classname.$init.overload('type1','type2').implementation
ps: 調用某些原方法可能無法正常工作,據說是系統原因,只有更換系統版本