banner
raye~

Raye's Journey

且趁闲身未老,尽放我、些子疏狂。
medium
tg_channel
twitter
github
email
nintendo switch
playstation
steam_profiles

frida hook工具使用

375791

本文當做複習 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 命令安裝 fridafrida-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 的目標進程.

簡單概括下:

  1. 下載好對應的 frida server,這個在官方的 GitHub release 頁面去找,注意如果用電腦模擬器(如夜神模擬器則對應架構位 x86 而不是 arm)
  2. adb 連接模擬器,push 第一步下載好的 server 到目錄 /data/local/tmp (一般是這個目錄),chmod a+x 後啟動
  3. pip install frida frida-tools
  4. 輸入 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 代碼片段,可以省去不少功夫

Untitled

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.nthis.m 要相差為 1,並且 this.cnt+1 後等於 1000

Untitled 1

那麼什麼時候會修改這幾個變量呢?往下看到了 onClick 方法

Untitled 2

那麼就可以編寫 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,日誌消息會打印出來

Untitled 3

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={})

運行效果:

Untitled 4

此時應用上就打印出了 flag

Untitled 5

吾愛破解紅包第三題#

jadx 分析下邏輯,只要 check 函數返回的結果等於 999 即可獲得 flag

Untitled 6
因此直接 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 了

Untitled 7

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 函數的

Untitled 8

Untitled 9

跟進下 a.a方法 ,簡單點就直接 hook 返回結果為 true 好了

Untitled 10

首先繞開 root 檢測,找一個大神公開的腳本:

Frida CodeShare

然後試一下就成功繞開了 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"

Untitled 11

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

Untitled 12

參考 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#

後面再來補充吧~

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。