【学习记录】Week13(三):House of Orange 经典复现与 exit 机制暗线劫持

📅 2026/7/4 17:04:53 👁️ 阅读次数 📝 编程学习
【学习记录】Week13(三):House of Orange 经典复现与 exit 机制暗线劫持

写在前面:在上一篇中,我们深入探讨了_IO_FILE的任意地址读写技巧与 vtable 校验的演进史。今天,我们将把堆漏洞与 IO_FILE 结合起来,复盘 glibc 2.23 时代的绝对经典——House of Orange。更重要的是,我们将把目光从_IO_list_all转向程序退出的另一条必经之路:exit机制,探索__exit_funcstls_dtor_list这两条独立于 FSOP 的高阶攻击面。

📑 目录

  1. 经典重温:House of Orange 与 Unsorted Bin Attack 的合谋
  2. 独立攻击面:exit机制与__exit_funcs劫持
  3. 线程局部存储暗线:tls_dtor_list劫持
  4. 时代的眼泪:__malloc_hook/__free_hook的最后辉煌 (≤2.33)
  5. 总结与下篇预告

1. 经典重温:House of Orange 与 Unsorted Bin Attack 的合谋

在 Week11 中我们提过,House of Orange 解决了“无free函数如何释放堆块”的问题(通过修改 Top Chunk size 触发sysmalloc将旧 Top 放入 Unsorted Bin)。但它更精髓的部分在于如何利用这个 Unsorted Bin chunk 完成 FSOP。

1.1 2.23 时代的无脑 FSOP

在 glibc 2.23 中,没有 vtable 校验。House of Orange 的利用链如下:

  1. 获取 Unsorted Bin chunk:通过 Top Chunk 劫持,旧 Top Chunk 进入 Unsorted Bin。
  2. Unsorted Bin Attack:利用漏洞修改该 chunk 的bk指针为_IO_list_all - 0x10。当下次malloc遍历 Unsorted Bin 时,触发bck = victim->bk; unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av);,这会将main_arena的地址写入_IO_list_all
  3. 链表错位与伪造:此时_IO_list_all指向了main_arena的头部。glibc 遍历_IO_list_all时,会把main_arena当作一个_IO_FILE结构体。由于_chain字段(偏移 0x68)恰好落在main_arena的 Smallbin 链表中,攻击者可以通过精心计算大小,让 Smallbin 的fd指针指向我们在堆上伪造的 Fake FILE。
  4. 触发与执行:当程序触发malloc_printerr或调用exit时,glibc 遍历到 Fake FILE,发现_IO_write_ptr > _IO_write_base,调用vtable->overflow。由于无 vtable 校验,直接执行system("/bin/sh")

1.2 2.24+ 的失效

随着 glibc 2.24 引入IO_validate_vtable,上述第 4 步中直接伪造vtable指向堆表的行为会被拦截。这催生了后续利用合法虚表(_IO_str_jumps,_IO_wfile_jumps)的绕过技术(如 House of Pig / Apple)。

2. 独立攻击面:exit机制与__exit_funcs劫持

除了 FSOP,程序退出时还有另一套执行逻辑。exit()函数在刷新 IO 流之后,会调用注册的析构函数。这就是__exit_funcs攻击面。

2.1exit内部调用链

void exit(int status) { __run_exit_handlers(status, &__exit_funcs, true, true); }

__exit_funcs是一个指向exit_function_list结构体链表的全局指针。在__run_exit_handlers中,会遍历这个链表,并根据每个节点的flavor(类型,如ef_cxa,ef_on,ef_at)调用对应的函数指针。

2.2 劫持思路

如果通过堆漏洞(如任意地址写)能够覆盖 libc 中全局的__exit_funcs指针,使其指向我们在堆上伪造的exit_function_list结构体,程序exit时就会执行我们构造的函数指针。

2.3 致命阻碍:PTR_MANGLE (指针加密)

在 glibc 2.24+ 中,为了防止这种劫持,__run_exit_handlers在调用函数指针前,会使用PTR_DEMANGLE宏对指针进行解密:

// 加密逻辑:函数指针与线程控制寄存器 (通常存放于 fs/gs 段的某个偏移,如 fs:0x30) 异或后再旋转 encrypted_ptr = (func_ptr ^ secret) << 0x11; // 解密逻辑: decrypted_ptr = (encrypted_ptr >> 0x11) ^ secret;

