溺的文档
libmsaoaidsec · 第 2 篇 / 共 2 篇

libmsaoaidsec.so 的 Frida 检测旁路与 SoInfo 阶段验证

2026-07-28 · 阅读 3

1. 目的与边界

本文面向已获授权的隔离测试,说明如何在不破坏 NAOP 载荷与 soinfo 元数据写入的前提下,验证并旁路 libmsaoaidsec.so 中已还原的 Frida、ART Hook 与常规反调试守护。

本文只讨论本样本的 Loader/lib/arm64-v8a/libmsaoaidsec.so,不覆盖 APK 其他组件。建议使用副本、离线重签名的测试包和可回滚设备;不要在生产应用、第三方服务或未授权环境执行修改。

核心结论是:**soinfo 写入不是 Frida 检测的启动点。**它和检测线程同在外层 _init 阶段,但两者是独立分支。最小影响的方案应优先抑制监控线程的创建,而不是跳过 sub_95C8soinfo 写入或直接改写公共失败桩。

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_1C5440x1C544)之后每 4 秒执行 Frida/Hook 规则;常规反调试线程 sub_1B8D40x1B8D4)的周期最短为 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_1BFAC0x1BFAC sub_1C544 每 4 秒 gum-js-loopgmain 命中后 exit(0)
FD 目标 sub_1C1580x1C158 同上 linjector 命中后 exit(0)
Agent ELF sub_1C26C0x1C26C)/ sub_1806C0x1806C 同上 /data/local/tmp + frida-agent SONAME 或 _AGENT_1.0 符号命中后 exit(0)
ART 入口跳板 sub_25B30 / sub_26334 创建线程前及每 4 秒 PrettyMethod 首指令为 0x58000050/0x58000051 后走 exit_group(0)
常规反调试 sub_1B8D40x1B8D4 最短每 2 秒 TracerPid、跟踪关系、线程 T 异常后走失败桩
备用 ptrace 守护 sub_1B3800x1B380 sub_CAE8()!=249 fork/ptrace 父子守护;当前文件默认开关不满足
周期性内存 CRC sub_8CAC0x8CAC 受多个运行期值门控;3 秒 失配走失败桩

公共失败桩为 sub_234E00x234E0)、sub_260B00x260B0)、sub_269AC0x269AC)。三者可在匿名可执行页中解码并执行 exit_group(0)。它们同时服务于完整性、调试和环境失败,不是 Frida 专用逻辑。

4.2 推荐的第一切断点:sub_1BEC4

推荐优先在 sub_1BEC40x1BEC4)入口做仅返回成功/无操作的实验性补丁,理由如下:

  1. 该函数位于 _init 的主进程监控初始化链,早于 Frida 线程的创建。
  2. 它切断 sub_1B924 -> sub_1CEF8 -> sub_1C544,避免 gum-js-loop、FD、agent ELF 和 ART 跳板规则开始轮询。
  3. 它同时避免保留的常规反调试守护在 attach 后因 TracerPid 或线程暂停而结束进程。
  4. DEX CRC、NAOP 解码、私有链接、soinfo 写入和内嵌 JNI_OnLoad 不在该函数的业务职责内,应继续保留,以减少改变样本功能的范围。

补丁语义应是“函数立即返回且向调用者表示无监控初始化错误”;不要替换整个 _init,也不要改写 sub_95C8。实际返回寄存器及覆盖长度应在当前 IDB 中以函数原型和首个基本块为准。AArch64 中常见的最小替换语义是令返回值为零(如调用方使用该值)后立即返回;不要在未确认调用约定时盲目只写 ret

4.3 第二选择:仅切断 Frida/ART 线程

若测试目标要求保留常规反调试线程,可把最小修改下沉至 sub_1CEF80x1CEF8):让它不调用解析出的 pthread_create,或让其创建回调不指向 sub_1C544。这会禁用 Frida/ART 轮询,但不会处理 sub_1B8D4TracerPid 与线程停止态检查。

适用条件和限制:

情况 建议
只想确认 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_95C80x95C8)会临时把 soinfo 所在页改为可写,并按 Android 版本结构偏移复制元数据。过早跳过它可能造成:

  • 私有映像与 Android linker 记录不一致;
  • 之后的内部 CRC、加载器状态或符号观察结果改变;
  • 不同 Android API 的模块枚举行为出现差异;
  • 把“观察工具看不到模块”误判成“Frida 检测仍在生效”。

因此第一轮测试应保留 sub_95C8,仅切断 sub_1BEC4。这样可以单独回答两个问题:

  1. Frida 退出是否来自监控线程,而不是 soinfo 写入;
  2. 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:离线基线

  1. 保存原始 APK、外层 SO 和重建 NAOP ELF 的 SHA-256。
  2. 在 SO 副本上只实施 sub_1BEC4 的最小返回补丁,保留 _initsub_13728sub_95C8JNI_OnLoad 与 NAOP 文件不变。
  3. 重新封装并仅在测试设备安装。若必须重签名,记录签名变化,因为 DEX CRC 绑定的是 DEX 内容而非 APK 签名,但其他未分析层可能还会检查签名。

阶段 B:启动与时间窗口验证

  1. 启动后立即记录 /proc/<pid>/maps、线程列表和日志时间线。
  2. 至少观察 5 秒:正常样本中 Frida 线程轮询为 4 秒,常规反调试可能在 2 秒内行动。
  3. 对比补丁前后:预期补丁后不应出现 sub_1C544 所属工作线程,也不应在 attach 后因 TracerPid 自动退出。
  4. 验证 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 旁路造成意外回归,按以下顺序缩小影响面:

  1. 恢复 sub_1BEC4,仅禁用 sub_1CEF8,确认是否只有 Frida/ART 线程需要旁路。
  2. 若 attach 仍退出,临时抑制 sub_1B8D4,确认是否为 TracerPid/暂停线程路径。
  3. 仅为定位目的,逐项令 sub_1BFACsub_1C158sub_1C26Csub_25B30sub_26334 返回未命中。
  4. 最后才检查受运行期开关控制的 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 映像差异。

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。