版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
前言
比如我们像分析某个 so 中偏移为 0x23AD0 的加密函数
IDA 反汇编 so,可以看到 so 中该函数做了混淆
控制流平坦化
Frida Stalker
Frida 的 Stalker 是一个强大的代码追踪工具。
主要功能
-
指令级跟踪:Stalker 可精确到指令级别,对应用的原生代码进行实时监控。
-
代码插桩:支持在指定指令前后插入自定义的代码逻辑。
-
内存访问监控:可以监视内存读写操作,分析数据流向。
-
自定义回调:提供回调函数,方便记录和分析执行轨迹。
目前 Stalker 对于 arm64 支持比较好,但是 arm32 并不是很完善。
关于 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 的特征
打印调用堆栈
打印该函数的调用堆栈看看
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...