安卓 - 基于异常的无痕Hook
言简意赅, 源代码地址: https://gist.github.com/fuqiuluo/c7a6468a6fcc056c51f874c34aab717e
核心原理
ARM64 页表项有一个 UXN(Unprivileged eXecute Never)位。将目标代码页的 PTE 设置 UXN=1 后,用户态执行该页任何指令都会触发 Instruction Abort(EC=0x20, DFSC=Permission Fault),陷入 EL1。
内核侧通过 inline hook do_mem_abort() 注册自定义 handler,在异常到达信号投递之前拦截。handler 检查 fault 地址是否属于已注册的断点页,是则修改 pt_regs->pc 重定向到预编译的 recomp(重编译)页,返回用户态后 CPU 从recomp 页继续执行。整个过程对用户态透明:没有 ptrace、没有 BRK 指令、没有信号投递。
重编译引擎
对目标页的全部 1024 条指令(4KB / 4 = 1024)做静态重编译:
两趟扫描
-
布局计算:遍历每条指令,分类并计算 expansion 大小,构建 offset_map[insn_idx] → output_offset 映射表
-
代码生成:根据分类结果 emit 重编译指令到 output buffer
指令分类与改写规则
关键设计:所有 PC-relative 指令都被改写为绝对寻址,保证在 recomp 页新位置上语义不变。
recomp 页以 VM_READ | VM_EXEC | VM_MIXEDMAP | VM_DONTDUMP 映射到目标进程用户空间,由 insert_vm_struct() 直接插入 VMA。
三种断点模式
BP_MODE_DBI(轻量断点,12 slot prefix)
slot 0: STP X30, X17, [SP, #-16]! 保存 LR 和 X17
slot 1: MOVZ X17, #magic 加载 handler-return magic
slot 2: STP XZR, X17, [SP, #-16]! push magic 帧
slot 3: LDR X30, [PC, #12] X30 = bp_addr(原始页地址)
slot 4: LDR X17, [PC, #16] X17 = handler
slot 5: BR X17 跳转 handler
slot 6-7: .quad bp_addr
slot 8-9: .quad handler
--- handler RET → bp_addr → UXN fault → fault handler 重定向 ---
slot 10: LDP XZR, X17, [SP], #16 pop magic
slot 11: LDP X30, X17, [SP], #16 恢复 LR 和 X17
slot 12+: 原始指令(重编译后)
handler 签名:void handler(void),可以读写寄存器但需自行保存。
BP_MODE_INSTRUMENT(全上下文,89 slot prefix)
slot 0: SUB SP, SP, #0x310 分配 784 字节栈帧
slot 1-17: STP/STR x0-x30 保存 31 个 GP 寄存器
slot 18-19: 保存原始 SP
slot 20-23: MRS/STR NZCV, FPSR 保存状态寄存器
slot 24-39: STP q0-q31 保存 32 个 SIMD 寄存器
slot 40: MOV X0, SP X0 = &WuwaInstrumentContext
slot 41-49: push magic + LR=bp_addr + BR handler
slot 50: pop magic
slot 51-66: 恢复 SIMD
slot 67-70: 恢复 NZCV/FPSR
slot 71-87: 恢复 GP
slot 88: ADD SP, SP, #0x310
slot 89+: 原始指令
handler 签名:void handler(WuwaInstrumentContext *ctx),可通过 ctx 读写所有寄存器(GP + SIMD + NZCV + FPSR),修改会反映到恢复后的执行。
BP_MODE_INLINE_HOOK(函数替换,4 slot prefix)
nearby: B handler 直接跳转,不碰任何寄存器
far: LDR X16, [PC, #8] 用 X16(不是 X17)
BR X16
.quad handler
不保存 LR,handler 看到原始调用者的 LR。适合函数级替换。
Fault Handler 调度逻辑
do_mem_abort() → bp_mem_abort_handler()
│
├─ 非用户态 or 非权限异常 → NOT_HANDLED
│
├─ dbi_find_by_page(tgid, PC & PAGE_MASK) → 未找到 → NOT_HANDLED
│
├─ Handler Return 检测(PC bp_addr 且 [SP+8] MAGIC)
│ └─ 重定向到 recomp 页 slot 10/50(restore 代码)
│
├─ Far-mode 跳板检测(X17 PC 且 [SP+8] FAR_MAGIC)
│ └─ 从栈帧读取真实目标 PC,恢复 X17
│
└─ 正常 translate:dbi_translate_pc(fault_pc) → recomp_pc
└─ 修改 pt_regs->pc = recomp_pc,返回用户态
Nearby vs Far 模式
recomp 页分配时用 get_unmapped_area(hint=原始页地址) 尝试就近分配:
-
Nearby(±120MB 内):outpage 分支直接用 B imm26(±128MB 范围),零寄存器污染
-
Far(超出范围):压 32 字节栈帧(saved X17 + magic + target PC),跳回原始 UXN 页触发 fault,fault handler 从栈帧取出真实目标地址
BP_FLAG_NO_NEARBY 可强制 far 模式.