Frida17.6轻量级Hook技术方案解析

admin 2026-01-22 00:12:52 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Frida17.6彻底重构AndroidHook:弃Zygote内agent,改用/proc/$pid/mem外部写295行payload,劫持setArgV0NativeArtMethod,握手后SIGSTOP,frida-core外部回滚,无需ptrace,根除system_server崩溃,需root且依赖符号与映射布局,spawngating能力保留,稳定性大幅提升。 综合评分: 92 文章分类: 移动安全,二进制安全,安全工具,漏洞分析,红队


cover_image

Frida17.6轻量级Hook技术方案解析

原创

非虫 非虫

软件安全与逆向分析

2026年1月21日 15:40 湖北

Frida17.6轻量级Hook技术方案解析

背景与变化

Frida 17.6 针对 Android 侧的稳定性做了根本性调整,核心变化集中在 Zygote 与 system_server 的处理方式。旧方案依赖向 Zygote 注入 frida-agent,再通过 child gating 观察 fork 过程并完成挂起,这条链路需要 ptrace 并在 Zygote 内长期驻留代码。新方案改为完全外部化的补丁与握手流程,避免 ptrace,避免在 Zygote 里常驻 agent,从根上降低了系统不稳定因素。

release notes 中明确指出两点:

  • 取消对 Zygote 和 system_server 的内部注入,消除最主要的不稳定来源。
  • 子进程挂起不再依赖 ptrace 的 syscall tracing,而是采用更轻量、专用的外部注入方式。

commit 3e778a99c066 对实现进行了完整重构,提交说明中直陈新方案要点:

  • 使用 /proc/$pid/mem 写入一个小型 payload。
  • 将 android.os.Process.setArgV0Native() 的 ArtMethod 指针替换到该 payload。
  • payload 作为代理继续调用原始 setArgV0Native,并通过 Unix 套接字向 frida-core 回连。
  • 完整流程在 Zygote 外部完成,不使用 ptrace。

旧方案的问题点

旧方案主要围绕 “在 Zygote 内注入 agent” 展开,为了实现 child gating 需要在 Zygote 中监控 fork 过程,典型问题包括:

  • 注入动作昂贵,需要暂停/恢复线程,并在所有子进程中留下痕迹。
  • 需要隐藏 FD,否则 Zygote 会因为异常状态主动 abort。
  • ptrace 的 syscall tracing 逻辑复杂且脆弱,容易在高并发启动阶段造成崩溃。
  • teardown 路径不安全,难以保证在各种时机正确回收。

这些问题叠加后,Zygote 与 system_server 变成最容易被 Frida 影响稳定性的两条路径。

新方案整体思路

新方案被称为 “light-weight Zygote hooking”,核心思路是将 hook 动作外置并变得短生命周期化:

  • 不注入 Zygote agent,而是通过 /proc/$pid/mem 写入 payload。
  • 利用 setArgV0Native 这一稳定入口作为触发点。
  • payload 的职责仅限于握手和暂停,不承担实际注入逻辑。
  • frida-core 在外部完成子进程恢复与回滚,避免在 Zygote 内留下常驻逻辑。

这个流程被实现为一个新的 “zymbiote” 机制,对应 payload 源码为 src/linux/helpers/zymbiote.c,编译成多架构二进制并嵌入到 frida-core 数据段中。

核心执行流程

流程描述以 Zygote/USAP 为入口,子进程触发后再回到 frida-core:

  • frida-core 枚举 zygote/usap 进程,准备注入补丁。

  • 读取 /proc/<pid>/maps,定位:

  • 可执行映射中的安全区间作为 payload 落点(17.6.0 为 /system/bin/app_process 末页,17.6.1 调整为更安全的映射)。

  • libc.so

    与 libandroid_runtime.so 以解析符号。

  • boot heap 区域,扫描 ArtMethod 槽位。

  • 在 boot heap 中搜索指向 setArgV0Native 的指针,得到 ArtMethod 槽位地址。

  • 将 payload 模板写入选定的可执行映射末页,并填充以下运行时信息:

  • Unix 抽象套接字名称。

  • ArtMethod 槽位地址与原始 setArgV0Native 地址。

  • socket/connect/__errno/getpid/getppid/sendmsg/recv/close/raise

    等 libc 函数地址。

  • 将 ArtMethod 槽位指针替换为 payload 地址。

  • Zygote fork 后调用 setArgV0Native,进入 payload。

  • payload 通过 Unix 套接字发送 hello 消息(pid、ppid、包名),等待 frida-core 的 ACK。

  • payload 触发 SIGSTOP,等待 frida-core 回滚与恢复。

  • frida-core 回滚子进程补丁并 SIGCONT,随后回滚 Zygote 补丁。

关键实现细节

外部注入与内存写入

实现完全依赖 /proc/$pid/mem 的读写权限。linux-host-session.vala 内新增 open_process_memory() 与补丁应用器 ZymbiotePatches,用于:

  • 在目标地址写入 payload。
  • 保存原始内存片段以便回滚。
  • 支持 “already_patched” 情况下从文件映射恢复原始内容。

payload 选址与布局

