博客 / 詳情

返回

【APP 逆向百例】某當勞 Frida 檢測

7WQWGU.png

聲明

本文章中所有內容僅供學習交流使用,不用於其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據接口等均已做脱敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關!

本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯繫作者立即刪除!

逆向目標

  • 目標:某當勞 APP
  • apk 版本:7.0.15.1
  • 下載地址:aHR0cHM6Ly93d3cuZG93bmt1YWkuY29tL2FuZHJvaWQvMTU0OTg1Lmh0bWw=

逆向分析

直接注入 frida 代碼,frida 命令如下:

frida -U -f com.mcdonalds.gma.cn -l .\3.js

結果如下:

EPv78J.png

發現閃退,老樣子,按照之前的思路,我們可以先 hook dlopen 方法,監控動態庫的加載情況。

dlopen 原型函數:

void *dlopen(const char *filename, int flag);
參數 説明
filename so 文件的路徑,例如 "libfoo.so" 或完整路徑 /data/app/.../libfoo.so。傳 NULL 表示獲取主程序自身句柄。
flag 加載選項,常見值:• RTLD_LAZY:按需解析符號(調用時綁定)• RTLD_NOW:立即解析所有未定義符號 • RTLD_GLOBAL:符號導出,可被後續庫使用 • RTLD_LOCAL:符號僅在本庫內可見(默認)

hook android_dlopen_ext 代碼如下:

function hook_dlopen() {
    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    Interceptor.attach(android_dlopen_ext, {
        onEnter: function (args) {
            var path_ptr = args[0];
            var path = ptr(path_ptr).readCString();
            console.log("[android_dlopen_ext -> enter", path);
            if (args[0].readCString() != null && args[0].readCString().indexOf("libmsaoaidsec.so") >= 0) {
                // hook_call_constructors()
                hook_pth()
            }
        },
        onLeave: function (retval) {
            console.log("android_dlopen_ext -> leave")

        }
    });
}
hook_dlopen()

EPvEtG.png

可以看到,程序雖然在我們的 libmsaoaidsec.so 退出了,但是在這之前加載了一個 libDexHelper.so 文件,通過 MT 管理器查看可知,是某梆加固,而某梆加固都是 libDexHelper.so 進行 frida 檢測的,所以我們應該先分析這個文件:

EPvQoB.png

so 文件分析

知道在這個 so 文件加密之後,我們直接 hook pthread_create 看看創建了哪些線程,hook 代碼如下:

function hook_pthread_create(){
    var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
    console.log("pthread_create_addr: ", pthread_create_addr);
    Interceptor.attach(pthread_create_addr,{
        onEnter:function(args){
            console.log(args[2], Process.findModuleByAddress(args[2]).name);
        },onLeave:function(retval){
        }
    });
}
hook_pthread_create();

EPvqyt.png

發現並沒有 libDexHelper.so 相關的線程,這是什麼原因呢?遇事不決問 ai,下面是 GPT 給出的部分答案:

EPvuUb.png

EPvwse.png

GPT 給出了重要結論,pthread_create 最終會調用 clone 方法。

clone 是 Linux/Android 系統的一個 底層系統調用,用於創建 線程或進程,它比 fork 更靈活,是 pthread_create 的底層實現基礎。

clone 函數原型如下:

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, 
          ... /* pid_t *ptid, void *tls, pid_t *ctid */);
參數 説明
fn 子線程/進程起始函數
child_stack 子線程棧頂地址
flags 控制資源共享與行為
arg 傳給 fn 的參數
ptid 父線程寫入子線程 PID 的地址
tls 子線程 TLS 基址
ctid 子線程寫入自己 PID 的地址

那我們直接調用 clone 函數試看看,hook clone 代碼如下:

var clone = Module.findExportByName(null, 'clone');

Interceptor.attach(clone, {
    onEnter: function(args) {
        // 獲取線程函數地址
        var func = args[0];
        // 獲取線程函數所在模塊
        var module = Process.findModuleByAddress(func);
        if (module) {
            console.log("Thread function is located in module: " + module.name);
        }

        // 打印調用棧
        console.log("Backtrace:");
        console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress)
            .join('\n'));
    },
    onLeave: function(retval) {
        // 可在這裏打印返回值或做後續處理
    }
});

EPvHEP.png

發現多次調用同一個線程創建,通過下面命令,把 lib.so 文件拷貝到電腦:

adb pull /system/lib64/libc.so ./libc64.so 

ida 分析

我們用 ida 打開 libc.so 文件,搜索 pthread_create 查看它的地址:

EPvKcw.png

我們主要關注上面 a3 的值,按住 tab 鍵找到 pthread_create 的地址:

