【学习记录】Week12(二):House of Pig——Tcache 与 FSOP 的终极复合利用
写在前面:在上一篇中,我们掌握了 House of Botcake,成功在现代 glibc (2.29+) 中制造了堆重叠并实现了 Tcache Poisoning。然而,在 glibc 2.34 中,官方彻底移除了
__free_hook和__malloc_hook。这就意味着,即便我们通过 Tcache Poisoning 拿到了任意地址写的原语,也面临着“写到哪、写什么才能劫持控制流”的困境。今天,我们将学习现代 CTF 中的另一大杀器——House of Pig。它将 Tcache 的分配能力与 IO_FILE (FSOP) 完美融合,在无 Hook 的时代为我们撕开通往 Shell 的突破口。
📑 目录
- 时代背景:Hook 没落与 FSOP 的崛起
- 核心跳板:
_IO_str_overflow源码剖析 - House of Pig 利用流程与数学推导
- 实战进阶:与 House of Botcake 的经典组合拳
- 总结与下篇预告
1. 时代背景:Hook 没落与 FSOP 的崛起
在 glibc 2.34 之前,堆漏洞利用的终点往往是覆盖 __free_hook 为 system,然后 free("/bin/sh")。随着 Hook 被移除,攻击者必须寻找新的“控制流劫持点”。
此时,FSOP (File Stream Oriented Programming) 成为了主流。FSOP 的核心是伪造 IO_FILE_plus 结构体,并通过某种方式触发 glibc 的 IO 流刷新机制(如调用 exit() 或触发 malloc_printerr)。在刷新过程中,glibc 会遍历 _IO_list_all 链表,调用每个 FILE 结构体虚表中的 overflow 函数。
然而,glibc 2.24 引入了 vtable 地址范围检查,我们不能再随意伪造一个虚表指针指向栈上的 shellcode。我们必须寻找合法 vtable 中的危险函数。House of Pig 利用的正是 _IO_str_jumps 这个合法虚表中的 _IO_str_overflow 函数。
2. 核心跳板:_IO_str_overflow 源码剖析
_IO_str_overflow 是 glibc 中处理字符串流缓冲区溢出的函数。当写入的数据超过当前缓冲区大小时,它会尝试分配一个更大的缓冲区。其核心 C 代码逻辑(简化后)如下:
int _IO_str_overflow (_IO_FILE *fp, int c) {
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES) // 必须可写
return flush_only ? 0 : EOF;
// 计算当前写入位置
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only)) {
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else {
// 核心逻辑:分配新缓冲区
char *new_buf;
char *old_buf = fp->_IO_buf_base; // 旧缓冲区
size_t old_blen = _IO_blen (fp); // 旧缓冲区长度
// 计算新长度,通常是 old_blen * 2 + 100
size_t new_size = 2 * old_blen + 100;
// 【漏洞点 1】调用 malloc,大小受我们控制!
new_buf = malloc (new_size);
if (new_buf == NULL) {
// 分配失败的处理...
}
// 复制旧数据到新缓冲区
memcpy (new_buf, old_buf, old_blen);
// 【漏洞点 2】调用 free,参数受我们控制!
if (old_buf != NULL)
free (old_buf);
// 更新 FILE 结构体的指针...
}
}
// ...
}
关键发现:
在这个函数中,malloc 的参数 new_size 由 _IO_buf_end - _IO_buf_base 计算得出,而 free 的参数 old_buf 就是 _IO_buf_base。
如果我们能伪造一个 IO_FILE_plus 结构体,并让其 vtable 指向 _IO_str_jumps,我们就能控制 malloc 的大小和 free 的参数!
3. House of Pig 利用流程与数学推导
House of Pig 的核心思想是:利用 Tcache Poisoning 劫持 malloc 的分配流,使得 _IO_str_overflow 中的 malloc(new_size) 返回我们想要的目标地址(如栈地址或 _IO_list_all 本身),然后再利用 free(old_buf) 触发执行。
3.1 构造伪造的 FILE 结构体
为了顺利走到 malloc 和 free,我们需要精心设置 IO_FILE_plus 的字段:
_flags: 设置为0(可写,且绕过_IO_USER_BUF检查)。_IO_write_base: 设置为0。_IO_write_ptr: 设置为1(保证pos >= _IO_blen(fp)触发扩容逻辑)。_IO_buf_base: 设置为"/bin/sh\x00"的地址 (作为free的参数,如果是覆盖__free_hook时代的变体则用此法;在无 Hook 时代,通常设为旧缓冲区地址,配合栈迁移)。_IO_buf_end: 设置为0。这样old_blen = 0 - _IO_buf_base,new_size = 2 * (-_IO_buf_base) + 100。vtable: 指向_IO_str_jumps。
3.2 结合 Tcache 的劫持逻辑
假设我们已经通过 House of Botcake 拿到了 Tcache Poisoning 能力,并且计算出了 new_size 落在某个 Tcache bin 的范围内(例如 0x60)。
- 我们在堆上伪造上述的
IO_FILE_plus结构体。 - 利用 Tcache Poisoning,将 Tcache[0x60] 的
next指针指向目标地址(例如存放返回地址的栈地址)。 - 利用 Largebin Attack 或 Tcache Poisoning 将
_IO_list_all覆盖为我们伪造的堆块地址。 - 调用
exit(0)触发_IO_flush_all_lockp。 - glibc 遍历到我们伪造的 FILE 结构体,调用
_IO_str_overflow。 - 函数内部执行
malloc(new_size)。因为 Tcache[0x60] 被投毒,这次malloc直接返回了栈地址! - 接着执行
memcpy(new_buf, old_buf, old_blen)。我们将精心构造的 ROP 链作为old_buf,长度作为old_blen,直接将 ROP 链写入了栈上! - 随后函数返回,栈展开,直接执行我们的 ROP 链(如
system("/bin/sh")或setcontext)。
准备: House of Botcake 获取 Tcache 投毒能力
伪造 IO_FILE_plus 结构体
vtable=_IO_str_jumps
计算 new_size 并投毒对应 Tcache bin
使 next 指向栈地址
劫持 _IO_list_all 指向伪造结构体
触发 exit 触发 FSOP
调用 _IO_str_overflow
malloc new_size
从 Tcache 取回栈地址
memcpy 将 ROP 链写入栈地址
函数返回执行 ROP 链
Getshell
4. 实战进阶:与 House of Botcake 的经典组合拳
在真实的 glibc 2.31~2.35 CTF 题目中,House of Botcake 和 House of Pig 往往是相伴而生的。一个完整的利用链如下:
- 漏洞触发:利用 UAF 或 Off-by-one。
- 堆重叠制造:使用 House of Botcake,让 chunk A 同时在 Unsorted Bin 和 Tcache 中。
- 信息泄露:通过 Unsorted Bin 中的 A 泄露 Libc 基址;通过 Tcache 中的 A 泄露堆地址(用于绕过 Safe-Linking)。
- 原语获取:修改 A 的 Tcache
next指针,实现任意地址分配(Tcache Poisoning)。 - 结构体伪造:利用任意地址分配,在堆上布置伪造的
IO_FILE_plus结构体。 - 劫持与触发 (House of Pig):再次利用 Tcache Poisoning 修改
_IO_list_all,或利用 Unsorted Bin Attack 修改_IO_list_all。调用exit触发 FSOP。 - 控制流劫持:
_IO_str_overflow内部的malloc取回栈地址,memcpy写入 ROP 链,完成栈迁移或直接执行system。
这套组合拳完美规避了 Double Free 检查、Safe-Linking 以及 Hook 移除带来的障碍,是现代堆利用的“标准答案”。
5. 总结与下篇预告
5.1 核心知识点总结
- FSOP 是现代替代方案:在
__free_hook被移除后,伪造 IO_FILE 结构体并触发overflow是劫持控制流的核心路径。 _IO_str_overflow是跳板:这个合法虚表函数内部包含malloc和free调用,其参数受 FILE 结构体字段控制。- Tcache 与 FSOP 的结合:House of Pig 的精髓在于,利用 Tcache Poisoning 控制
_IO_str_overflow中malloc的返回地址,将数据(如 ROP 链)精准写入目标地址(如栈帧),实现“任意地址写任意值”并触发执行。
5.2 下篇预告
在掌握了 Botcake 和 Pig 这两大现代基石后,下一篇我们将转向另一个高频考点:House of Corrosion(global_max_fast 利用)。
我们将探讨如何通过破坏 global_max_fast 这个全局变量,将原本只能管理小块内存的 Fastbin 机制扩展到全尺寸,从而实现对更大范围内存(如 stdout 或 _IO_list_all)的强力劫持。
结语:House of Pig 展示了现代漏洞利用的典型范式——不再追求单点突破,而是将多种机制(Tcache 的分配能力 + IO_FILE 的执行路径)串联起来,形成一条从内存破坏到代码执行的完整通道。掌握这种“组合思维”,是进阶高阶 PWN 选手的关键。
更多推荐

所有评论(0)