PLT Hook,全称是 Procedure Linkage Table Hook,是 Native 层最经典、最稳定的 Hook 技术之一。它不直接修改函数指令,而是通过修改 ELF 文件中的 GOT 表,来"偷梁换柱",实现对外部函数调用的拦截。
下面我们结合与 Inline Hook 的对比,从底层原理到代码落地,把它彻底讲透。
🎯 为什么选择 PLT Hook?
在 Native Hook 的众多流派中,PLT Hook 因其独特的优势和局限性,有自己最擅长的应用场景。
| 特性维度 | PLT Hook (GOT/PLT Hook) | Inline Hook |
|---|---|---|
| 核心原理 | 修改GOT表中的函数地址 | 修改函数前几条指令,写入跳转代码 |
| 实现复杂度 | 较低。不涉及指令修复,逻辑清晰 | 极高。需处理指令重定位、多架构兼容、指令缓存等 |
| 稳定性 | 极高。业界公认成熟稳定,可部署到生产环境 | 相对较低。易出现兼容性问题 |
| Hook范围 | 仅能拦截对动态库的外部调用 (如fopen、pthread_create) |
可以 Hook 任意函数,包括内部函数和静态函数 |
| 典型应用 | I/O监控、网络监控、线程监控(如 Matrix、xHook) | 复杂的性能监控、游戏外挂、逆向工程 |
简单来说,如果你的目标是监控应用调用了哪些系统 API(如文件读写、网络请求、线程创建),PLT Hook 是首选,因为它足够简单和安全。
⚙️ 核心原理:从ELF到GOT的"移花接木"
PLT Hook 的整个流程,依赖于 Linux 系统动态链接的实现机制。我们可以把它的工作流程拆解为三步:

- 第一步:查找目标函数的"门牌号" (GOT表偏移) 在 ELF 文件中,
.rel.plt节(重定位表)专门记录了所有需要重定位的外部函数信息。通过readelf -r命令,我们可以找到目标函数(如fwrite)在 GOT 表中的偏移量,例如0x2FE0。 - 第二步:定位运行时的"真实地址" (基址 + 偏移) 当 SO 被加载到内存后,它的起始地址(基址)是动态变化的。我们可以通过读取
/proc/<pid>/maps文件,找到目标 SO 的基址。 运行时函数GOT地址 = 基址 + 偏移量 。这个地址里,存放的就是真正fwrite函数的地址。 - 第三步:执行"移花接木" (修改GOT)
- 权限修改 :代码段通常是只读的。我们需要先调用
mprotect将包含目标 GOT 项的内存页权限改为可写(PROT_READ | PROT_WRITE)。 - 地址替换 :直接向计算出的 GOT 地址中,写入我们自定义的 Hook 函数(如
my_fwrite)的地址。 - 缓存同步 :ARM 架构有指令缓存,为确保 CPU 读取到最新的指令,需调用
__builtin___clear_cache清除缓存。
- 权限修改 :代码段通常是只读的。我们需要先调用
完成这三步后,当程序执行流通过 PLT 跳转到 GOT 表获取 fwrite 地址时,拿到的就是 my_fwrite 的地址,从而成功进入我们的 Hook 函数。
💻 实战代码要点
虽然实际项目中更推荐直接使用成熟的框架(如爱奇艺的 xHook 、微信 Matrix 的 ELF Hook 或 Facebook 的 PLT Hook),但理解其核心代码有助于你更好地驾驭它们。一个典型的 PLT Hook 核心操作如下:
c
// 假设已通过maps解析出目标函数在GOT表中的运行时地址:got_addr
// 假设自定义的Hook函数为:my_function
// 1. 修改内存页权限
uintptr_t page_start = PAGE_START((uintptr_t)got_addr);
mprotect((void*)page_start, PAGE_SIZE, PROT_READ | PROT_WRITE);
// 2. 替换GOT表项 (got_addr中存储的就是原函数地址)
void** got_item = (void**)got_addr;
void* original_function = *got_item; // 如果需要原函数,先保存
*got_item = (void*)my_function; // 替换为Hook函数
// 3. 清除指令缓存
__builtin___clear_cache((void*)page_start, (void*)PAGE_END(got_addr));
💡 总结
PLT Hook 之所以经典,是因为它巧妙地利用了系统已有的动态链接机制。它不是侵入式地修改代码,而是"欺骗"了程序查找函数地址的过程,因此稳定性极高。在 Android 性能优化的战场上,它是你监控 I/O、网络、线程等系统调用的"特种部队",精准且可靠。