mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2670 字
7 分钟
蜘蛛断点:一个运行在 Android 内核里的 ARM64 硬件断点 KPM 模块

蜘蛛断点:一个运行在 Android 内核里的 ARM64 硬件断点 KPM 模块#

如果你也折腾过 Android 上的运行时分析,大概会有过类似的经历:想看某个函数什么时候被调用、某个内存地址被谁读写了、某个系统调用返回值能不能偷偷改掉——但用户态的 ptrace 太重,Frida 又容易被检测,而且都进不了真正的内核上下文。

于是我写了一个内核模块:蜘蛛断点(Spider HWBreak)。它基于 KernelPatch 的 KPM 机制,直接运行在 ARM64 的 EL1 特权级,通过操作 CPU 自带的硬件调试寄存器(Hardware Breakpoint / Watchpoint)来实现断点和监视点。本篇介绍它的设计、能力,以及怎么用。

它到底是什么#

一句话:蜘蛛断点是一个 ARM64 EL1 硬件断点 / 硬件监视点 KPM 模块。

项目
模块名蜘蛛断点
版本1.10.68
产物arm64_hwbreak.kpm
KernelPatch0.13.2
构建Windows 本机(Android NDK)或 Ubuntu

硬件断点(Breakpoint)和硬件监视点(Watchpoint)是 ARMv8 架构里 CPU 原生支持的调试能力:你往 DBGBVR/DBGBCR(断点值/控制)或 DBGWVR/DBGWCR(监视点值/控制)这组系统寄存器里写入地址和控制位,当 CPU 执行到那个地址、或读写那个内存范围时,硬件就会主动抛出一个 debug 异常。它不需要改代码、不需要插桩、对目标执行路径几乎没有侵入,这正是它相比软件方案最大的优势。

蜘蛛断点做的事情,就是把这个底层能力封装成一个可被 kpatch 命令行驱动的内核模块:配置槽位、接管 debug 异常、记录命中现场、按需采集寄存器/内存/字符串/Binder,甚至改写返回值。

适用场景#

  • 本地调试:在没有源码、不方便上 GDB 的环境下定位某段逻辑。
  • 逆向分析:追踪某个地址的执行流、某块内存的读写来源与方式。
  • 运行时定位:确认「这个函数到底有没有被调到」「这个字段被谁改了」。
  • 行为干预(慎用):临时改掉某个返回值或寄存器,验证假设。

⚠️ 它是 root / 内核级工具,会改变系统运行行为,只建议在自己拥有且可控的设备上做研究,风险分层见文末。

整体架构#

模块的目录很清晰,核心逻辑分散在 src/ 下,各司其职:

文件职责
main.cKPM 入口、命令分发、版本号
slots.c断点槽位管理、命中处理、rearm / limit 策略
debug_chain.cdebug 异常链的 hook 与接管
hwreg.cARM64 调试寄存器的读写、MDSCR 开关
step.c单步恢复与 fastforward 路径
actions.c命中后的寄存器 / 内存 / 字符串 / Binder 采集与改写
insn.cARM64 load/store 指令解码(读写反查)
smp_sync.c多 CPU 同步、online mask 变化自动补齐
events.c命中事件环形缓存(ring buffer)

1. 接管内核的 debug 异常链#

模块启动后通过 hook 命令,用 KernelPatch 的 hook_wrap3 包裹内核的:

  • breakpoint_handler —— 执行断点命中
  • watchpoint_handler —— 数据监视点命中
  • single_step_handler —— 单步恢复
  • hw_breakpoint_thread_switch —— 线程切换时把活跃槽位恢复到当前 CPU

关键点在于「取舍」:before_breakpoint_handler / before_watchpoint_handler 里会判断这次异常是不是本模块配置的槽位触发的,只吞掉自己识别的硬件断点,其余 debug 异常原样交还内核处理,避免和内核自带的 hw_breakpoint 框架打架。

2. 直接操作调试寄存器#

hwreg.c 是所有能力的地基。它把 16 组调试寄存器用 READ_SYSREG/WRITE_SYSREG 宏封装,并负责:

  • 通过 ID_AA64DFR0_EL1 探测本机实际的 BRPs(断点数量)和 WRPs(监视点数量);
  • 构造控制字:监视点用 BAS 位按 8 字节窗口编码访问范围,断点用 4 字节对齐;
  • 通过内核 enable_debug_monitors / disable_debug_monitors 正确开关 MDSCR_EL1MDE 位(真正的「总开关」)。

