使用 Frida Stalker 反 OLLVM 算法还原

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

前言

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

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

控制流平坦化

Frida Stalker

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

主要功能

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

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

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

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

文档:frida.re/docs/javasc...

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

frida.re/docs/stalke...

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

onCallSummary(函数调用摘要)

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

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

通常是在:

  • 函数执行结束后

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

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

  • Stalker.flush();

因此,你会在目标函数执行完毕后,看到 onCallSummary 输出的调用信息。

summary 数据结构通常类似于以下格式:

json 复制代码
{
  "函数地址": 调用次数
}

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

javascript 复制代码
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 并执行脚本

r 复制代码
frida -H 127.0.0.1:1234 -F -l onCallSummary.js

输出如下:

makefile 复制代码
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() 解析,解析后的数据是数组类型,格式如下:

css 复制代码
call,0x789143a16c,0x77a1addf1c,0
ret,0x7890000e34,0x788ff64e68,2

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

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

javascript 复制代码
function getModuleByAddressSafe(address) {
    try {
        // 尝试获取模块
        var module = Process.getModuleByAddress(address);

        // 如果模块存在,返回模块
        if (module) {
            return module;
        } else {
            // 如果没有找到模块,返回 null
            return null;
        }
    } catch (e) {
        // 捕获异常,返回 null
        return null;
    }
}

function onReceive() {

    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: true,       // 捕获函数返回
                    exec: false,      // 捕获指令执行
                    block: false,    // 捕获基本块
                    compile: false   // 捕获编译事件
                },
                // 实时接收事件数据
                onReceive: function (events) {
                    var parsedEvents = Stalker.parse(events);

                    console.log(`onReceive 事件数量: ${parsedEvents.length}`);

                    parsedEvents.forEach(function (event) {
                        // console.log(`收到事件: ${event}`);
                        var caller = getModuleByAddressSafe(event[1]);
                        var target = getModuleByAddressSafe(event[2]);

                        // 判断地址是否属于目标 so
                        if (caller && caller.name === soName) {

                            var callerName = caller ? caller.name : "Unknown"
                            var targetName = target ? target.name : "Unknown"

                            var callerOffset = caller ? ptr(event[1]).sub(caller.base) : "Unknown";
                            var targetOffset = target ? ptr(event[2]).sub(target.base) : "Unknown";

                            console.log(`[${event[0]}] from: ${event[1]} | ${callerName} | ${callerOffset} -> to: ${event[2]} | ${targetName} | ${targetOffset}`);
                        }
                    });
                }
            });
        },

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

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

附加到当前 app 并执行脚本

arduino 复制代码
frida -H 127.0.0.1:1234 -F -l onReceive.js

输出如下:

