libmsaoaidsec.so 的 Frida 检测旁路与 SoInfo 阶段验证
1. 目的与边界
本文面向已获授权的隔离测试,说明如何在不破坏 NAOP 载荷与 soinfo 元数据写入的前提下,验证并旁路 libmsaoaidsec.so 中已还原的 Frida、ART Hook 与常规反调试守护。
本文只讨论本样本的 Loader/lib/arm64-v8a/libmsaoaidsec.so,不覆盖 APK 其他组件。建议使用副本、离线重签名的测试包和可回滚设备;不要在生产应用、第三方服务或未授权环境执行修改。
核心结论是:**soinfo 写入不是 Frida 检测的启动点。**它和检测线程同在外层 _init 阶段,但两者是独立分支。最小影响的方案应优先抑制监控线程的创建,而不是跳过 sub_95C8 的 soinfo 写入或直接改写公共失败桩。
2. 静态时间线
已确认的主进程控制关系如下:
System.loadLibrary("msaoaidsec")
-> _init 0x1472C
-> sub_25A48:受开关控制的 MAGISKTMP 前置门控
-> sub_1BEC4:主进程监控初始化
-> sub_1B924(0x1B924)
-> dlopen("libc.so") + dlsym("pthread_create")
-> sub_1CEF8
-> pthread_create(..., sub_1C544(0x1C544), PrettyMethod)
-> sub_13728(0x13728):DEX CRC、NAOP 解码与私有加载器引导
-> sub_23AD4:受开关控制的 x86 Dalvik 检查
-> sub_95C8:Android linker soinfo 元数据写入
-> 外层 JNI_OnLoad 0x13A4C
-> 解析并调用内嵌 NAOP 的 JNI_OnLoad
sub_1C544(0x1C544)之后每 4 秒执行 Frida/Hook 规则;常规反调试线程 sub_1B8D4(0x1B8D4)的周期最短为 2 秒。sub_95C8 的职责是将私有加载对象的地址、长度、动态表等元数据复制到当前库的 Android linker soinfo 记录。当前静态证据没有显示它创建 Frida 线程、读取 Frida 关键字或直接终止进程。
因此,不应把“Frida 在 soinfo 阶段开始”作为结论。正确的风险点是:一旦 _init 到达 sub_1BEC4,检测线程可能已经建立;在其后再用常规 Frida attach,可能在下一轮检查中被发现。
3. 文件地址与修改前提
外层 ELF 的首个 PT_LOAD 为:
| 属性 | 值 |
|---|---|
p_offset |
0x0 |
p_vaddr |
0x0 |
p_filesz / p_memsz |
0x37000 / 0x37000 |
| 权限 | R-X |
所以本文件中下列代码 RVA 均位于该段内,满足:
file_offset = p_offset + (RVA - p_vaddr) = RVA
这只适用于本版本外层库及 RVA < 0x37000 的代码点。修改前必须重新核对库 SHA-256:
38757CC75418654893B029D8C0410C4A08CC143DCBBF9D0D387736075BEC65D0
不要将载荷重建 ELF 的地址、进程运行时 load bias、IDA 显示地址和外层文件偏移混用。运行时地址应为:
runtime_address = module_load_bias + RVA
4. 规则与最小切断点
4.1 检测面
| 检测面 | 函数 | 周期/条件 | 已知处置 |
|---|---|---|---|
| Frida 线程名 | sub_1BFAC,0x1BFAC |
sub_1C544 每 4 秒 |
gum-js-loop 或 gmain 命中后 exit(0) |
| FD 目标 | sub_1C158,0x1C158 |
同上 | linjector 命中后 exit(0) |
| Agent ELF | sub_1C26C(0x1C26C)/ sub_1806C(0x1806C) |
同上 | /data/local/tmp + frida-agent SONAME 或 _AGENT_1.0 符号命中后 exit(0) |
| ART 入口跳板 | sub_25B30 / sub_26334 |
创建线程前及每 4 秒 | PrettyMethod 首指令为 0x58000050/0x58000051 后走 exit_group(0) |
| 常规反调试 | sub_1B8D4(0x1B8D4) |
最短每 2 秒 | TracerPid、跟踪关系、线程 T 异常后走失败桩 |
| 备用 ptrace 守护 | sub_1B380(0x1B380) |
sub_CAE8()!=249 时 |
fork/ptrace 父子守护;当前文件默认开关不满足 |
| 周期性内存 CRC | sub_8CAC(0x8CAC) |
受多个运行期值门控;3 秒 | 失配走失败桩 |
公共失败桩为 sub_234E0(0x234E0)、sub_260B0(0x260B0)、sub_269AC(0x269AC)。三者可在匿名可执行页中解码并执行 exit_group(0)。它们同时服务于完整性、调试和环境失败,不是 Frida 专用逻辑。
4.2 推荐的第一切断点:sub_1BEC4
推荐优先在 sub_1BEC4(0x1BEC4)入口做仅返回成功/无操作的实验性补丁,理由如下:
- 该函数位于
_init的主进程监控初始化链,早于 Frida 线程的创建。 - 它切断
sub_1B924 -> sub_1CEF8 -> sub_1C544,避免gum-js-loop、FD、agent ELF 和 ART 跳板规则开始轮询。 - 它同时避免保留的常规反调试守护在 attach 后因
TracerPid或线程暂停而结束进程。 - DEX CRC、NAOP 解码、私有链接、
soinfo写入和内嵌JNI_OnLoad不在该函数的业务职责内,应继续保留,以减少改变样本功能的范围。
补丁语义应是“函数立即返回且向调用者表示无监控初始化错误”;不要替换整个 _init,也不要改写 sub_95C8。实际返回寄存器及覆盖长度应在当前 IDB 中以函数原型和首个基本块为准。AArch64 中常见的最小替换语义是令返回值为零(如调用方使用该值)后立即返回;不要在未确认调用约定时盲目只写 ret。
4.3 第二选择:仅切断 Frida/ART 线程
若测试目标要求保留常规反调试线程,可把最小修改下沉至 sub_1CEF8(0x1CEF8):让它不调用解析出的 pthread_create,或让其创建回调不指向 sub_1C544。这会禁用 Frida/ART 轮询,但不会处理 sub_1B8D4 的 TracerPid 与线程停止态检查。
适用条件和限制:
| 情况 | 建议 |
|---|---|
| 只想确认 Frida 四类规则的影响 | 可只切断 sub_1CEF8,但不要暂停进程或附加调试器。 |
| 需要常规 Frida attach / spawn 观察 | 同时抑制 sub_1B8D4,或改用上层 sub_1BEC4 切断。 |
PrettyMethod 在测试中被 inline hook |
仅改线程名、路径或 FD 没有作用;要切断 sub_25B30/sub_26334 或不使用该类 ART 跳板。 |
| 运行期完整性 CRC 被启用 | 单独检查 sub_8CAC;它与 Frida 线程不是同一个门控。 |
4.4 叶子函数方案:用于定位而非首选
当需要保留线程以记录每条规则时,可以让下列谓词统一返回“未命中”:
0x1BFAC Frida 线程名
0x1C158 linjector FD
0x1C26C agent 映像/ELF 身份
0x25B30 启动前 PrettyMethod 检查
0x26334 周期 PrettyMethod 检查
这适合逐条验证,但不是稳定的首选方案:前三个函数命中后直接 exit(0),调用点和返回值语义必须逐个确认;ART 规则有启动前与周期两处;而常规反调试线程仍可独立触发。叶子方案适合作为“确定哪条规则命中”的诊断手段,不适合作为第一次旁路。
5. SoInfo 阶段的正确处理方式
5.1 保留 sub_95C8
sub_95C8(0x95C8)会临时把 soinfo 所在页改为可写,并按 Android 版本结构偏移复制元数据。过早跳过它可能造成:
- 私有映像与 Android linker 记录不一致;
- 之后的内部 CRC、加载器状态或符号观察结果改变;
- 不同 Android API 的模块枚举行为出现差异;
- 把“观察工具看不到模块”误判成“Frida 检测仍在生效”。
因此第一轮测试应保留 sub_95C8,仅切断 sub_1BEC4。这样可以单独回答两个问题:
- Frida 退出是否来自监控线程,而不是
soinfo写入; soinfo写入后,私有 NAOP 载荷和 OAID JNI 路线能否仍正常完成初始化。
5.2 模块定位不要依赖单一 linker 视图
soinfo 修改可能改变依赖 solist/dl_iterate_phdr 的工具视角。受控验证中应同时比对以下数据源:
| 数据源 | 用途 |
|---|---|
/proc/<pid>/maps |
以真实映射区间确定外层库、匿名 NAOP 派生映像及 load bias。 |
dl_iterate_phdr |
观察标准 linker 公布的模块与程序头。 |
sub_95C8 前后 soinfo 快照 |
判断哪些字段被复制、是否改变名称/地址/动态表视图。 |
内部加载器上下文 qword_49248 |
确认私有符号解析仍指向真实 NAOP JNI_OnLoad。 |
若 Frida 的模块 API 与 maps 结果不一致,先将其归为 soinfo/手工映射可见性差异,而不要把它当作某条 Frida 规则触发。此时应以 maps 的可执行映射和已知 RVA 计算运行时地址。
5.3 不依赖“改名”规避
本样本的 Frida 覆盖面有四层:线程、FD、映像 ELF 身份、ART 入口。仅修改 agent 文件名、移动文件路径或隐藏一个 FD 不能形成可靠旁路:
gum-js-loop/gmain仍能从任务 status 被发现;linjector是独立 FD 规则;- agent 检查读取 SONAME 与动态符号,不止使用路径名;
- 某些 hook 仍可能被 PrettyMethod 首指令规则发现。
对本样本,线程创建切断比伪装单一痕迹更可验证、影响更小,也更适合定位保护逻辑。
6. 建议的受控验证流程
阶段 A:离线基线
- 保存原始 APK、外层 SO 和重建 NAOP ELF 的 SHA-256。
- 在 SO 副本上只实施
sub_1BEC4的最小返回补丁,保留_init、sub_13728、sub_95C8、JNI_OnLoad与 NAOP 文件不变。 - 重新封装并仅在测试设备安装。若必须重签名,记录签名变化,因为 DEX CRC 绑定的是 DEX 内容而非 APK 签名,但其他未分析层可能还会检查签名。
阶段 B:启动与时间窗口验证
- 启动后立即记录
/proc/<pid>/maps、线程列表和日志时间线。 - 至少观察 5 秒:正常样本中 Frida 线程轮询为 4 秒,常规反调试可能在 2 秒内行动。
- 对比补丁前后:预期补丁后不应出现
sub_1C544所属工作线程,也不应在 attach 后因TracerPid自动退出。 - 验证
libmsaoaidsec.so本体、NAOP 匿名映射和内嵌JNI_OnLoad仍已完成。不能只以“进程没有退出”判定旁路正确。
阶段 C:功能回归
按最小业务面验证:
| 项目 | 预期 |
|---|---|
| 外层 DEX 门控 | 当前未修改 DEX 时通过。 |
| NAOP 映射与重定位 | 存在对应匿名/派生映射,内嵌 JNI_OnLoad 可解析。 |
soinfo 阶段 |
sub_95C8 仍执行;模块视图差异被记录而非简单屏蔽。 |
| OAID SDK JNI | 载荷 0x14330 已初始化;需要调用方显式调用 0x37C0C 才发生 OEM 采集。 |
| 附加观察 | attach 后超过 4 秒不因 Frida 本地规则退出;超过 2 秒不因普通 TracerPid 规则退出。 |
阶段 D:必要时逐条回退
若上层 sub_1BEC4 旁路造成意外回归,按以下顺序缩小影响面:
- 恢复
sub_1BEC4,仅禁用sub_1CEF8,确认是否只有 Frida/ART 线程需要旁路。 - 若 attach 仍退出,临时抑制
sub_1B8D4,确认是否为TracerPid/暂停线程路径。 - 仅为定位目的,逐项令
sub_1BFAC、sub_1C158、sub_1C26C、sub_25B30、sub_26334返回未命中。 - 最后才检查受运行期开关控制的
sub_8CAC完整性线程;不要先改公共失败桩。
每次只改一个层级并保留 maps、线程和日志证据。否则无法区分 Frida 规则、常规反调试、DEX CRC、内存 CRC 或链接器状态变化。
7. 不建议的修改点
| 修改点 | 不建议原因 |
|---|---|
整个 _init |
会同时跳过 DEX 完整性、NAOP 建立、私有链接与 linker 处理,无法再分析真实载荷行为。 |
sub_95C8 |
与 Frida 规则无直接静态调用关系,可能破坏手工加载器与系统 linker 的一致性。 |
sub_234E0 / sub_260B0 / sub_269AC |
公共失败桩覆盖多种真实错误;改为 no-op 会掩盖 DEX/ELF/内存完整性故障。 |
| 只改 agent 文件名或路径 | 线程名、FD、SONAME/符号及 ART 规则仍可能触发。 |
只切断 sub_1C544 |
普通 TracerPid/线程状态守护仍可能在 attach 时退出。 |
| 使用暂停式调试验证 | 暂停线程状态 T 本身是检测面,结果不可解释。 |
8. 结论与待确认项
对于本版本,最合理的旁路顺序是:
优先:抑制 sub_1BEC4 的监控初始化
保留:sub_13728、sub_95C8、JNI_OnLoad、NAOP 解码/链接
验证:maps + 线程 + 超过 2/4 秒的 attach 行为 + OAID 载荷初始化
回退:只在需要归因时逐条抑制 sub_1CEF8 / 叶子检测函数
这能将“Frida 检测”与“soinfo 元数据写入”解耦验证,并最大限度保持外层壳的真实加载路径。仍需动态确认的事项包括:sub_1BEC4 的精确返回值约定、sub_95C8 对每个 Android API 的最终字段写入、运行期 CRC 开关是否被载荷设置,以及不同 Frida 版本产生的线程/FD/agent 映像差异。
评论
- 还没有评论,来说点什么吧。