对于got和hook的讨论
在 PWN 中,篡改 __free_hook 和 free@GOT 都是为了在程序执行堆内存释放操作时劫持控制流)。虽然目的相似,但它们在内存位置、受到的保护机制以及适用场景上有很大的区别。
那么我们应该如何对 got 和 hook 进行选择呢:以下我们以 free 为例去分析
@GOT Global Offset Table
GOT 表存放在 ELF 文件的.got.plt 数据段中。他的用处是记录主程序外部函数的真实地址,篡改他会改变主程序对相关函数(例如 free)的跳转调用,它仅影响主程序内部发起的 free 调用,地址和程序的基址相关。受 RELRO (Relocation Read-Only) 保护。
hook
hook 一般存在于 libc 的 .bss 或 .data 段中,是 glibc 提供给开发者自定义内存释放行为的钩子,我们通常也称之为指针,如果 __free_hook 不为空,当执行 free()的时候就会直接执行这个钩子。他主要影响包括主程序以及 libc 内部发起的任何 free 调用,其地址与 libc 基地址相关。
hook 主要受 glibc 版本限制,由于安全问题,在 glibc2.34 及以上版本已将其彻底移除。
那什么时候篡改什么比较合适?
选择篡改哪一个,主要取决于目标程序开启的安全保护(Checksec)以及你掌握的地址信息(Leak)。
选择篡改 free@GOT 的场景
- 程序未开启 Full RELRO (Partial RELRO 或 No RELRO): 这是绝对前提。如果开启了 Full RELRO,GOT 表在初始化后会被设置为只读,你将无法往里面写数据。
- 没有 Libc Leak,但能计算出程序基址: 如果 PIE 关闭,GOT 表地址是固定的;即使 PIE 开启,只要你能 leak 出程序本身的地址,就能算出
free@GOT的位置。你可以利用堆漏洞直接把 GOT 表里的地址改掉(例如改成程序代码段中的后门函数system_addr)。
一般的攻击思路是: 将 free@GOT 的内容覆盖为 system 的地址。当程序调用 free(chunk_ptr) 时,实际上执行了 system(chunk_ptr)。只要提前在 chunk 里写入 "/bin/sh",就能拿到 Shell。
选择篡改 __free_hook 的场景
- 程序开启了 Full RELRO: 此时 GOT 表不可写,此时也就无法去篡改
free@GOT。但 Full RELRO 保护不了 libc,__free_hook依然是可写的。 - 需要获取 Libc_base: 因为
__free_hook在 libc 当中,必须泄露 libc 的基址才能算出它的准确内存地址 - 目标环境 glibc 版本 < 2.34: 在较老的 Ubuntu(如 16.04, 18.04, 20.04)中非常通用。但在最新的系统中(Ubuntu 22.04+,glibc 2.34+),所有的 hook 变量都被移除了,意味着我们无法使用 hook。
攻击思路: 将 __free_hook 的内容覆盖为 system 的地址或 one_gadget 的地址。
- 如果覆盖为
system:同上,释放包含"/bin/sh"的 chunk。 - 如果覆盖为
one_gadget:在调用free时,就会直接触发one_gadget,此时即可获取 shell。
小结一下:
- 看 RELRO:
Full RELRO➔ 放弃free@GOT,选择__free_hook或者其他用法Partial RELRO||No RELRO➔free@GOT可写,两个都可以选。
- 看 Glibc 版本:
glibc >= 2.34➔ 放弃__free_hook,寻找其他目标。glibc < 2.34➔__free_hook是最香的攻击目标之一。
- 看 Leak:
- 只有程序地址 ➔ 攻击
free@GOT。 - 有 libc 地址 ➔ 攻击
__free_hook。
- 只有程序地址 ➔ 攻击
更新: 2026-04-23 16:01:09
原文: https://www.yuque.com/idcm/wnemg9/gbivc023i37grybn