使用 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...

相关推荐
sun0077003 小时前
android ndk编译valgrind
android
AI视觉网奇4 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空4 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet5 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin5 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo030519877 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
伐尘8 小时前
【CE】图形化CE游戏教程通关手册
前端·chrome·游戏·逆向
00后程序员张9 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风11 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio
编程乐学11 小时前
安卓原创--基于 Android 开发的菜单管理系统
android