3. 事件环形缓存#

每次命中都会在 events.c 的 ring buffer 里落一条结构化记录,包含:事件序号、纳秒时间戳、CPU、槽位、类型、FARPCPSTATEESR、命中次数、任务的 pid/tgid/comm、watchpoint 访问方向、指令解码结果,以及该槽位配置的各种采集动作输出。后续用 events:all 一次性读出,或者配合 Host 脚本导出。

断点类型与作用域#

类型含义长度
x执行断点固定 4 字节
r读监视点1 / 2 / 4 / 8 字节
w写监视点1 / 2 / 4 / 8 字节
rw读写监视点1 / 2 / 4 / 8 字节

作用域(scope)决定了断点在哪个特权级生效:

  • el1:仅内核态(默认)。注意它不包含普通 APP 的 EL0 访问。
  • el0:仅用户态(普通 APP 的地址用它)。
  • all:EL0 + EL1 同时监视。

监视点允许非对齐地址,但监视范围必须留在同一个 8 字节窗口内,且同一窗口同一时刻只能被一个数据监视点占用——模块在 stage 时会主动检查窗口冲突。

命中后的采集与改写#

这是模块最有意思的部分。命中之后,除了基础现场,你还可以给槽位挂各种「动作」:

只读采集(不改运行逻辑):

  • regs:打印 x0~x30sppcpstate
  • simd:记录 SIMD/浮点状态
  • peek:读 reg + offset 处的 64 位值
  • mem:dump reg + offset 开始的用户态内存(最多 128 字节)
  • memabs:dump 绝对用户态地址(最多 128 字节)
  • code:从 pc - beforebefore + after 字节的代码片段
  • str:从 reg + offset 读 NUL 结尾字符串
  • binder:解析 binder_write_read,提取 BC_TRANSACTION / BC_REPLY 基础字段

改写动作(会改变运行结果,慎用):

  • lock:watchpoint 命中后,把访问相关的寄存器改成锁定值。模块会解码当前 PC 的 ARM64 LDR/STR unsigned immediate 指令——读操作直接把目标寄存器替换为锁定值并跳过本次 load,写操作把参与写入的寄存器值改掉。它不是后台持续扫描内存,只在命中瞬间介入。
  • ret:执行断点命中后,把 x0 设为指定值并直接返回。
  • setreg:执行断点命中后写一个通用寄存器(拒绝写 pstate)。

采集动作只对 EL0 用户态上下文有意义(内核态槽会拒绝),这也符合「用户态内存才能被安全地读」的约束。

读写反查:这次命中到底是谁干的#

数据监视点最麻烦的是「知道被访问了,但不知道是谁访问的」。蜘蛛断点在 EL0 watchpoint 命中时会尝试解码触发点的 ARM64 load/store 指令,把这些信息一并打印出来:

  • 指令机器码、访问大小、目标寄存器、基址寄存器、偏移
  • 计算后的地址、访问值
  • 如果是 pair 指令,还有第二地址 / 第二值
  • register offset 的索引寄存器和 shift

