技能:堆利用——专家攻击手册
AI 加载指令:glibc 堆利用专家技术。涵盖 ptmalloc2 内部机制、bin 结构、tcache 机制、libc/堆泄漏方法,以及根据 glibc 版本选择攻击方式。内容提炼自 ctf-wiki 的堆利用章节、how2heap 和真实世界的利用案例。基础模型经常混淆 glibc 版本限制,并遗漏 2.32 引入的 safe-linking(PROTECT_PTR)。
0. 相关路由
进阶参考
1. PTMALLOC2 结构速查
malloc_chunk 布局(64 位)
chunk pointer (returned by malloc - 0x10)
┌──────────────────────────┐
0x00 │ prev_size (if prev free)│
0x08 │ size | A | M | P │ ← P=PREV_INUSE, M=IS_MMAPPED, A=NON_MAIN_ARENA
├──────────────────────────┤ ← user data starts here (returned pointer)
0x10 │ fd (if free) │ ← forward pointer to next free chunk
0x18 │ bk (if free) │ ← backward pointer to prev free chunk
0x20 │ fd_nextsize (large only)│
0x28 │ bk_nextsize (large only)│
└──────────────────────────┘
Bin 类型
| Bin | 大小范围(64 位) | 结构 | LIFO/FIFO |
|---|
| tcache(每线程) | ≤ 0x410(每种大小 7 个条目) | 单向链表(next 指针) | LIFO |
| fastbin | ≤ 0x80(默认值) | 单向链表(fd) | LIFO |
| unsortedbin | 任意已释放大小 | 双向循环链表 | FIFO |
| smallbin | < 0x400 | 双向循环链表 | FIFO |
| largebin | ≥ 0x400 | 双向链表,按大小排序 | 已排序 |
关键全局结构
| 结构 | 位置 | 用途 |
|---|
main_arena | libc .data 段 | 包含 bin 头、top chunk、system_mem |
mp_ | libc .data | malloc 参数(tcache 设置、mmap 阈值) |
tcache_perthread_struct | 堆(首次分配时) | 每线程的 tcache bin 和计数 |
2. 泄漏方法
libc 基址泄漏
| 方法 | 前提条件 | 技术 |
|---|
| Unsortedbin fd/bk | 释放一个大于 tcache 范围的 chunk(或填满 tcache) | fd/bk → main_arena + 0x60(或根据版本为 +0x70)→ libc 基址 |
| Smallbin fd/bk | chunk 从 unsortedbin 移至 smallbin | 与 unsortedbin 泄漏相同 |
| stdout FILE 泄漏 | 写入 _IO_2_1_stdout_ | 破坏 _IO_write_base 以泄漏 libc 数据(参见 IO_FILE) |
堆基址泄漏
| 方法 | 前提条件 | 技术 |
|---|
| Tcache fd 指针 | 释放两个 tcache chunk,读取第一个 chunk 的 fd | fd → 堆地址(在 ≥ 2.32 中经过 XOR) |
| Fastbin fd | 释放两个 fastbin chunk | fd → 堆地址 |
| UAF 读取 | 对已释放 chunk 进行释放后使用 | 直接读取 fd/bk |
Safe-Linking 解码(glibc ≥ 2.32)
# PROTECT_PTR: fd_stored = (chunk_addr >> 12) ^ real_fd
# To decode: real_fd = fd_stored ^ (chunk_addr >> 12)
# To encode: fd_stored = (chunk_addr >> 12) ^ target_addr
def deobfuscate(stored_fd, chunk_addr):
return stored_fd ^ (chunk_addr >> 12)
def obfuscate(target, chunk_addr):
return (chunk_addr >> 12) ^ target
3. 按 GLIBC 版本划分的攻击类别
glibc < 2.26(无 tcache)
| 攻击 | 所需原语 | 结果 |
|---|
| Fastbin dup | Double free | 任意分配 |
| Unsortedbin attack | 破坏 unsortedbin bk | 将 main_arena 地址写入目标(用于覆盖 __malloc_hook 附近) |
| Unlink attack | 堆溢出覆盖 prev_size + fd/bk | 任意写入(需已知堆指针) |
| House of Force | 覆盖 top chunk 大小 | 任意分配 |
| House of Spirit | 写入伪造的 chunk 头 | 在伪造 chunk 处进行 fastbin 分配 |
| Off-by-one null | 用空字节溢出到下一个 chunk 的大小字段 | 重叠 chunk |
glibc 2.26–2.28(有 tcache,无 key)
| 攻击 | 说明 |
|---|
| Tcache poisoning | 覆盖 tcache fd → 任意分配,无大小检查 |
| Tcache dup | 对 tcache 中的 chunk 进行 Double free(尚无 double-free 检测) |
| 之前的所有攻击 | 仍然有效,但 chunk 会优先进入 tcache |
glibc 2.29–2.31(引入 tcache key)
| 攻击 | 绕过 tcache key 的方法 |
|---|
| Tcache dup | 在第二次释放前,破坏 key 字段(位于 chunk+0x18) |
| House of Botcake | 双重释放:一个位于 unsortedbin,一个位于 tcache → 形成重叠 |
| Tcache stashing unlink | 滥用 smallbin→tcache 补充机制,获取任意 chunk |
glibc 2.32–2.33(safe-linking / PROTECT_PTR)
| 攻击 | 适配方式 |
|---|
| Tcache poisoning | 使用 (chunk_addr >> 12) ^ target 对目标进行编码 |
| 需要堆泄漏 | 需要堆地址才能解码/编码 safe-linked 指针 |
| Fastbin dup | 同样需要编码 |
glibc ≥ 2.34(移除 hooks)
| 变更 | 影响 |
|---|
移除 __malloc_hook | 无法覆盖 hook 来使用 one_gadget |
移除 __free_hook | 无法覆盖 hook |
移除 __realloc_hook | 无法利用 realloc 技巧满足 one_gadget 的约束 |
2.34 之后的目标:有关 _IO_FILE、exit_funcs、TLS_dtor_list、_dl_fini,请参见 arbitrary-write-to-rce。
4. 常见漏洞模式
| 漏洞 | 描述 | 利用路径 |
|---|
| UAF(释放后使用) | 释放后访问 chunk | 读取:泄漏 fd/bk;写入:破坏 fd 以进行 tcache poisoning |
| 双重释放 | 对同一个 chunk 调用两次 free() | Tcache dup(绕过 key)或 fastbin dup |
| 堆溢出 | 写入超出 chunk 边界 | 破坏下一个 chunk 的元数据(size、fd、bk) |
| Off-by-one | 溢出一个字节 | 空字节 → 缩小下一个 chunk 的大小 → 形成重叠 chunk |
| Off-by-null | 特指 \x00 溢出 | 清除 PREV_INUSE → 触发向后合并 |
| 未初始化读取 | 不清零便读取堆内存 | 从回收的 chunk 中泄漏 fd/bk |
5. 工具
# pwndbg heap inspection
pwndbg> heap # display all chunks
pwndbg> bins # show all bin contents
pwndbg> tcachebins # tcache status
pwndbg> fastbins # fastbin status
pwndbg> unsortedbin # unsortedbin content
pwndbg> vis_heap_chunks # visual heap layout
pwndbg> find_fake_fast &__malloc_hook # find nearby fake fastbin chunks
# how2heap — reference implementations
git clone https://github.com/shellphish/how2heap
# heapinspect
pip install heapinspect
heapinspect <pid>
# pwntools helpers
from pwn import *
libc = ELF('./libc.so.6')
print(hex(libc.symbols['__malloc_hook']))
print(hex(libc.symbols['__free_hook']))
6. 决策树
Heap vulnerability identified
├── What is the primitive?
│ ├── UAF (read + write)
│ │ ├── Can read freed chunk? → Leak libc (unsortedbin) or heap (tcache fd)
│ │ └── Can write freed chunk? → Tcache poisoning / fastbin dup
│ ├── Double free
│ │ ├── glibc < 2.29 → direct tcache dup
│ │ ├── glibc 2.29-2.31 → corrupt tcache key first, or House of Botcake
│ │ └── glibc ≥ 2.32 → need heap leak for safe-linking encode
│ ├── Heap overflow (controlled size)
│ │ ├── Overwrite next chunk size → overlapping chunks → UAF
│ │ └── Overwrite fd directly → arbitrary allocation
│ ├── Off-by-one / off-by-null
│ │ ├── Null byte into size → House of Einherjar (backward consolidation)
│ │ └── One byte into size → shrink chunk, create overlap
│ └── Arbitrary write (from overlap or poisoned allocation)
│ ├── glibc < 2.34 → __malloc_hook / __free_hook → one_gadget
│ ├── glibc ≥ 2.34 → _IO_FILE vtable, exit_funcs, TLS_dtor_list
│ └── Partial RELRO → GOT overwrite
│
├── Need libc leak?
│ ├── Free chunk into unsortedbin (size > 0x410 or fill 7 tcache)
│ ├── Read fd/bk → main_arena offset → libc base
│ └── Alternative: stdout FILE partial overwrite for leak
│
└── Need heap leak? (glibc ≥ 2.32)
├── Read tcache fd from freed chunk
└── Decode: real_addr = stored_fd ^ (chunk_addr >> 12)