目录
最近把收藏的百来篇网页技术文章保存到本地, 构建知识库供 AI 调用, 希望以此自动化 ai 一把嗦
实际使用下来确实有部分成果, 但依赖文章本身的准确性和完整性, AI 复现起来还是很快的
但没有对应资料时, 自动化嗦就有一些问题了:
如何省去人工打开 IDA 分析 so 的操作, 让 ai 自动调用 IDA 分析?
关于这个问题, 无名侠的 IDA-NO-MCP 足够用, 但是还不够自动化
于是搜索是否有 cli 版本, 正好有, 顺手让 ai 改了一份自用
IDA 会导出海量反编译文件, 包括大量函数
AI 的 Context 有限, 容易走进死胡同转圈, 反复踩坑
上来 hook exit, kill 等各种系统api, 力大砖飞
AI 谎报军情, 说脚本通过了, 实际是卡死了 APP 造成存活假象
看到 AI 谎报军情还产出了一坨屎山, 选择回归手工, 途中 AI 的帮助主要是: 改 trace 脚本, 分析函数功能等
手工跑通一遍之后, 可以总结经验, 提取特征码, 让 AI 自动分析其他样本实现通杀, 没必要每个样本都手工
总而言之, 希望本文能给师傅们带来一些帮助和启发
环境:
工具:
某加固最近的 4 个版本 (近2年):
针对 V1-V2版本, dump so , trace 和 bypass 较为简单, 前文 某加固新版frida检测绕过-trace一把嗦 已有详细介绍
针对 V3-V4版本, 后续则使用二分法和 Trace 定位辅助分析, 在此感谢迷人哥的思路
声明:
老式欧美打法, 起 frida-server, hook dlopen 并 dump so
会发现输出了目标 so 的基本信息,但是 dump 失败

如果查看maps, 利用maps dump出的so则没有有效信息, 说明真实目标 so不在系统 linker 的soinfo list中
推测存在自定义linker, 关于这方面知识点可参考前一篇文章 Android从ELF-Loader到自定义Linker的实现及原理
在内存中搜索匿名可执行模块成功dump目标 so

提取到 PC 并使用 SoFixer 修复:

修复后的 dump 版so 如下,无 JNI_OnLoad 符号,需要人工判定
tip: 也可以使用 bindiff 和旧版 libDexHelper 对比, 恢复部分符号

除了 dump 之外, 还可以使用脚本静态解密 apk的壳 so 以得到目标 so, 此处不做展开(后续水一篇一键脱壳).
unpack 版相比 dump 版, 保留了更多符号信息:
unpack 版,有 JNI_OnLoad 符号,可以看到地址为 0x13A8C

后续分析基于 unpack 版, 但两版地址相同, 脚本通用
前文 dump 得到了被自定义 linker 加载的 so, 那么问题来了:
通过 maps 和 soinfo 只能定位壳 so, 无法定位真实 so, 所以 frida 的部分 API (如 Module.findModuleByAddress) 无法正常使用
解决方法: scanHiddenModule 扫描到目标 so 后, 可获取对应的base和range, 后续hook 需要基于 base+offset 的方式进行
hook clone 定位目标 so 创建的线程检测函数, 可注释非目标 so的 log 防止干扰
结果如下, 输出了多个目标 so 创建的线程函数

直接 patch 这些函数开头为 ret 指令
patch成功, 但无法 bypass, 说明还有其他检测函数

(注意: 复现时使用 插件V1.2版本, log可能有所差别)
接下来可以使用 glass-stalker-trace trace 生成函数调用树以辅助分析
使用方法很简单, 将 glass_stalker_trace_ida.py 放入 ida的 plugins/目录下
依次点击 Edit > Plugins > Glass Stalker Trace: Export JS 即可导出 trace 脚本

使用前需要调用 configureTrace 进行基本配置, 之后传入 base 和 range 调用 trace_start 即可开始 trace, 建议输出 log 到文件中方便查看
trace log 如下, 再此简单介绍 trace 格式:
以节点 [tree] |-- sub_29D78 (0x29d78) [from=0x2832c, bl, depth=2] 为例:
[tree] 函数调用树标签, 用于打印函数节点, 还有其他各类标签, 方便分类 log
sub_29D78 函数名称, 若函数有符号或手动命名则有效, 否则默认 sub_xxx, 以 IDA 导出结果为准
(0x29d78) 函数相对模块的偏移地址
[from=0x2832c, bl, depth=2]
该节点 在 base+0x2832c 处被调用, 跳转指令为 bl, 节点深度为 2