换句话说,你能直接看到「0x7100a31aa8 这个地址,是被 ldr x3, [x8, #0x10] 在读,读出来的值是 0x1234」。这对逆向定位读写来源极有帮助。

过滤与策略#

真实场景里监视点可能高频触发,所以模块提供了一组收敛手段:

  • pid / comm:只记录指定进程或 comm 名的命中。
  • policy
    • oneshot-cpu——每个 CPU 单次命中后自动 rearm(适合「统计每个核各命中几次」)。
    • oneshot-global——全局只命中一次。
  • limit:达到指定命中次数后不再 rearm,相当于「只观察前 N 次」。

所有槽位默认是 oneshot-cpu 策略,命中即 trip(关闭 enable 位),需要时手动或自动 rearm。模块还维护了一个软件状态机(slots.c),并对多 CPU 做了 sync / verify / 自动补齐——发现 CPU online mask 变化(比如大小核调度、热插拔)会自动把活跃槽位重写到新上线的核。

自测#

模块内置了一组自测,方便在真机上快速验证功能是否正常工作:

selftest:addr # 地址相关自测
selftest:data # 数据相关自测
selftest:call # 调用路径自测
selftest:read # 读监视点自测
selftest:write # 写监视点自测
selftest:rw # 读写监视点自测
selftest:all # 全部核心功能自测
selftest:stress # 压力测试

构建与部署#

Windows 本机构建#

现在不需要 Ubuntu,直接在 Windows 上用 Android NDK 就能编译(默认用 NDK 27.1.12297006):

Terminal window
cd C:\Users\77006\Desktop\Code\31_Android_KPM_Arm64_HwBreak
powershell -ExecutionPolicy Bypass -File .\build-windows.ps1 -Clean

产物是根目录下的 arm64_hwbreak.kpm

推送到手机#

只使用 /data/adb 目录,不碰 /data/local/tmp

Terminal window
adb shell "su -c 'mkdir -p /data/adb/kpms && chmod 700 /data/adb/kpms'"
adb push .\arm64_hwbreak.kpm /data/adb/kpms/arm64_hwbreak.kpm
adb shell "su -c '/data/adb/modules/KPatch-Next/bin/kpatch kpm load /data/adb/kpms/arm64_hwbreak.kpm'"
adb shell "su -c '/data/adb/modules/KPatch-Next/bin/kpatch kpm ctl0 蜘蛛断点 status'"

一个最小工作流示例#

# 1. 安装 hook,接管内核 debug 异常链
kpatch kpm ctl0 蜘蛛断点 hook
# 2. 在用户态地址 0x7100a31aa8 上布置一个 8 字节读写监视点
kpatch kpm ctl0 蜘蛛断点 replace:0:rw:0x7100a31aa8:8:el0
# 3. 打开寄存器、代码片段采集
kpatch kpm ctl0 蜘蛛断点 regs:0:on
kpatch kpm ctl0 蜘蛛断点 code:0:32:96
# 4. 启用并等待命中
kpatch kpm ctl0 蜘蛛断点 enable:0
# 5. 读取命中事件
kpatch kpm ctl0 蜘蛛断点 events:all
# 6. 用完清理
kpatch kpm ctl0 蜘蛛断点 disable:all
kpatch kpm ctl0 蜘蛛断点 clear:all
kpatch kpm ctl0 蜘蛛断点 unhook

风险与安全提示#

模块对动作做了明确的风险分层,使用时心里要有数:

风险功能
status events dump verify sync pid comm policy limit
低~中regs peek mem memabs code str(只读采集)
binderr/w/rw 高频监视点(异常开销大)
lock(改写访问值)
ret setreg(改写返回值/寄存器,改变运行结果)

只读采集一般不改变用户态逻辑;lock / ret / setreg 会直接改变运行结果;高频监视点会带来明显的异常处理开销。调试完记得 disable/clear/unhook 一键归位。

还有一套 Host 工具链#

除了内核模块本身,仓库里还附了一组 Windows 侧 PowerShell 脚本,把「采集 → 标注 → 导出」串成工作流:

  • hwbp_host.ps1:事件解析、/proc/<pid>/maps 标注、JSONL/CSV 输出
  • annotate_events.ps1:给事件 PC / FAR 标注模块与偏移
  • break_module_offset.ps1:按「进程名 + 模块名 + offset」自动换算 ASLR 地址并布点
  • load_profile.ps1 / watch_profile.ps1:从 JSON profile 批量配置、循环监听、自动 rearm 导出
  • export_trace.ps1:导出 IDA / Ghidra bookmark CSV、trace summary
  • export_binder_trace.ps1 / analyze_app_scan_trace.ps1 / export_capture_blobs.ps1:各种专项导出

可导出格式包括 text、JSONL、CSV、IDA/Ghidra 书签、Binder CSV、app scan CSV,以及把 mem/code/binder 原始字节导出成 .bin


蜘蛛断点是我近段时间折腾 Android 内核调试的一个小结。它把 ARM64 硬件调试能力从「要看一大堆寄存器手册」变成了「几条命令就能用」,对本地调试和逆向分析确实好用。后续如果有人感兴趣,我可以单独写一篇讲讲 lock 的指令解码实现,或者 Host 脚本如何对接 IDA/Ghidra。

项目基于 KernelPatch 0.13.2,源码与构建脚本在本地工程 31_Android_KPM_Arm64_HwBreak 中。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

蜘蛛断点:一个运行在 Android 内核里的 ARM64 硬件断点 KPM 模块
https://zhizhu-blog.pages.dev/posts/spider-hwbreak/
作者
蜘蛛
发布于
2026-07-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录