传统hook方案很成熟——找到目标函数入口,把前几个字节改成一条跳转指令,跳到你的 hook 函数里。处理完之后再跳回去执行原始逻辑。这就是 inline hook,几乎所有 hook 框架的基本套路。
你写好了 hook,注入进去,完美运行。函数被成功劫持,返回值被修改,一切如你所愿。
然后你发现目标应用闪退了。
翻日志,发现应用在启动后会做一轮"完整性自检"——它把自己 .text 段的每个字节读出来,算一遍 CRC,和编译时的预期值对比。你写入的那条跳转指令,就这样被发现了。
这不是什么罕见的对抗手段。很多加固方案、反作弊系统、甚至一些金融类 App 都会做类似的事:读自己的代码段,检查有没有被人动过。手段可以很简单(CRC、MD5),也可以很暴力(逐字节比对、mmap 后重新校验)。
你的 hook 面临一个尴尬的处境:它必须存在于代码段里才能生效,但只要它存在于代码段里就会被发现。
不只是 inline hook。调试器断点(BRK 指令)、热补丁(NOP 替换)、uprobe——任何需要修改代码页的技术,都面临同一个根本矛盾。
而基于硬件断点的无痕hook方法受限于寄存器数量,存在诸多局限性,本文提供的方法提供的无痕hook方式 不存在这种问题。
ARM64 的内存管理单元(MMU)负责把虚拟地址翻译成物理地址。翻译的依据是页表,页表的每一项(PTE)记录了两个关键信息:这个虚拟页对应哪个物理页帧,以及访问权限是什么。
通常,一个虚拟地址只对应一个物理页面。但如果我们能在不同的时机,让 PTE 指向不同的物理页面呢?
思路就是这么直接:
准备两个物理页面。 一个是原始代码的完整拷贝,在上面写入你需要的修改(跳转指令、BRK、NOP,随便什么)——叫它 shadow page。另一个就是原封不动的原始页面——叫它 original page。
然后,根据访问类型动态切换 PTE:
ARM64 的 PTE 里有三个和权限相关的关键位:
这里有一个关键的架构特性:ARM64 的指令 fetch 不检查 AP 位。
也就是说,即使你不设 AP[1](不允许 EL0 数据访问),用户态程序的 read() 会被拒绝,但 CPU 的指令 fetch 不受影响——只要 UXN 没有置 1,这个页面上的代码就能正常执行。
这就给了我们构造"仅执行"页面的能力:
有了这组权限编码,"仅执行"和"仅读取"就不再是概念,而是可以写进页表的具体比特位。
如果在虚拟化场景(Hypervisor / EL2),事情会更干净。Stage-2 页表使用一套完全独立的权限编码,数据访问和指令取指由两组互不干扰的比特位分别控制:
关键区别在于:Stage-1 里 AP 位同时影响数据和(间接地)取指;而 Stage-2 中 S2AP 只管数据路径,XN 只管取指路径,二者完全正交。
这意味着 S2AP=00, XN=00 就是一个干净的 execute-only 语义:CPU 能取指执行,但任何 LDR/STR 都会触发 Stage-2 Data Abort,陷入 EL2。
理解了权限编码之后,关键问题是:谁来触发切换?答案是——CPU 自己。
正常运行时,代码页处于 SHADOW_X 状态(PTE 指向 shadow page,权限 --x)。程序正常跑着,CPU 取指令一切正常。直到有人试图读这个页面——两个状态之间就开始来回切换:
整个过程对进程完全透明。它读到的永远是干净的原始代码,CRC、MD5、逐字节比对——全部通过。但实际执行的是 shadow page 上被修改过的指令。
切换通过 page fault 自动触发,不需要任何额外的定时器或轮询。读了就切到原始页,执行了就切回 shadow 页,像一个跷跷板。
除了用户态的 read(),还需要处理内核态的读取路径。/proc/pid/mem、process_vm_readv、ptrace 的 PEEKDATA 走的是 GUP(Get User Pages)路径,直接解析页表拿物理页,不触发 fault。对此,我们 hook GUP 的核心函数 follow_page_pte,在读取前临时将 PTE 指向 original page,读完立刻恢复,确保所有读取路径都拿到原始代码。
还有一点值得强调:R^X 的权限拆分仅发生在页表(PTE)层面,VMA 层面完全不受影响。 我们没有修改任何 vm_area_struct 的 vm_flags,所以进程通过 /proc/self/maps 看到的内存权限仍然是原始的 r-xp——不会突然变成 --xp 或 r--p。同理,mprotect 的查询、mincore、以及内核内部遍历 VMA 的逻辑,都看不到任何异常。权限的"跷跷板"只在硬件页表这一层悄悄发生,内核的内存管理子系统对此一无所知。
[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)
最后于 2026-3-16 23:44
被wx_唔.编辑
,原因: