首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【APP 逆向百例】某当劳 Frida 检测

【APP 逆向百例】某当劳 Frida 检测

原创
作者头像
K哥爬虫
发布2025-09-08 17:42:00
发布2025-09-08 17:42:00
7100
代码可运行
举报
文章被收录于专栏:Python 爬虫Python 爬虫
运行总次数:0
代码可运行
7WQWGU.png
7WQWGU.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

  • 目标:某当劳 APP
  • apk 版本:7.0.15.1
  • 下载地址:aHR0cHM6Ly93d3cuZG93bmt1YWkuY29tL2FuZHJvaWQvMTU0OTg1Lmh0bWw=

逆向分析

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

代码语言:shell
复制
frida -U -f com.mcdonalds.gma.cn -l .\3.js

结果如下:

EPv78J.png
EPv78J.png

发现闪退,老样子,按照之前的思路,我们可以先 hook dlopen 方法,监控动态库的加载情况。

dlopen 原型函数:

代码语言:c
代码运行次数:0
运行
复制
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 代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvEtG.png

可以看到,程序虽然在我们的 libmsaoaidsec.so 退出了,但是在这之前加载了一个 libDexHelper.so 文件,通过 MT 管理器查看可知,是某梆加固,而某梆加固都是 libDexHelper.so 进行 frida 检测的,所以我们应该先分析这个文件:

EPvQoB.png
EPvQoB.png

so 文件分析

知道在这个 so 文件加密之后,我们直接 hook pthread_create 看看创建了哪些线程,hook 代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvqyt.png

发现并没有 libDexHelper.so 相关的线程,这是什么原因呢?遇事不决问 ai,下面是 GPT 给出的部分答案:

EPvuUb.png
EPvuUb.png
EPvwse.png
EPvwse.png

GPT 给出了重要结论,pthread_create 最终会调用 clone 方法。

clone 是 Linux/Android 系统的一个 底层系统调用,用于创建 线程或进程,它比 fork 更灵活,是 pthread_create 的底层实现基础。

clone 函数原型如下:

代码语言:c
代码运行次数:0
运行
复制
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 代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvHEP.png

发现多次调用同一个线程创建,通过下面命令,把 lib.so 文件拷贝到电脑:

代码语言:bash
复制
adb pull /system/lib64/libc.so ./libc64.so 

ida 分析

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

EPvKcw.png
EPvKcw.png

我们主要关注上面 a3 的值,按住 tab 键找到 pthread_create 的地址:

EPvbj6.png
EPvbj6.png
代码语言:bash
复制
0x7278e88aa8 libc.so!pthread_create+0x290

根据上面的输出加上偏移得到 clone 函数的最终地址为 0xAFAA8:

代码语言:bash
复制
0x00000000000AF818 + 0x290 = 0x00000000000AFAA8

按 g 跳转到该地址,如下所示:

EPvGtf.png
EPvGtf.png
代码语言:c
代码运行次数:0
运行
复制
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 内部,线程函数会存储在线程控制块中:

代码语言:c
代码运行次数:0
运行
复制
*(_QWORD *)(v31 + 96) = a3;  // 将用户线程函数写入线程控制块

通过读取 v31 + 96 的地址,我们可以获取实际执行的线程函数,hook 代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvxoc.png

可以看到成功输出了该 so 文件的线程函数,接下来,我们尝试着先 nop 掉这几个函数,看能否过检测,hook 代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvAH3.png

发现我们 nop 掉线程后,并没有报错,那证明我们 nop 掉没有问题,但是卡在了最开始的 libmsaoaidsec.so 文件里,对于这个文件,我们同样直接 nop 掉里面的线程即可:

代码语言:javascript
代码运行次数:0
运行
复制
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
EPvSL9.png

至此,该 app 的 frida 检测分析流程就结束了。

相关 hook 脚本,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 声明
  • 逆向目标
  • 逆向分析
  • so 文件分析
  • ida 分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档