本文當做複習 frida 的使用,並且給出一些例題,封面圖 via AI
frida 的常見命令速查#
frida-ls-devices # 列出所有設備
frida-ps -U # 列出usb設備的進程
frida-ps -Ua # 列出正在跑的應用
frida-ps -Uai # 列出所有安裝的應用
frida-ps -D xxxxx # 連接指定的設備,設備id可以從第一條命令獲得
# 啟動應用並注入腳本 後面會提到這兩者的區別
frida -U -l exploit.js -f com.xxx.yyy
# 應用已經在運行,注入腳本
frida -U -l exploit.js "XXXX"
frida 基礎#
frida
是一款基於 python+javascript 的 hook 框架,可運行在 android、ios、linux、win 等各個平台,主要使用的動態二進制插桩技術。
pip 命令安裝 frida
和 frida-tools
:
pip install frida
pip install frida-tools
查看 python 安裝的 frida 版本
frida --version
之後在 PC 端和手機端分別安裝 frida 和 frida-server,並且版本要一致。
之後在手機端運行 frida-server,並將手機端的端口轉發到 PC 端,以便 PC 端的 python 腳本和手機端的 frida-server 通信。可以使用 adb 命令來實現端口轉發,這一步似乎並不是必須的,不轉發好像也可以。例如:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
可以使用 frida-ps -U 命令來查看手機上的進程信息,以便選擇要 hook 的目標進程.
簡單概括下:
- 下載好對應的 frida server,這個在官方的 GitHub release 頁面去找,注意如果用電腦模擬器(如夜神模擬器則對應架構位 x86 而不是 arm)
- adb 連接模擬器,push 第一步下載好的 server 到目錄 /data/local/tmp (一般是這個目錄),chmod a+x 後啟動
- pip install frida frida-tools
- 輸入 frida-ps -U 可以看到手機的進程列表,此時就配置成功了
編寫、運行 hook 腳本#
啟動並注入一個 hook 腳本
frida -U -l exploit.js -f com.xxx.yyy
一些簡單的編寫用法,如果熟悉了 java 反射會很好理解
如果要使用一個 string,因為java.lang.String
是一個類,所以我們需要 new 出來(其餘的類如果想要用到類方法同理,如果是靜態方法則不需)
var x = Java.use("java.lang.String").$new("xxxxxxxx")
如果要用到某個類的某個靜態方法(類方法則需要先 new 對象)
Java.use("com.xxx.yyy").method(param1, param2)
如果要 hook 某個方法
Java.perform(function() {
var class = Java.use("com.xxx.yyy")
classs.方法名.implementation = function(param1, param2){
send(); // 打印日誌
return this.xxxx // hook 返回值
}
})
基礎的用法可以說就這麼多
踩坑 && 技巧#
jadx 支持直接複製 frida 代碼片段,可以省去不少功夫
hook 方法需要指定參數:
// JS 代碼
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // 獲取類引用
var a = MainActivity.a.overload("int", "int", "long"); // 指定要 hook 的重載方法的參數類型
a.implementation = function (x, y, z) { // 替換重載方法的實現
console.log("x: " + x + ", y: " + y + ", z: " + z); // 打印參數
var result = this.a(x, y, z); // 調用原始的重載方法
console.log("result: " + result); // 打印返回值
return result; // 返回原始的值
};
});
如果有些函數沒有參數,則 overload
不需要指定
// JS 代碼
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // 獲取類引用
var onCreate = MainActivity.onCreate.overload(); // 指定要 hook 的函數的重載類型,不傳入任何參數
onCreate.implementation = function () { // 替換函數的實現
console.log("onCreate() is called"); // 打印日誌
this.onCreate(); // 調用原始的函數
};
});
示例#
hook MainActivity 方法示例#
參照官方文檔的示例,apk 也可以下載:Android | Frida • A world-class dynamic instrumentation toolkit
jadx 分析下邏輯,可以看到要求 this.n
和 this.m
要相差為 1,並且 this.cnt+1
後等於 1000
那麼什麼時候會修改這幾個變量呢?往下看到了 onClick
方法
那麼就可以編寫 js 腳本,hook onClick
方法
Java.perform (() => {
// 選擇類
const MainActivity = Java.use ('com.example.seccon2015.rock_paper_scissors.MainActivity');
// 選擇onClick方法
const onClick = MainActivity.onClick;
// hook onClick
onClick.implementation = function (v) {
// 函數被調用的時候打印一條消息
send ('onClick');
// 調用原始的onClick方法
onClick.call (this, v);
// 調用完之後修改m,n,cnt的值
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// frida控制台打印結果
// hook結束
console.log ('Done:' JSON.stringify (this.cnt));
};
});
運行命令
frida -U -l .\seccon2015.js rock_paper_scissors
執行後會進入 frida 的 shell,日誌消息會打印出來
frida 也提供了 python 方式調用,不過有個坑點在代碼註釋中,個人更喜歡用 js 的方式
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
...複製上面的jscode
"""
# 注意這裡有一個坑點,attach的時候不用輸入全包名
process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
找了下原因:
import frida
process = frida.get_usb_device().enumerate_processes() # 枚舉所有的process
此時發現實際的 process 名字,所以代碼中直接寫 rock_paper_scissors
而不是完整的包名
Process(pid=2982, name="rock_paper_scissors", parameters={})
運行效果:
此時應用上就打印出了 flag
吾愛破解紅包第三題#
jadx 分析下邏輯,只要 check
函數返回的結果等於 999 即可獲得 flag
因此直接 hook MainActivity
下的 check 方法即可,返回 999
function main(){
Java.perform(function(){
var activity = Java.use("com.zj.wuaipojie2023_3.MainActivity");
var check_method = activity.check.overload(); // 這裡實際沒有重載,因此不需要
check_method.implementation = function(arg){
// send('check');
check_method.call(this);
return 999
}
})
}
setImmediate(main)
點幾下就可以獲得 flag 了
hook rsa 加密#
一次比賽中,有一個關卡是破解 apk,(源文件遺失)jadx 獲得源碼發現是一個 RSA 加密,這裡有個坑搞了半天
安卓的 RSA 和 java 包中的實現不一樣,需要手動下載一個 BouncyCastleProvider.jar 包,引入項目後修改代碼即可運行
其實最簡單的方案就是用 frida 進行 hook
實現的代碼如下:
function main(){
Java.perform(function(){
var enc = Java.use("java.lang.String").$new("F3RSCi59ewpMm+dIGtiAn752tBQ0TPx7tCayrCXrrwyG2gtlUJ3C82rcrfgg2A4X7XfhXWGidsJgGDzbD9JwvzGbKMHTK5kxJUf1EJ605R/Rv2N+6Jtr54wQAKbqGyU8g/rUhLUf/EgNqPPYTQ6fa4LqFEMTrPm/UZMvL/4yTNhFoM5vQbceQeD9QNATh6kdYEzLfRdWbv/ESllURlgZbQsc7+pHC8Sg9wVh/2MGZHKzZI8a67Usqhk8ojmfgN5ABodQ2z1DOCICg7AH+l+3fx6eOn1dup/Q1hpCHVCgtojMOPfoLcxICY5LkSonrFjAKzv4JHO/f5ihFXX8Dr4MFw=="); // new一個string
var PrivateKey = Java.use("java.lang.String").$new("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcscawF6Fy4Smk\nqwOy2W/IcewNTYzHe9ZFV6w1dqrZ+sRBRD6Sapv7k4deLF9ziYsWXfcDNuvG2HN6\nAgk6CeN5QHUj7OiJmTnitHAQFIw7Ed6O3uyE64i3dhZIKh7oAies6EMpfKusXIZ6\nt1lFwVVpPjlrW6435j/nW5X0OeaRRk89zOxIbzAhS0yxArkMSecy9lr+UwzWQhFO\nW4+O/ywesds9VdCuycx2S/1zbG7BwJP507nTxstLkUnet0BHqXH3tH8cRXXDyFo1\n13gBgrBZ7Y79Z7T39B+21qq7RxfwvPDqu6Rq3p9cOUQCwfJSWlHYBVtYOME04vZP\nkZj/PqLhAgMBAAECggEBAL9QLRkBoydfISZqSW6FJ23vs91kWKGlLH44HkTKdhk4\nVWDus/9hA472uOZeOPCDT1s4YaUbuxBJGvGtSipR4CuAQQu/l2WQuqlYGb0W0ulx\n+CiJ/ybKt12ytGYifWBKXfJ59Z7FQFzOaJWA05awnkX5dvUmKMS0iLkan8dzWfic\nCn379Jn/bLmRXSTKgWNMi9toT4kNhnr2/cf1ah1i2MHHZb4ZMdC9rqnSvyzFRAFM\nuess0Qg8ckyoNzx2daP9vXapn8rg6B/MFBZqjorwyv0KJms/S6W/YKw88OixM/J3\nxlBsk8QHJqaL5IZW6/QeIHJk4K6o9+3fTxtHEjcWoAECgYEA8ASApzgATrHUFVdT\nZ6ZohLFMyQQZzaKLtZhBRfenASrWQSpHHcGdF15C45fik/vohZvF/byrHLkwvosp\nvC6BHyF7toJDrTEWBo/xymGNYmovr0keMUc3T4aRomaERz0gY8CLhKpHMKJrVtgj\nkHlAGqkRZB6AAlcEQUSOyvYziyECgYEA62Pg4xMw1BfJKSfLhCoaWHeWBMAbuZ8K\ncEOLIO8CtGKwXRLBe6MUKTDnHt7aXawVd/j6jk27uhD8p1noYqbsAexzMIKAAzic\nI6fsCVgUfvRZzhaQ98xyBO34AmAtzhJQEg65TnybyEf8tC5xPSegCzzYA5w8QfaM\necRq8V8R38ECgYEAkRNXpDuATBDGzaN8AXGfSV1VuUcmRZpTcg82nREFO/pliPwC\nAmVuC5rpOWIsDSC9ukyezzEECJeTdAjv0BQX7fYyLe3s9DlwQ8Ur9BUk/XCLpBrB\nCi4uq33+dzgaCmlTM5hFfInj/+ycjuCUFpaDfdnlbivVWhS4uK601M8d9qECgYEA\nuxel1ZaZRtqo/tcsgZ3dVtemG5x97OmmKFjnKoQOiEWwnCmeM6EJEzxVV0oWEkIG\nxlbU/2p1fYym5HUnqdG24EiJvdLb4LOMFyMPDtY9ZDLfdlilXO/Y6GYkq+66OYKA\ntfmR+/o679OX3bbUNqFaKdRwdV4m3t8SBG7D+Zlw4YECgYAp2B4F1WOu5H/P0yC6\n6Hn/FGcQec8ty6MCPlH1km9dQBS3jMvBAFpmnMv+hWiR4W93tOHRqRndR6VFC/dX\n+io76NklgjQGteUJ/Nawhg0sDpWCUGECRypwbNTgmhcrmu/RF1B22rO8sckPXXeB\nDzVmjKFXgpDDIJ0eDUTQbqEEMw=="); // new一個string
var encB64Decode = Java.use("com.chaos.view.example.Base64Utils").decode(enc); // 調用Base64Utils的decode方法
var loadPrivateKey = Java.use("com.chaos.view.example.RSAUtils").loadPrivateKey(PrivateKey); // 調用RSAUtils的loadPrivateKey方法
var res = Java.use("java.lang.String").$new(Java.use("com.chaos.view.example.RSAUtils").decryptDataPrivate(encB64Decode, loadPrivateKey)); // 調用RSAUtils的decryptDataPrivate解密方法
var qaq = Java.use("com.chaos.view.example.Qaq").decode(res, 127);
console.log(qaq);
})
}
setImmediate(main)
調用 frida -U -l xxx.js
owasp.mstg.uncrackable1 繞開 root 檢測#
apk 有 root 檢測,jadx 查看邏輯發現判斷是寫在 a.a
函數的
跟進下 a.a方法
,簡單點就直接 hook 返回結果為 true 好了
首先繞開 root 檢測,找一個大神公開的腳本:
然後試一下就成功繞開了 root
frida -U --codeshare dzonerzy/fridantiroot -f "owasp.mstg.uncrackable1"
接下來就是 hook 下
Java.perform(() => {
// 找到a類
const a = Java.use('sg.vantagepoint.uncrackable1.a');
// 找到a方法,雖然沒有重載但還是指定下參數類型
const a_method = a.a.overload("java.lang.String");
a_method.implementation = function (v) {
// 打印一條消息表示hook了
send('a method call');
// call原方法
a_method.call(this, v);
return true; // hook 原函數的返回值
};
});
接下來在通過 -l
去運行自己的腳本,--codeshare
是運行的繞開 root 檢測到腳本
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable1.js -f "owasp.mstg.uncrackable1"
owasp.mstg.uncrackable2#
這裡的邏輯類似第一個,但是方法是位於 native lib 中的,因此需要去 hook libfoo.so
中的函數
hook native 方法的模版如下:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'xxxx'),{
onEnter: function(args){
// 這裡修改函數進入的時的參數
},
onLeave: function(retval){
// 這裡修改函數的返回值
}
});
},2000);
});
參考模版代碼,可以寫出如下 hook 腳本:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'Java_sg_vantagepoint_uncrackable2_CodeCheck_bar'),{
onEnter: function(args){
},
onLeave: function(retval){
console.log(retval);
retval.replace(ptr(1));
console.log(retval);
}
});
},2000);
});
注意這裡的坑:
這裡要區分下 frida hook 的兩種方式
- 注入
frida -U -l .\uncrackable2.js "Uncrackable Level 2" # 注入對應的進程
- 啟動
frida -U -l .\uncrackable2.js -f owasp.mstg.uncrackable2 # 啟動對應的進程
我最開始用的是 -f
導致程序一開始其實沒有加載到 libsfoo.so
為這個問題找了半天解決辦法,甚至一度以為夜神模擬器就是不支持
最後整理出的兩種均可用的代碼,只要加上 setTimeout
就行了
注意第一次運行失敗,第二次才會成功
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable2.js -f owasp.mstg.uncrackable2
參考 https://1337.dcodx.com/mobile-security/owasp-mstg-crackme-2-writeup-android 這裡給出了去 hook native 中的 strncmp
方法的代碼
Java.perform(() => {
console.log("[*] Hijacking the onClick button")
var clazz_main = Java.use('sg.vantagepoint.uncrackable2.MainActivity$1')
clazz_main.onClick.implementation = function () {
console.log('onCLick() is replaced ');
};
console.log()
console.log('[*] ACTION NEEDED: Insert the string "I want your secret asap" as input')
console.log()
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'strncmp'),{
onEnter: function(args){
if( Memory.readUtf8String(args[1]).length == 23 && Memory.readUtf8String(args[0]).includes("I want your secret asap")){
console.log()
console.log()
console.log("*******SECRET********")
console.log(Memory.readUtf8String(args[1]))
console.log("*******SECRET********")
console.log()
console.log()
}
},
onLeave: function(retval){
}
});
},2000);
});
運行即可
frida -U -l .\uncrackable2.js "Uncrackable Level 2"
owasp.mstg.uncrackable3#
後面再來補充吧~