一文搞懂 Frida Stalker:对抗 OLLVM 的算法还原利器

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

在移动应用的安全加固中,OLLVM(Obfuscator-LLVM) 是一种常见的代码混淆与保护手段。它通过控制流平坦化、虚假控制流、指令替换等方式,使逆向分析者很难直接还原出原始算法逻辑。

然而,通过 Frida 提供的 Stalker 模块去动态分析让我们有机会对 OLLVM 的“黑盒逻辑”进行还原。

通过 Stalker 的指令级追踪能力,我们不仅能捕获 函数调用 、记录 调用参数 ,还可以打印 调用堆栈 ,最终逐步揭开 OLLVM 加固算法的真实运行流程。

比如,分析某个 so 中偏移为 0x23AD0 的加密函数。使用 IDA 反汇编 so,可以看到 so 中该函数做了混淆

word/media/image1.png

使用了控制流平坦化混淆

word/media/image2.png

Frida Stalker

Frida Stalker 是 Frida 提供的一个强大的指令级追踪引擎,它能够在目标进程运行时,动态捕获每一条指令的执行情况。与传统的函数级 hook 不同,Stalker 可以深入到 原生汇编层面 ,追踪寄存器变化、内存访问、函数调用关系等底层细节。

相关链接:

目前 Stalker 对于 arm64 支持比较好,但是 arm32 并不是很完善。

word/media/image3.png

onCallSummary(函数调用摘要)

onCallSummary 是 Frida Stalker 提供的一个回调方法,用于在 函数调用层面 对收集到的执行数据进行归纳和统计。

它会将某一段追踪区间内的 调用信息进行汇总 ,例如:

  • 哪些函数被调用了

  • 每个函数被调用了多少次

  • 调用分布和频率

简而言之,onCallSummary 像是 函数调用的统计报表 ,让你能在混淆代码的“噪音”中看清主干逻辑。

返回数据的结构(summary)通常类似于以下格式:

{
  "函数地址": 调用次数
}

hook 目标函数,跟踪 call 事件并打印 call summary

function onCallSummary() {
    // 目标 so
    var soName = "libaes.so"
    // 目标 so 基址
    var baseAddress = Module.findBaseAddress(soName);
    // 目标函数地址 = 基址 + 偏移
    var targetAddr = baseAddress.add(0x23AD0);

    console.log('Target function found at:', targetAddr);

    Interceptor.attach(targetAddr, {
        onEnter: function (args) {
            console.log('Entering target function');
            Stalker.follow(Process.getCurrentThreadId(), {
                events: {
                    call: true,      // 捕获函数调用
                    ret: false,       // 捕获函数返回
                    exec: false,      // 捕获指令执行
                    block: false,    // 捕获基本块
                    compile: false   // 捕获编译事件
                },
                onCallSummary: function (summary) {
                    console.log('Call Summary:');
                    Object.keys(summary).forEach(function (addr) {
                        var module = Process.getModuleByAddress(ptr(addr));
                        // 判断地址是否属于目标 so
                        if (module && module.name === soName) {
                            var offset = ptr(addr).sub(module.base);
                            console.log(`调用函数地址: ${ptr(addr)} | 模块: ${module.name} | 偏移: ${offset} | 次数: ${summary[addr]}`);
                        }
                    });
                }
            });
        },

        onLeave: function (retval) {
            console.log('Leaving target function');
            Stalker.unfollow(Process.getCurrentThreadId());
        }
    });
}

// setImmediate():确保代码在 Frida 环境准备好后执行。
setImmediate(onCallSummary)

启动 frida-server,附加到当前 app 并执行脚本

frida -H 127.0.0.1:1234 -F -l onCallSummary.js

输出如下:

Target function found at: 0x77fdf2ead0
[Remote::AndroidExample]-> Entering target function
Leaving target function
Call Summary:
调用函数地址: 0x77fdf62d50 | 模块: libaes.so | 偏移: 0x57d50 | 次数: 4
调用函数地址: 0x77fdf62d00 | 模块: libaes.so | 偏移: 0x57d00 | 次数: 1
调用函数地址: 0x77fdf62dd0 | 模块: libaes.so | 偏移: 0x57dd0 | 次数: 1
调用函数地址: 0x77fdf62f70 | 模块: libaes.so | 偏移: 0x57f70 | 次数: 3
调用函数地址: 0x77fdf62d80 | 模块: libaes.so | 偏移: 0x57d80 | 次数: 1
调用函数地址: 0x77fdf30f60 | 模块: libaes.so | 偏移: 0x25f60 | 次数: 1
调用函数地址: 0x77fdf62d30 | 模块: libaes.so | 偏移: 0x57d30 | 次数: 1
调用函数地址: 0x77fdf62e00 | 模块: libaes.so | 偏移: 0x57e00 | 次数: 1
调用函数地址: 0x77fdf30b8c | 模块: libaes.so | 偏移: 0x25b8c | 次数: 1
调用函数地址: 0x77fdf34580 | 模块: libaes.so | 偏移: 0x29580 | 次数: 10
调用函数地址: 0x77fdf62ce0 | 模块: libaes.so | 偏移: 0x57ce0 | 次数: 1
调用函数地址: 0x77fdf62db0 | 模块: libaes.so | 偏移: 0x57db0 | 次数: 1
调用函数地址: 0x77fdf62d60 | 模块: libaes.so | 偏移: 0x57d60 | 次数: 13
调用函数地址: 0x77fdf303cc | 模块: libaes.so | 偏移: 0x253cc | 次数: 1
调用函数地址: 0x77fdf62d10 | 模块: libaes.so | 偏移: 0x57d10 | 次数: 1
调用函数地址: 0x77fdf62de0 | 模块: libaes.so | 偏移: 0x57de0 | 次数: 1
调用函数地址: 0x77fdf30bc8 | 模块: libaes.so | 偏移: 0x25bc8 | 次数: 1
调用函数地址: 0x77fdf62eb0 | 模块: libaes.so | 偏移: 0x57eb0 | 次数: 1
调用函数地址: 0x77fdf62d40 | 模块: libaes.so | 偏移: 0x57d40 | 次数: 1
调用函数地址: 0x77fdf62cf0 | 模块: libaes.so | 偏移: 0x57cf0 | 次数: 2
调用函数地址: 0x77fdf62dc0 | 模块: libaes.so | 偏移: 0x57dc0 | 次数: 1
调用函数地址: 0x77fdf62d70 | 模块: libaes.so | 偏移: 0x57d70 | 次数: 1
调用函数地址: 0x77fdf62df0 | 模块: libaes.so | 偏移: 0x57df0 | 次数: 2
调用函数地址: 0x77fdf62ec0 | 模块: libaes.so | 偏移: 0x57ec0 | 次数: 1
调用函数地址: 0x77fdf62da0 | 模块: libaes.so | 偏移: 0x57da0 | 次数: 2

onReceive(接收捕获的事件)

onReceive 是 Frida Stalker 的另一个重要回调方法,用于 逐条接收捕获到的事件 。与 onCallSummary 不同,它不会进行统计汇总,而是将底层指令级别的执行轨迹实时发送到回调中。

在 onReceive 中,你能拿到最原始的 执行事件数据 ,例如:

  • 每条指令的执行地址

  • 寄存器变化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值