trace 结果有 300+ 行, trace 目标函数退出前的 log 如下:
直接跟进 0x15a40, 发现位于 JNI_OnLoad 尾部, 确实调用了 sub_A4868, 但该函数并非检测函数

所以当前的情况是: patch绕过了线程检测函数, trace 可以完整跑完 JNI_OnLoad, 但 app 仍然会检测到 frida 从而崩溃, 且 trace 不能直接定位到具体崩溃点位, 即没有明确的崩溃函数/指令.
那么我们可以猜测: trace 的函数调用链中, 有某个/某些函数检测到 frida, 但没有直接 kill 进程, 而是交由其他函数/子线程/子进程执行, 从而避免位于主线程的调用链导致被 trace.
定位这个检测点可以使用 二分法 (设左右边界节点分别为 [left,right]):
根据 trace 结果, 选取合适的中间节点mid, hook 该节点并 sleep 一定时间
如果 sleep 没有完成便崩溃, 说明 [left,mid] 即中间节点前方有检测函数
如果 sleep 完成后才崩溃, 说明 [mid,right] 即中间节点后方有检测函数
如何选取合适节点? 目标节点尽量满足居中, 唯一, 防止同地址函数在不同节点的调用干扰分析.
另外每次二分 trace 时, 需要保留对应节点的 trace 结果, 和原始 trace 结果对比
以 174 行的节点 64ED8 为例 (该节点选取并不理想, 仅模拟直接分析踩坑情况, 前方 69 行也有该函数调用节点)

代码如下, 注意 sleep 时间不能太久, 防止系统 kill app, 5-10s 比较合适
Trace 结果如下, 可以看到在 69 行的 67ED8 函数命中了 hook, 并且完成了一次完整 sleep
后续又命中一次, 且启动 sleep 便崩溃, 说明检测点位于 69 行之后

下一个节点选取194 行的 17534, 该节点只有一次调用, 且内部调用函数较多, 比较可疑

注意此时应该注释前一个节点的 sleep
trace 结果如下, 完整sleep 一次, 说明检测点在 sub_17534 之后
同时可以发现 sleep end 后有一次 fork 调用, 可以猜测是 fork 的子进程进行了检测, 并 kill 了父进程

下一个节点选取 229行的 1AB90

此次有变化,多输出了几次 1AB90 调用, 但实际触发 hook 和 sleep 的位于末尾
说明检测点位于sub_17534~sub_1AB90
并且可以发现在 fork 调用后, 创建了几个线程, 之后命中 hook, app 崩溃

之后的二分 trace 是重复试验过程, 不再赘述, 明确指出检测点为 sub_2813C, 且不是 JNI_OnLoad 开头调用的那一个, 而是在 418 行, if 内 fork 的分支.

目测该函数的功能为扫描 cmdline 判断当前是否处于被调试状态
hook 该函数, 观察返回值可以发现:
所以 hook 后 replace retval = 3 即可绕过
成功绕过, 并且 trace 的目标函数JNI_OnLoad 完整结束

值得一提的是 trace 末尾的 2813C 并非检测点, 该函数共有 3 次调用, 其中第二次为检测点

对应的点位, 紧跟 sub_17534, 返回值为 0 时触发 fork 子进程并创建检测线程
bypass 后, if 分支外主进程继续执行其他函数, 所以 bypass 前后对应的 trace 结果会有不同

同上类似, 可以 hook dlopen, 扫描匿名可执行模块 dump 目标 so, 也可以静态脚本脱壳
hook clone定位检测线程函数
结果如下

patch这 7 个线程函数后再次启动, 会出现第 8 个

所以共有 8 个检测函数
同V3类似, 此处不再赘述重复 trace + 二分法定位, 实际上有了 V3 版本的结论, 可以通过特征码直接定位
对于该样本, 对应的目标函数为 sub_2DF48
结果如下: 成功 bypass sub_2DF48, 创建的线程函数有变化, 但app仍然崩溃, 说明有其他检测点

使用前文提到的 trace 插件, 启动 ida 导出脚本, 配置基本信息后开始 trace
结果如下, trace 结果提示由于持续时间限制(10s) 导致 root leave, 即 trace 终止

说明默认的 10s 不够用,可以适当增大
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 4天前
被东方玻璃编辑
,原因: 修复 dump 匿名模块脚本缺失问题