绕过策略
如果我们要伪造加密的函数指针,必须知道secret(通常称为 Pointer Guard)。
在题目存在严重信息泄露(如能泄露 TLS 基址和fs:0x30的值)时,可以计算出secret,从而伪造加密后的system地址。但这在实战中条件极为苛刻。

3. 线程局部存储暗线:tls_dtor_list劫持

既然__exit_funcs的指针被加密了,安全研究员又发现了另一条路:tls_dtor_list

3.1 TLS 析构机制

在多线程环境下,线程退出时会调用__call_tls_dtors来清理线程局部存储相关的资源。

void __call_tls_dtors(void) { while (tls_dtor_list) { struct dtor_list *cur = tls_dtor_list; // 调用析构函数 cur->func(cur->obj); tls_dtor_list = tls_dtor_list->next; } }

tls_dtor_list是一个存在于线程控制块(TCB,通常由fsgs寄存器寻址)中的指针。

3.2 致命缺陷与利用

令人兴奋的是,在 glibc 2.34 之前的某些版本中,__call_tls_dtors调用cur->func时,并没有使用 PTR_DEMANGLE 进行解密!
这意味着,只要我们能通过堆漏洞实现任意地址写,将tls_dtor_list指向我们在堆上伪造的dtor_list结构体,并在其中写上system的地址和/bin/sh的参数,程序退出时就会直接调用system("/bin/sh"),完全绕过了 vtable 检查和__exit_funcs的加密!

如何写tls_dtor_list
tls_dtor_list存放在 TLS 中,TLS 的地址通常在 libc 之前(由mmap分配)。如果题目允许大规模堆溢出或存在任意地址写,可以通过覆盖 libc 附近的内存来篡改这个指针。这也是现代高版本 glibc 利用的一条重要暗线。

4. 时代的眼泪:__malloc_hook/__free_hook的最后辉煌 (≤2.33)

在谈论了这么多复杂的绕过,我们不能忘记过去十年 PWN 题的“标准答案”。

4.1 一击必杀的 Hook

在 glibc 2.33 及以前版本,libc 数据段中存在两个裸的函数指针:

  • __malloc_hook
  • __free_hook

每当调用mallocfree时,glibc 会首先检查这两个指针是否为空,如果不为空,则跳转执行。
利用流程极其简单:

  1. 通过堆漏洞泄露 Libc 基址。
  2. 通过 Tcache Poisoning 或 Fastbin Attack 实现“任意地址写”。
  3. __free_hook覆盖为system地址。
  4. 释放一个内容为/bin/sh\x00的 chunk。
  5. free("/bin/sh")-> 触发__free_hook-> 执行system("/bin/sh")

4.2 时代的终结

由于这种利用方式过于无脑,使得堆漏洞利用变成了“找 Hook”的流水线作业。glibc 官方在 2.34 版本中,以“安全性能低于预期”为由,彻底移除了这两个 Hook 变量。
这标志着“裸 Hook 时代”的终结,逼迫所有 CTF 选手转向了以_IO_FILE(FSOP) 和exit机制为代表的“结构体伪造时代”。

5. 总结与下篇预告

5.1 核心知识点总结

  1. House of Orange:经典展示了如何将堆块状态破坏与 Unsorted Bin Attack 结合,进而劫持_IO_list_all完成链表劫持。
  2. exit攻击面__exit_funcs虽然是退出必经之路,但受制于PTR_MANGLE指针加密;tls_dtor_list提供了无加密的替代路径,但需要定位 TLS 地址。
  3. Hook 的兴衰__malloc_hook/__free_hook是低版本的利器,但在高版本中已成历史,理解其原理有助于体会现代利用复杂度的提升。

5.2 下篇预告

在 Week13 的最后,我们将进行stderr/stdout独立攻击面与综合防御机制总结

  • 深入探讨如何在不修改_IO_list_all的情况下,仅通过篡改stderrstdout完成信息泄露与控制流劫持。
  • 梳理 glibc 在各版本中对 IO_FILE 防御的补丁历史。
  • Week 13 全局总结。

结语:从 House of Orange 的链表错位,到exit机制的暗度陈仓,再到 Hook 的简单粗暴。漏洞利用的历史就是一部攻防对抗的演化史。高版本的复杂利用,往往是对低版本简单逻辑的层层包装与绕过。理解这些历史的演进,能让你在面对未知的 glibc 版本时,拥有更敏锐的嗅觉。