使用 Frida Stalker 反 OLLVM 算法还原

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

前言

比如我们像分析某个 so 中偏移为 0x23AD0 的加密函数

IDA 反汇编 so,可以看到 so 中该函数做了混淆

word/media/image1.png

控制流平坦化

word/media/image2.png

Frida Stalker

Frida 的 Stalker 是一个强大的代码追踪工具。

主要功能

  1. 指令级跟踪:Stalker 可精确到指令级别,对应用的原生代码进行实时监控。

  2. 代码插桩:支持在指定指令前后插入自定义的代码逻辑。

  3. 内存访问监控:可以监视内存读写操作,分析数据流向。

  4. 自定义回调:提供回调函数,方便记录和分析执行轨迹。

文档:https://frida.re/docs/javascript-api/#stalker

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

word/media/image3.png
https://frida.re/docs/stalker/

关于 Frida 的使用可以参考这篇文章:使用 Frida Hook Android App

onCallSummary(函数调用摘要)

onCallSummary 用于返回函数调用的摘要信息。

Stalker.follow() 使用 onCallSummary 时,不会实时回调,而是在合适的时间点返回已汇总的调用信息。

通常是在:

  • 函数执行结束后

  • 线程空闲或上下文切换时

  • Stalker缓冲区达到一定大小时

  • Stalker.flush();

因此,你会在目标函数执行完毕后,看到 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 在 Stalker 捕获到事件后被调用,它会以批量形式传递事件数据,通常用于实时分析或记录。

onReceive 传递的 events 数据需要用 Stalker.parse() 解析,解析后的数据是数组类型,格式如下:

call,0x789143a16c,0x77a1addf1c,0
ret,0x7890000e34,0x788ff64e68,2

[事件类型], [调用方地址], [目标地址], [附加信息]

跟踪 call 和 ret 事件 并打印日志:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值