yaml 复制代码
Target function found at: 0x77fdf2ead0
[Remote::AndroidExample]-> Entering target function
Leaving target function
onReceive 事件数量: 390
[call] from: 0x77fdf2eb08 | libaes.so | 0x23b08 -> to: 0x77fdf62d30 | libaes.so | 0x57d30
[call] from: 0x77fdf2ef48 | libaes.so | 0x23f48 -> to: 0x780d775348 | libart.so | 0x360348
[ret] from: 0x77fdf2ef54 | libaes.so | 0x23f54 -> to: 0x77fdf2eb0c | libaes.so | 0x23b0c
[call] from: 0x77fdf2eb18 | libaes.so | 0x23b18 -> to: 0x77fdf62d40 | libaes.so | 0x57d40
[call] from: 0x77fdf2ef7c | libaes.so | 0x23f7c -> to: 0x780d773378 | libart.so | 0x35e378
[ret] from: 0x77fdf2ef88 | libaes.so | 0x23f88 -> to: 0x77fdf2eb1c | libaes.so | 0x23b1c
[call] from: 0x77fdf2eb34 | libaes.so | 0x23b34 -> to: 0x77fdf303cc | libaes.so | 0x253cc
[ret] from: 0x77fdf3048c | libaes.so | 0x2548c -> to: 0x77fdf2eb38 | libaes.so | 0x23b38
[call] from: 0x77fdf2eb3c | libaes.so | 0x23b3c -> to: 0x77fdf62d00 | libaes.so | 0x57d00
[call] from: 0x77fdf2e87c | libaes.so | 0x2387c -> to: 0x77fdf62cf0 | libaes.so | 0x57cf0
[call] from: 0x77fdf38f98 | libaes.so | 0x2df98 -> to: 0x77fdf62d50 | libaes.so | 0x57d50
[ret] from: 0x77fdf38fbc | libaes.so | 0x2dfbc -> to: 0x77fdf2e880 | libaes.so | 0x23880
[ret] from: 0x77fdf2e8ec | libaes.so | 0x238ec -> to: 0x77fdf2eb40 | libaes.so | 0x23b40
[call] from: 0x77fdf2eb4c | libaes.so | 0x23b4c -> to: 0x77fdf62ce0 | libaes.so | 0x57ce0
[call] from: 0x77fdf2e7f4 | libaes.so | 0x237f4 -> to: 0x77fdf62cf0 | libaes.so | 0x57cf0
[call] from: 0x77fdf38f98 | libaes.so | 0x2df98 -> to: 0x77fdf62d50 | libaes.so | 0x57d50
[ret] from: 0x77fdf38fbc | libaes.so | 0x2dfbc -> to: 0x77fdf2e7f8 | libaes.so | 0x237f8
[ret] from: 0x77fdf2e864 | libaes.so | 0x23864 -> to: 0x77fdf2eb50 | libaes.so | 0x23b50
[call] from: 0x77fdf2eb80 | libaes.so | 0x23b80 -> to: 0x77fdf62d50 | libaes.so | 0x57d50
[call] from: 0x77fdf2ebb8 | libaes.so | 0x23bb8 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf2ebd0 | libaes.so | 0x23bd0 -> to: 0x77fdf62d10 | libaes.so | 0x57d10
[ret] from: 0x77fdf2e9bc | libaes.so | 0x239bc -> to: 0x77fdf2ebd4 | libaes.so | 0x23bd4
[call] from: 0x77fdf2ebdc | libaes.so | 0x23bdc -> to: 0x77fdf62d70 | libaes.so | 0x57d70
[ret] from: 0x77fdf35128 | libaes.so | 0x2a128 -> to: 0x77fdf2ebe0 | libaes.so | 0x23be0
[call] from: 0x77fdf2ebfc | libaes.so | 0x23bfc -> to: 0x77fdf62d80 | libaes.so | 0x57d80
[call] from: 0x77fdf36664 | libaes.so | 0x2b664 -> to: 0x77fdf62f70 | libaes.so | 0x57f70
[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -> to: 0x77fdf36668 | libaes.so | 0x2b668
[call] from: 0x77fdf366b0 | libaes.so | 0x2b6b0 -> to: 0x77fdf30b8c | libaes.so | 0x25b8c
[call] from: 0x77fdf30bb8 | libaes.so | 0x25bb8 -> to: 0x77fdf62eb0 | libaes.so | 0x57eb0
[call] from: 0x77fdf31744 | libaes.so | 0x26744 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf3179c | libaes.so | 0x2679c -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf317f4 | libaes.so | 0x267f4 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf3184c | libaes.so | 0x2684c -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[call] from: 0x77fdf31898 | libaes.so | 0x26898 -> to: 0x77fdf34580 | libaes.so | 0x29580
[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -> to: 0x77fdf3189c | libaes.so | 0x2689c
[ret] from: 0x77fdf324d8 | libaes.so | 0x274d8 -> to: 0x77fdf30bbc | libaes.so | 0x25bbc
[ret] from: 0x77fdf30bc4 | libaes.so | 0x25bc4 -> to: 0x77fdf366b4 | libaes.so | 0x2b6b4
[ret] from: 0x77fdf36764 | libaes.so | 0x2b764 -> to: 0x77fdf2ec00 | libaes.so | 0x23c00
[call] from: 0x77fdf2ed68 | libaes.so | 0x23d68 -> to: 0x77fdf62d50 | libaes.so | 0x57d50
[call] from: 0x77fdf2ed80 | libaes.so | 0x23d80 -> to: 0x77fdf62db0 | libaes.so | 0x57db0
[call] from: 0x77fdf35fcc | libaes.so | 0x2afcc -> to: 0x77fdf62f70 | libaes.so | 0x57f70
[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -> to: 0x77fdf35fd0 | libaes.so | 0x2afd0
[call] from: 0x77fdf36190 | libaes.so | 0x2b190 -> to: 0x77fdf30bc8 | libaes.so | 0x25bc8
[call] from: 0x77fdf30bec | libaes.so | 0x25bec -> to: 0x77fdf62ec0 | libaes.so | 0x57ec0
[call] from: 0x77fdf326c8 | libaes.so | 0x276c8 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf3274c | libaes.so | 0x2774c -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf327d0 | libaes.so | 0x277d0 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf32834 | libaes.so | 0x27834 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf32fa8 | libaes.so | 0x27fa8 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf330e8 | libaes.so | 0x280e8 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf33208 | libaes.so | 0x28208 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[call] from: 0x77fdf332f0 | libaes.so | 0x282f0 -> to: 0x77fdf62d60 | libaes.so | 0x57d60
[ret] from: 0x77fdf33334 | libaes.so | 0x28334 -> to: 0x77fdf30bf0 | libaes.so | 0x25bf0
[ret] from: 0x77fdf30bf8 | libaes.so | 0x25bf8 -> to: 0x77fdf36194 | libaes.so | 0x2b194
[ret] from: 0x77fdf3626c | libaes.so | 0x2b26c -> to: 0x77fdf2ed84 | libaes.so | 0x23d84
[call] from: 0x77fdf2ee08 | libaes.so | 0x23e08 -> to: 0x77fdf62dc0 | libaes.so | 0x57dc0
[call] from: 0x77fdf35cc4 | libaes.so | 0x2acc4 -> to: 0x77fdf62f70 | libaes.so | 0x57f70
[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -> to: 0x77fdf35cc8 | libaes.so | 0x2acc8
[call] from: 0x77fdf35d08 | libaes.so | 0x2ad08 -> to: 0x77fdf30f60 | libaes.so | 0x25f60
[ret] from: 0x77fdf30f6c | libaes.so | 0x25f6c -> to: 0x77fdf35d0c | libaes.so | 0x2ad0c
[ret] from: 0x77fdf35d20 | libaes.so | 0x2ad20 -> to: 0x77fdf2ee0c | libaes.so | 0x23e0c
[call] from: 0x77fdf2ee14 | libaes.so | 0x23e14 -> to: 0x77fdf62dd0 | libaes.so | 0x57dd0
[call] from: 0x77fdf2efb0 | libaes.so | 0x23fb0 -> to: 0x780d775280 | libart.so | 0x360280
[ret] from: 0x77fdf2efbc | libaes.so | 0x23fbc -> to: 0x77fdf2ee18 | libaes.so | 0x23e18
[call] from: 0x77fdf2ee30 | libaes.so | 0x23e30 -> to: 0x77fdf62de0 | libaes.so | 0x57de0
[call] from: 0x77fdf2effc | libaes.so | 0x23ffc -> to: 0x780d775690 | libart.so | 0x360690
[ret] from: 0x77fdf2f008 | libaes.so | 0x24008 -> to: 0x77fdf2ee34 | libaes.so | 0x23e34
[call] from: 0x77fdf2ee5c | libaes.so | 0x23e5c -> to: 0x77fdf62df0 | libaes.so | 0x57df0
[call] from: 0x77fdf2ee94 | libaes.so | 0x23e94 -> to: 0x77fdf62df0 | libaes.so | 0x57df0
[call] from: 0x77fdf2eeb4 | libaes.so | 0x23eb4 -> to: 0x77fdf62e00 | libaes.so | 0x57e00
[call] from: 0x77fdf2f040 | libaes.so | 0x24040 -> to: 0x780d775448 | libart.so | 0x360448
[ret] from: 0x77fdf2f04c | libaes.so | 0x2404c -> to: 0x77fdf2eeb8 | libaes.so | 0x23eb8
[call] from: 0x77fdf2eebc | libaes.so | 0x23ebc -> to: 0x77fdf62da0 | libaes.so | 0x57da0
[call] from: 0x77fdf2eec4 | libaes.so | 0x23ec4 -> to: 0x77fdf62da0 | libaes.so | 0x57da0
[ret] from: 0x77fdf2ef10 | libaes.so | 0x23f10 -> to: 0x789143a60c | Unknown | Unknown

假如汇编代码中 BLR X8 我们不知道它具体调用的是什么

通过 onRecive 解析可以知道 调用的是 libaes.so 偏移 0x25f60 的函数

用 IDA 打开 libaes.so 并调整到对应的地址,可以找到调用的函数

hook 所有 call 参数分析

把所有调用到的函数 hook 分析一下

去掉一些系统 api 的 hook

如果是跳转表则在 IDA 找到跳转的真实偏移地址

kotlin 复制代码
function printArg(addr) {
    // 查找给定地址所在的内存范围
    var range = Process.findRangeByAddress(addr);
    // 如果该地址属于进程中的已知内存范围(例如模块中的数据段或代码段等)
    if (range) {
        return hexdump(addr) + "\n";
    } else {
        return ptr(addr) + "\n";
    }
}


function hookNativeAddr(addr) {

    var module = Process.findModuleByAddress(ptr(addr))

    Interceptor.attach(addr, {
        onEnter: function (args) {

            this.arg0 = args[0];
            this.arg1 = args[1];
            this.arg2 = args[2];
            this.arg3 = args[3];
            this.arg4 = args[4];
            this.logs = [];
            
            this.logs.push("call " + module.name + " | " + ptr(addr).sub(module.base) + "\n");
            this.logs.push("arg0:" + printArg(this.arg0));
            this.logs.push("arg1:" + printArg(this.arg1));
            this.logs.push("arg2:" + printArg(this.arg2));
            this.logs.push("arg3:" + printArg(this.arg3));
            this.logs.push("arg4:" + printArg(this.arg4));
        },

        onLeave: function (retval) {
            this.logs.push("onLeave arg0:" + printArg(this.arg0));
            this.logs.push("onLeave arg1:" + printArg(this.arg1));
            this.logs.push("onLeave arg2:" + printArg(this.arg2));
            this.logs.push("onLeave arg3:" + printArg(this.arg3));
            this.logs.push("onLeave arg4:" + printArg(this.arg4));
            this.logs.push("retval:" + printArg(retval));
            console.log(this.logs);
        }
    });
}


function main() {
    // 目标 so 基址
    var baseAddress = Module.findBaseAddress("libaes.so");

    // hookNativeAddr(baseAddress.add(0x23AD0));

    // hookNativeAddr(baseAddress.add(0x57d50)); // .malloc
    hookNativeAddr(baseAddress.add(0x23868));    // stringToSecretKey 跳转表,0x57d00 实际偏移是 0x23868
    hookNativeAddr(baseAddress.add(0x23F8C));    // _JNIEnv::NewByteArray(_JNIEnv *this, unsigned int) 跳转表,0x57dd0 实际偏移是 0x23F8C
    hookNativeAddr(baseAddress.add(0x2ABEC));    // 0x57f70 -> 0x2ABEC
    hookNativeAddr(baseAddress.add(0x2B528));    // 0x57d80 -> 0x2B528
    hookNativeAddr(baseAddress.add(0x25f60));
    hookNativeAddr(baseAddress.add(0x23F1C));    // 0x57d30 -> 0x23F1C
    hookNativeAddr(baseAddress.add(0x2400C));
    hookNativeAddr(baseAddress.add(0x25b8c));
    hookNativeAddr(baseAddress.add(0x29580));
    hookNativeAddr(baseAddress.add(0x237E0));
    hookNativeAddr(baseAddress.add(0x2AE80));
    // hookNativeAddr(baseAddress.add(0x57d60));  // _memcpy_chk
    hookNativeAddr(baseAddress.add(0x253cc));
    hookNativeAddr(baseAddress.add(0x238F0));
    hookNativeAddr(baseAddress.add(0x23FC0));
    hookNativeAddr(baseAddress.add(0x25bc8));
    hookNativeAddr(baseAddress.add(0x26524));
    hookNativeAddr(baseAddress.add(0x23F58));
    // hookNativeAddr(baseAddress.add(0x2E038));    // operator new[](unsigned __int64)
    hookNativeAddr(baseAddress.add(0x2AC50));
    hookNativeAddr(baseAddress.add(0x29F6C));
    // hookNativeAddr(baseAddress.add(0x2E090));   // operator delete[](void *)
    hookNativeAddr(baseAddress.add(0x274DC));
    // hookNativeAddr(baseAddress.add(0x57da0));   // free
}

setImmediate(main)

附加到当前 app 并执行脚本

r 复制代码
frida -H 127.0.0.1:1234 -F -l hookNativeAddr.js

app 中加密结果

在日志中找到第一次出现结果的地方

找到这个函数 call libaes.so | 0x274dc

用 IDA 看这个函数中多处引用到一个全局变量

是一些常量值

搜索看看,是 AES 的特征

打印调用堆栈

打印该函数的调用堆栈看看

文档:frida.re/docs/javasc...

typescript 复制代码
function getModuleByAddressSafe(address) {
    try {
        // 尝试获取模块
        var module = Process.getModuleByAddress(address);

        // 如果模块存在,返回模块
        if (module) {
            return module;
        } else {
            // 如果没有找到模块,返回 null
            return null;
        }
    } catch (e) {
        // 捕获异常,返回 null
        return null;
    }
}

function main() {
    var addr = Module.findBaseAddress("libaes.so").add(0x274DC);

    Interceptor.attach(addr, {
        onEnter: function (args) {
            console.log('called from:\n' +
                Thread.backtrace(this.context, Backtracer.ACCURATE)
                    .map((address) => {
                        const symbol = DebugSymbol.fromAddress(address);

                        if (symbol && symbol.name) {
                            // 如果有符号信息,直接显示
                            return `${address} ${symbol.moduleName}!${symbol.name}+0x${symbol.address.sub(Module.findBaseAddress(symbol.moduleName)).toString(16)}`;
                        } else {
                            // 如果没有符号信息,尝试获取模块和偏移信息
                            const module = getModuleByAddressSafe(address);
                            if (module) {
                                const offset = ptr(address).sub(module.base);
                                return `${address} ${module.name} + 0x${offset.toString(16)}`;
                            } else {
                                return `${address} [Unknown]`;
                            }
                        }
                    })
                    .join('\n') + '\n');
        },

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

setImmediate(main);

附加到当前 app 并执行脚本

r 复制代码
frida -H 127.0.0.1:1234 -F -l printStack.js

输出如下:

sql 复制代码
[Remote::AndroidExample]-> called from:
0x77fe5f0bf0 libaes.so + 0x25bf0
0x77fe5f0bec libaes.so + 0x25bec
0x77fe5f6190 libaes.so + 0x2b190
0x77fe5eed80 libaes.so + 0x23d80
0x780d554350 libart.so!art_quick_generic_jni_trampoline+0x90+0x13f350
0x780d54b5b8 libart.so!art_quick_invoke_static_stub+0x238+0x1365b8
0x780d55a0cc libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0x114+0x1450cc
0x780d6f6f98 libart.so!_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+0x180+0x2e1f98
0x780d6f2024 libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x384+0x2dd024
0x780d9b81f8 libart.so!MterpInvokeStatic+0x170+0x5a31f8
0x780d545994 libart.so!mterp_op_invoke_static+0x14+0x130994
0x7fd160b0ac [Unknown]

开始调用的位置在 0x77fe5eed80 libaes.so + 0x23d80

函数真实地址是 0x2ae80

在 call libaes.so | 0x2ae80 的 arg3 中找到 key / iv

验证算法

把 arg3 的 hexdump 复制到 CyberChef

得到 key / iv 应该是 "CYRUS STUDIO "

使用 CyberChef 的 AES CBC 算法加密得到结果和 app 的是一样的。

所有这就是一个标准的 AES CBC 算法,key 和 iv 都是 "CYRUS STUDIO "

Frida Trace

把 exec 设置为 true 也可以当 trace 用

javascript 复制代码
function getModuleByAddressSafe(address) {
    try {
        // 尝试获取模块
        var module = Process.getModuleByAddress(address);

        // 如果模块存在,返回模块
        if (module) {
            return module;
        } else {
            // 如果没有找到模块,返回 null
            return null;
        }
    } catch (e) {
        // 捕获异常,返回 null
        return null;
    }
}


function main(soName, offset) {
    var baseAddress = Module.findBaseAddress(soName);
    var targetAddr = baseAddress.add(offset);

    Interceptor.attach(targetAddr, {
        onEnter: function (args) {
            console.log(`Entering function at: ${targetAddr}`);

            Stalker.follow(Process.getCurrentThreadId(), {
                events: {
                    exec: true
                },
                onReceive: function (events) {
                    var parsedEvents = Stalker.parse(events);
                    parsedEvents.forEach(event => {
                        if (event[0] === 'exec') {
                            const address = ptr(event[1]);
                            const instruction = Instruction.parse(address);
                            const module = getModuleByAddressSafe(address);
                            const offset = module ? address.sub(module.base) : null;

                            // 判断地址是否属于目标 so
                            if (module && module.name === soName) {
                                if (module) {
                                    const logMessage = `${address} | ${module.name} + 0x${offset.toString(16)} | ${instruction}`;
                                    console.log(logMessage)
                                } else {
                                    const logMessage = `${address} | Unknown | ${instruction}`;
                                    console.log(logMessage)
                                }
                            }
                        }
                    });
                }
            });
        },

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

setImmediate(function () {
    main("libaes.so", 0x274DC)
});

附加到当前 app 并执行脚本,并把日志保存到 trace.txt

bash 复制代码
frida -H 127.0.0.1:1234 -F -l trace.js | tee trace.txt

效果如下:

完整源码

完整源码地址:github.com/CYRUS-STUDI...

相关推荐
Tsing72212 分钟前
Android窗口Surface简介
android
诺亚凹凸曼14 分钟前
23种设计模式-结构型模式-桥接器
android·java·设计模式
帅次1 小时前
Flutter DropdownButton 详解
android·flutter·ios·kotlin·gradle·webview
际宇人1 小时前
移动端APP阿里云验证码2.0接入实录
android
.又是新的一天.1 小时前
02_MySQL安装及配置
android·数据库·mysql
QING6182 小时前
Kotlin groupBy用法及代码示例
android·kotlin·源码阅读
QING6182 小时前
Kotlin getOrElse用法及代码示例
android·kotlin·源码阅读
QING6182 小时前
Kotlin getOrNull用法及代码示例
android·kotlin·源码阅读
QING6182 小时前
Kotlin getValue用法及代码示例
android·kotlin·源码阅读
QING6182 小时前
Kotlin getOrPut用法及代码示例
android·kotlin·源码阅读