上篇介绍了 native 去混淆中字符串加解密的技巧,本篇聚焦去除 native 中的间接跳转(indirect jump)和间接函数调用(indirect call)混淆。这类混淆经常被大厂 APP 使用,用以增加静态分析的难度和时间成本。间接跳转与间接调用的本质是隐藏静态控制流:静态分析无法直接得知某条跳转或调用会落到哪个目标。最直接的思路是让程序运行一次,记录实际执行时的跳转/调用目标,然后把这些目标 patch 回二进制,从而恢复可读的控制流。本文介绍两种简单、高效且实践可行的“暴力”修复思路,无需完整模拟环境或复杂的符号化执行:对间接跳转与间接调用实现感兴趣的朋友,可以参考 ollvm 混淆项目:
6bcK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2L8$3#2A6e0h3!0W2i4K6u0r3b7i4u0C8j5i4u0A6
c26K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2L8h3W2E0L8#2)9J5c8X3N6G2M7X3!0F1
801K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6p5M7X3g2S2L8g2y4G2N6h3I4W2i4K6u0r3L8$3I4D9N6X3@1I4y4H3`.`.思路概述:对于只需还原“大体执行逻辑”而不关心精确分支行为的场景,可以直接把函数内的所有 BR/BLR(或类似的间接跳转/调用指令)替换为 NOP,然后合并基本块。这样静态反编译会把原本被混淆的控制流线性化,便于快速查看调用关系与整体逻辑。适用场景:优缺点:具体流程(推荐步骤):示例截图: 运行脚本并合并基本块后的效果(可以看到这里还用到了间接函数调用保护):
思路概述:通过静态分析先定位所有可能的间接跳转/调用指令(BR、BLR 等),自动生成 Frida hook 脚本,在运行时捕获这些指令的实际目标地址(通常以模块基址的相对偏移记录)。把运行时采集到的目标地址写入日志后,使用 IDA Python 脚本把原来的间接跳转/调用 patch 为直接跳转/调用(或直接把目标地址写回),从而尽可能恢复原始控制流。优缺点:具体流程 :示例流程截图:
静态分析 修复前的控制流与伪代码:
运行 fix_blr_2_nop.py 作为预处理(可选):
生成 Frida hook 脚本:
调整脚本中的模块名并以 spawn 模式注入:
将 Frida 输出保存到日志文件,运行IDA 脚本 fix_indirect_jump.py 解析并 patch:
补丁生效后刷新伪代码:
小提示与实践建议:本文给出两种“暴力但实用”的思路来处理基于 ollvm 的间接跳转与间接函数调用混淆:两者结合使用,通常能大幅降低分析成本并较好地恢复可读控制流。代码仓库: 27cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6A6L8h3t1@1k6r3@1@1L8W2)9J5c8V1c8W2j5h3c8D9P5g2)9J5k6o6x3H3x3o6l9`.
方法1:暴力 NOP(快速但破坏分支信息)
方法2:利用 Frida 批量 hook(更精确,但依赖实际执行路径)
想快速定位函数调用、数据访问,而不要求分支精确还原;
分析时间紧张,需要先得到可读伪代码以便后续调查。
优点:快捷、无需运行环境或动态跟踪;
缺点:破坏原始控制流,条件分支(if/while)将丢失,导致反编译器无法恢复分支语义。
从函数入口向后扫描到下一个 RET(或函数结束)位置;
列出这一区间内的 BR 指令地址;
将这些指令 patch 为 NOP 并尝试合并基本块;
优点:能恢复很多实际执行过的跳转/调用,得到更准确的控制流;
缺点:不能覆盖未执行到的路径;条件跳转对应的分支如果未走到也无法修复;需要可运行环境并能加载目标 SO。
使用 Frida 的 spawn + resume 或 frida -f 来启动并注入;
让程序执行到这些间接跳转/调用位置;
先用方法1 快速线性化函数,得到可读伪代码,再针对剩余的间接函数调用用方法2 精修;
确保 Frida hook 输出包含模块相对偏移;
对于未执行到的分支,可以考虑构造输入或模拟路径以触发更多分支,但工作量会增加;
patch 不满意,可以Ctl + ALT + P 打开补丁界面,把补丁删除后,F5刷新。
在 IDA 中把光标移动到目标函数入口;
运行脚本 fix_blr_2_nop.py ,脚本会:
从函数入口向后扫描到下一个 RET(或函数结束)位置;
列出这一区间内的 BR 指令地址;
将这些指令 patch 为 NOP 并尝试合并基本块;
刷新反编译(F5)观察伪代码并继续人工分析。
静态定位目标指令:在 IDA 中扫描待修复函数,记录 BR/BLR 指令地址;
自动生成 Frida 脚本:ALT+F7运行脚本 toolchain_trace_indirect_jumps.py 生成一个包含所有待 hook 地址的 Frida 脚本;
调整脚本:把脚本中的模块名改为你要分析的 so 名称,确认 log 输出格式(建议输出模块偏移、指令地址、寄存器值等);
启动被测进程并注入(建议使用 spawn 模式以便在早期捕获流程):
使用 Frida 的 spawn + resume 或 frida -f 来启动并注入;
让程序执行到这些间接跳转/调用位置;
保存日志:把 Frida 的输出保存为文本文件;
在 IDA 中运行补丁脚本:使用 fix_indirect_jump.py(或自写脚本)解析日志并将间接跳转/调用替换为直接跳转/调用或填写目标地址;
刷新反编译查看修复结果并人工验证。
回复或点赞可查看完整内容
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289439.htm
[原创][推荐][原创]用奥卡姆剃刀解决 ollvm 间接跳转和函数调用
354 浏览
9 回复
感谢分享
tql
学习学习
看看
你的帖子非常有用,感谢!
fd
直接trace,blr和br打印调用
感谢分享
学习