EPvbj6.png

0x7278e88aa8 libc.so!pthread_create+0x290

根據上面的輸出加上偏移得到 clone 函數的最終地址為 0xAFAA8:

0x00000000000AF818 + 0x290 = 0x00000000000AFAA8

按 g 跳轉到該地址,如下所示:

EPvGtf.png

clone(__pthread_start, v19, 4001536, v31, v31 + 16, v23 + 8, v31 + 16);
參數 説明
__pthread_start 線程函數入口(pthread 內部包裝 start_routine
v19 新線程棧頂地址
4001536 clone flags(如 CLONE_VM)
v31 入口函數參數(通常封裝了 start_routine + arg)
v31 + 16 父線程寫入子線程 PID 的地址(ptid)
v23 + 8 新線程 TLS 基址
v31 + 16 子線程寫自己 PID 的地址(ctid)

在 pthread 內部,線程函數會存儲在線程控制塊中:

*(_QWORD *)(v31 + 96) = a3;  // 將用户線程函數寫入線程控制塊

通過讀取 v31 + 96 的地址,我們可以獲取實際執行的線程函數,hook 代碼如下:

var clone = Module.findExportByName('libc.so', 'clone');
Interceptor.attach(clone, {
    onEnter: function(args) {
        // 只有當 args[3] 不為 NULL 時,才説明上層確實把 “線程控制塊指針” 傳進來了
        if(args[3] != 0){
            // 真正的用户線程函數地址
            var addr = args[3].add(96).readPointer()
            // 根據線程函數地址 addr,找它屬於哪個模塊
            var so_name = Process.findModuleByAddress(addr).name;
            // 獲取該 so 在進程裏的基址
            var so_base = Module.getBaseAddress(so_name);
            // 獲取相對於 so_base 的偏移
            var offset = (addr - so_base);
            console.log("===============>", so_name, addr,offset, offset.toString(16));
        }
    },
    onLeave: function(retval) {
 
    }
});

結果如下:

EPvxoc.png

可以看到成功輸出了該 so 文件的線程函數,接下來,我們嘗試着先 nop 掉這幾個函數,看能否過檢測,hook 代碼如下:

function nopFunc(parg2) {
    Memory.protect(parg2, 4, 'rwx');  // 修改該地址的權限為可讀可寫
    var writer = new Arm64Writer(parg2);
    writer.putRet();   // 直接跳到 ret 返回地方 ,不反回值
    writer.flush();   // 寫入操作刷新到目標內存,使得寫入的指令生效。  從緩存中寫道內存
    writer.dispose();  // 釋放 Arm64Writer 使用的資源。
    console.log("nop " + parg2 + " success");
}

function hook_dlopen(so_name) {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                console.log("[android_dlopen_ext -> enter", path);
                if (path.indexOf(so_name) !== -1) {
                    this.match = true
                }
            }
        },
        onLeave: function (retval) {
            if (this.match) {
                console.log(so_name, "加載成功");
                var base = Module.findBaseAddress("libDexHelper.so")
                nopFunc(base.add(308204));
                nopFunc(base.add(362896));
                nopFunc(base.add(332536));
                nopFunc(base.add(366304));
                nopFunc(base.add(385348));
            }
            console.log("android_dlopen_ext -> leave")
        }
    });
}
hook_dlopen("libDexHelper.so")

結果如下:

EPvAH3.png

發現我們 nop 掉線程後,並沒有報錯,那證明我們 nop 掉沒有問題,但是卡在了最開始的 libmsaoaidsec.so 文件裏,對於這個文件,我們同樣直接 nop 掉裏面的線程即可:

function hook_pth() {
    var pth_create = Module.findExportByName("libc.so", "pthread_create");
    console.log("[pth_create]", pth_create);
    Interceptor.attach(pth_create, {
        onEnter: function (args) {
            var module = Process.findModuleByAddress(args[2]);
            if (module != null) {
                console.log("開啓線程-->", module.name, args[2].sub(module.base));
                if (module.name.indexOf("libmsaoaidsec.so") != -1) {
                    Interceptor.replace(module.base.add(0x175f8), new NativeCallback(function () {
                        console.log("替換成功")
                    }, "void", ["void"]))
                    Interceptor.replace(module.base.add(0x16d30), new NativeCallback(function () {
                        console.log("替換成功")
                    }, "void", ["void"]))
                }
            }

        },
        onLeave: function (retval) {
        }
    });
}

最終也是成功繞過了檢測,結果如下:

EPvSL9.png

至此,該 app 的 frida 檢測分析流程就結束了。

相關 hook 腳本,會分享到知識星球當中,需要的小夥伴自取,僅供學習交流。

user avatar erma0 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.