do_prepare_zymbiote_injection() 在 17.6.0 中选择 /system/bin/app_process 的可执行映射末页作为 payload 基址。17.6.1 改为放置在更安全的地址范围(例如 libstagefright.so 的可执行映射末页),以避免与 Zygote 使用的内存区间冲突。

payload 在构建阶段通过 helper.lds 的 .payload 段导出,并通过 objcopy -j .payload 生成最小二进制。

这一设计使 payload 可放置在已存在的可执行文件映射中,避免额外的内存映射和权限申请。

ArtMethod 槽位替换

setArgV0Native 的入口地址来自 libandroid_runtime.so 的符号导出表:

  • 解析 _Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring 获取该方法的 native 入口地址。
  • 在 boot heap 中扫描此地址指针,定位 ArtMethod 槽位。
  • 将槽位指针替换为 payload 地址,并缓存原指针用于回滚。

ArtMethod 槽位定位与替换是整个方案的关键点,也是无需 ptrace 的核心原因。

payload 逻辑与握手协议

payload 以极小的 C 代码实现,仅负责两件事:

  • 调用原始 setArgV0Native,保持系统行为一致。
  • 通过 Unix 套接字向 frida-core 报告新进程信息并进入暂停状态。

hello 消息格式包含:

  • pid

  • ppid

  • package_name

    长度与内容

frida-core 发送单字节 ACK 后,payload 触发 SIGSTOP。frida-core 接收 ACK 后完成回滚与恢复,这样 payload 在子进程内几乎不留下长尾副作用。

release notes 强调该 payload 仅约 295 行 C 代码,并包含极少量内联汇编用于尾调用,从设计上保证了最小化与可维护性。

补丁回滚与恢复

ZymbiotePatches 记录所有写入位置与原始数据,在以下时机回滚:

  • 子进程通过 ACK 进入 SIGSTOP 后,回滚该子进程的 ArtMethod 槽位与 payload 区域。
  • 处理完成后,再回滚 Zygote 进程的补丁。

这一设计确保 Zygote 的修改是短时的,不会长期污染后续 fork。

spawn gating 的新实现

旧方案的 child gating 基于注入 Zygote agent。新方案通过握手与 SIGSTOP 达到类似效果:

  • payload 在子进程内阻塞,直到 frida-core 发起 ACK。
  • frida-core 可选择立即恢复或进入 gating 状态。
  • gating 状态下,连接会被暂存并在外部触发恢复流程。

这使得挂起逻辑转移到外部控制面,减少 Zygote 内的复杂度与风险。

system_server 与 helper 的调整

release notes 明确说明 system_server 内部注入被移除。对应实现调整为:

  • frida-helper.dex

    成为共享的通用 helper。

  • helper 的 request 类型扩展,支持启动 Activity、发送广播等。

  • frida-core 不再依赖 frida-java-bridge,从而规避未来 libart 兼容性风险。

system_server 不再承载内部 agent,减少系统层面的冲突面。需要延长启动超时等功能时,可以通过独立脚本进行按需注入。

构建与交付变化

构建系统新增 zymbiote payload 的生成与嵌入流程:

  • zymbiote.c

    编译为多架构 zymbiote-*.bin

  • helper.lds

    将 .payload 段独立出来,便于 objcopy 直接裁剪。

  • meson.build

    使用 -j .payload 仅提取 payload 段。

这保证了 payload 最小化、可控、跨架构部署。

17.6.1 维护修正

17.6.1 主要解决 17.6.0 在部分设备上的崩溃与地址冲突问题,核心改动集中在 zymbiote payload 的放置与 arm64 的指令完整性:

  • payload 放置调整到更安全的可执行映射区间,避免与 Zygote 使用的内存范围重叠,同时保持在 fork 前回滚补丁的行为不变。
  • arm64 payload 以 BTI 构建,保证 setArgV0Native 的间接跳转进入 payload 时不触发 BTI 异常。

能力边界与注意事项

该方案对以下条件有明确依赖:

  • 需要能够读取/写入 /proc/<pid>/mem,要求 root 权限。

  • libandroid_runtime.so

    必须导出 setArgV0Native 符号或可匹配替代符号。

  • 需要在 boot heap 中成功找到 ArtMethod 槽位,失败即不可用。

  • payload 放置在 Zygote 进程中可执行映射的安全区间(17.6.0 为 app_process 末页,17.6.1 调整为更安全的映射),对系统映射布局有依赖。

这些限制与旧方案相比更明确、更可控,但也意味着在极端裁剪系统或非标准 ROM 上需要额外适配。

总结

Frida 17.6 的轻量级 Zygote hook 方案用外部内存补丁替代内部 agent 注入,以 setArgV0Native 作为统一的稳定入口,再通过极简 payload 完成握手与暂停。该设计显著降低了 Zygote 与 system_server 的侵入性,同时保留 spawn gating 能力,并让注入流程更易维护与推断。对于 Android 侧的稳定性改进,这一方案是结构性的升级。

参考Frida的全新Zygote的Hook原理,将其中的注入部分提取出来,做了一个android-setarg0-injector的注入器,效果还可以,会在后面课程中分享给大家!


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:软件安全与逆向分析 非虫 非虫《Frida17.6轻量级Hook技术方案解析》

评论:0   参与:  0