heap-exploitation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSKILL: Heap Exploitation — Expert Attack Playbook
SKILL: 堆利用——专家攻击手册
AI LOAD INSTRUCTION: Expert glibc heap exploitation techniques. Covers ptmalloc2 internals, bin structures, tcache mechanics, libc/heap leak methods, and attack selection by glibc version. Distilled from ctf-wiki heap sections, how2heap, and real-world exploitation. Base models often confuse glibc version constraints and miss safe-linking (PROTECT_PTR) introduced in 2.32.
AI加载说明: 专家级glibc堆利用技术,涵盖ptmalloc2内部原理、bin结构、tcache机制、libc/堆地址泄露方法,以及根据glibc版本选择攻击方案。内容提炼自ctf-wiki堆章节、how2heap和真实场景利用经验。基础模型通常会混淆glibc版本限制,遗漏2.32版本引入的safe-linking(PROTECT_PTR)机制。
0. RELATED ROUTING
0. 相关跳转链接
- stack-overflow-and-rop — when the overflow is on the stack rather than the heap
- format-string-exploitation — leak heap/libc addresses via format string
- arbitrary-write-to-rce — convert heap arbitrary write into code execution
- binary-protection-bypass — bypass ASLR/RELRO to use heap write effectively
- 栈溢出与ROP —— 适用于溢出发生在栈而非堆的场景
- 格式化字符串漏洞利用 —— 通过格式化字符串泄露堆/libc地址
- 任意写转RCE —— 将堆任意写转化为代码执行
- 二进制保护绕过 —— 绕过ASLR/RELRO以有效利用堆写能力
Advanced References
进阶参考资料
- HOUSE_OF_TECHNIQUES.md — House of Force/Spirit/Orange/Einherjar/Roman/Pig/Banana/Cat/Apple and tcache attacks
- IO_FILE_EXPLOITATION.md — _IO_FILE vtable hijack, FSOP, stdout/stdin abuse, exit flow exploitation
- HOUSE_OF_TECHNIQUES.md —— House of Force/Spirit/Orange/Einherjar/Roman/Pig/Banana/Cat/Apple等系列攻击及tcache攻击
- IO_FILE_EXPLOITATION.md —— _IO_FILE 虚表劫持、FSOP、stdout/stdin滥用、exit流程利用
1. PTMALLOC2 STRUCTURE QUICK REFERENCE
1. PTMALLOC2 结构速查
malloc_chunk Layout (64-bit)
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)│
└──────────────────────────┘ 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 Types
Bin 类型
| Bin | Size Range (64-bit) | Structure | LIFO/FIFO |
|---|---|---|---|
| tcache (per-thread) | ≤ 0x410 (7 entries per size) | Singly linked (next pointer) | LIFO |
| fastbin | ≤ 0x80 (default) | Singly linked (fd) | LIFO |
| unsortedbin | Any freed size | Doubly linked circular | FIFO |
| smallbin | < 0x400 | Doubly linked circular | FIFO |
| largebin | ≥ 0x400 | Doubly linked + size-sorted | Sorted |
| Bin类型 | 大小范围(64位) | 结构 | 出入栈规则 |
|---|---|---|---|
| tcache (per-thread) | ≤ 0x410 (每个大小最多7个条目) | 单链表(next指针) | LIFO |
| fastbin | ≤ 0x80 (默认) | 单链表(fd指针) | LIFO |
| unsortedbin | 任意已释放大小 | 双向循环链表 | FIFO |
| smallbin | < 0x400 | 双向循环链表 | FIFO |
| largebin | ≥ 0x400 | 双向链表+按大小排序 | 有序 |
Key Global Structures
核心全局结构
| Structure | Location | Purpose |
|---|---|---|
| libc .data segment | Contains bin heads, top chunk, system_mem |
| libc .data | malloc parameters (tcache settings, mmap threshold) |
| Heap (first allocation) | Per-thread tcache bins and counts |
| 结构名 | 存储位置 | 用途 |
|---|---|---|
| libc .data段 | 存储bin头部、top chunk、system_mem |
| libc .data | malloc参数(tcache配置、mmap阈值) |
| 堆(首次分配时创建) | 线程级tcache bin及计数 |
2. LEAK METHODS
2. 地址泄露方法
Libc Base Leak
Libc基地址泄露
| Method | Precondition | Technique |
|---|---|---|
| Unsortedbin fd/bk | Free a chunk > tcache range (or fill tcache) | fd/bk → |
| Smallbin fd/bk | Chunk moved from unsortedbin to smallbin | Same as unsortedbin leak |
| stdout FILE leak | Write to | Corrupt |
| 方法 | 前置条件 | 技术原理 |
|---|---|---|
| Unsortedbin fd/bk | 释放一个大于tcache范围的chunk(或填满tcache) | fd/bk指向 |
| Smallbin fd/bk | Chunk从unsortedbin移动到smallbin | 和unsortedbin泄露原理一致 |
| stdout FILE泄露 | 向 | 篡改 |
Heap Base Leak
堆基地址泄露
| Method | Precondition | Technique |
|---|---|---|
| Tcache fd pointer | Free two tcache chunks, read first's fd | fd → heap address (XOR'd in ≥ 2.32) |
| Fastbin fd | Free two fastbin chunks | fd → heap address |
| UAF read | Use-after-free on freed chunk | Read fd/bk directly |
| 方法 | 前置条件 | 技术原理 |
|---|---|---|
| Tcache fd指针 | 释放两个tcache chunk,读取第一个的fd值 | fd指向堆地址(2.32及以上版本会做XOR混淆) |
| Fastbin fd | 释放两个fastbin chunk | fd指向堆地址 |
| UAF读 | 对已释放chunk存在UAF读漏洞 | 直接读取fd/bk值 |
Safe-Linking Decode (glibc ≥ 2.32)
Safe-Linking解码(glibc ≥ 2.32)
python
undefinedpython
undefinedPROTECT_PTR: fd_stored = (chunk_addr >> 12) ^ real_fd
PROTECT_PTR: fd_stored = (chunk_addr >> 12) ^ real_fd
To decode: real_fd = fd_stored ^ (chunk_addr >> 12)
解码: real_fd = fd_stored ^ (chunk_addr >> 12)
To encode: fd_stored = (chunk_addr >> 12) ^ target_addr
编码: 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
---def deobfuscate(stored_fd, chunk_addr):
return stored_fd ^ (chunk_addr >> 12)
def obfuscate(target, chunk_addr):
return (chunk_addr >> 12) ^ target
---3. ATTACK CATEGORIES BY GLIBC VERSION
3. 按glibc版本划分的攻击类别
glibc < 2.26 (No tcache)
glibc < 2.26 (无tcache)
| Attack | Primitive Needed | Result |
|---|---|---|
| Fastbin dup | Double free | Arbitrary allocation |
| Unsortedbin attack | Corrupt unsortedbin bk | Write |
| Unlink attack | Heap overflow into prev_size + fd/bk | Arbitrary write (with known heap pointer) |
| House of Force | Top chunk size overwrite | Arbitrary allocation |
| House of Spirit | Write fake chunk header | Fastbin allocation at fake chunk |
| Off-by-one null | Null byte overflow into next chunk size | Overlapping chunks |
| 攻击方式 | 所需原语 | 效果 |
|---|---|---|
| Fastbin dup | 双重释放 | 任意地址分配 |
| Unsortedbin attack | 篡改unsortedbin的bk指针 | 将 |
| Unlink attack | 堆溢出覆盖prev_size + fd/bk | 任意写(需已知堆指针) |
| House of Force | 篡改top chunk大小 | 任意地址分配 |
| House of Spirit | 写入伪造的chunk头部 | 在伪造chunk地址分配fastbin |
| Off-by-one null | 空字节溢出覆盖下一个chunk的size字段 | 生成重叠chunk |
glibc 2.26–2.28 (tcache, no key)
glibc 2.26–2.28 (有tcache,无key校验)
| Attack | Notes |
|---|---|
| Tcache poisoning | Overwrite tcache fd → arbitrary allocation, no size check |
| Tcache dup | Double free into tcache (no double-free detection yet) |
| All previous attacks | Still work, but chunks go to tcache first |
| 攻击方式 | 说明 |
|---|---|
| Tcache poisoning | 篡改tcache fd → 任意地址分配,无大小校验 |
| Tcache dup | 双重释放到tcache(此时还没有双重释放检测) |
| 所有旧版本攻击 | 仍然可用,但chunk会优先进入tcache |
glibc 2.29–2.31 (tcache key introduced)
glibc 2.29–2.31 (引入tcache key校验)
| Attack | Bypass for tcache key |
|---|---|
| Tcache dup | Corrupt |
| House of Botcake | Double free: one in unsortedbin, one in tcache → overlapping |
| Tcache stashing unlink | Abuse smallbin→tcache refill to get arbitrary chunk |
| 攻击方式 | Tcache key绕过方法 |
|---|---|
| Tcache dup | 第二次释放前篡改 |
| House of Botcake | 双重释放:一个进unsortedbin,一个进tcache → 生成重叠chunk |
| Tcache stashing unlink | 利用smallbin→tcache refill逻辑获取任意chunk |
glibc 2.32–2.33 (safe-linking / PROTECT_PTR)
glibc 2.32–2.33 (safe-linking / PROTECT_PTR机制)
| Attack | Adaptation |
|---|---|
| Tcache poisoning | Encode target with |
| Heap leak required | Need heap addr to decode/encode safe-linked pointers |
| Fastbin dup | Same encoding required |
| 攻击方式 | 适配调整 |
|---|---|
| Tcache poisoning | 使用 |
| 需要堆地址泄露 | 需要堆地址来解码/编码safe-linking指针 |
| Fastbin dup | 同样需要做编码处理 |
glibc ≥ 2.34 (hooks removed)
glibc ≥ 2.34 (移除hook)
| Change | Impact |
|---|---|
| Cannot overwrite hook for one_gadget |
| Cannot overwrite hook |
| Cannot use realloc trick for one_gadget constraints |
Post-2.34 targets: see arbitrary-write-to-rce for , , , .
_IO_FILEexit_funcsTLS_dtor_list_dl_fini| 变更 | 影响 |
|---|---|
移除 | 无法覆盖hook来调用one_gadget |
移除 | 无法覆盖该hook |
移除 | 无法使用realloc技巧绕过one_gadget约束 |
2.34及以上版本目标: 参考任意写转RCE的、、、相关利用方案。
_IO_FILEexit_funcsTLS_dtor_list_dl_fini4. COMMON VULNERABILITY PATTERNS
4. 常见漏洞模式
| Vulnerability | Description | Exploitation Path |
|---|---|---|
| UAF (Use-After-Free) | Access chunk after free | Read: leak fd/bk; Write: corrupt fd for tcache poisoning |
| Double Free | free() same chunk twice | Tcache dup (bypass key) or fastbin dup |
| Heap Overflow | Write past chunk boundary | Corrupt next chunk's metadata (size, fd, bk) |
| Off-by-one | One byte overflow | Null byte → shrink next chunk size → overlapping chunks |
| Off-by-null | Specifically | Clear PREV_INUSE → trigger backward consolidation |
| Uninitialized read | Read heap memory without clearing | Leak fd/bk from recycled chunk |
| 漏洞类型 | 描述 | 利用路径 |
|---|---|---|
| UAF (Use-After-Free) | 释放后仍可访问chunk | 读:泄露fd/bk;写:篡改fd实现tcache poisoning |
| Double Free | 对同一个chunk调用两次free() | Tcache dup(绕过key)或fastbin dup |
| Heap Overflow | 写入超出chunk边界 | 篡改下一个chunk的元数据(size、fd、bk) |
| Off-by-one | 单字节溢出 | 空字节→缩小下一个chunk大小→生成重叠chunk |
| Off-by-null | 特指 | 清除PREV_INUSE标记→触发向后合并 |
| Uninitialized read | 未清零就读取堆内存 | 从回收的chunk中泄露fd/bk |
5. TOOLS
5. 工具
bash
undefinedbash
undefinedpwndbg heap inspection
pwndbg堆检查命令
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
pwndbg> heap # 显示所有chunk
pwndbg> bins # 显示所有bin内容
pwndbg> tcachebins # 查看tcache状态
pwndbg> fastbins # 查看fastbin状态
pwndbg> unsortedbin # 查看unsortedbin内容
pwndbg> vis_heap_chunks # 可视化堆布局
pwndbg> find_fake_fast &__malloc_hook # 查找附近的伪造fastbin chunk
how2heap — reference implementations
how2heap —— 参考实现
git clone https://github.com/shellphish/how2heap
git clone https://github.com/shellphish/how2heap
heapinspect
heapinspect
pip install heapinspect
heapinspect <pid>
pip install heapinspect
heapinspect <pid>
pwntools helpers
pwntools辅助代码
from pwn import *
libc = ELF('./libc.so.6')
print(hex(libc.symbols['__malloc_hook']))
print(hex(libc.symbols['__free_hook']))
---from pwn import *
libc = ELF('./libc.so.6')
print(hex(libc.symbols['__malloc_hook']))
print(hex(libc.symbols['__free_hook']))
---6. DECISION TREE
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)识别到堆漏洞
├── 可用原语是什么?
│ ├── UAF (读+写)
│ │ ├── 可以读取已释放chunk? → 泄露libc(unsortedbin)或堆地址(tcache fd)
│ │ └── 可以写入已释放chunk? → Tcache poisoning / fastbin dup
│ ├── 双重释放
│ │ ├── glibc < 2.29 → 直接使用tcache dup
│ │ ├── glibc 2.29-2.31 → 先篡改tcache key,或使用House of Botcake
│ │ └── glibc ≥ 2.32 → 需要堆地址泄露来做safe-linking编码
│ ├── 堆溢出(大小可控)
│ │ ├── 覆盖下一个chunk size → 重叠chunk → UAF
│ │ └── 直接覆盖fd → 任意地址分配
│ ├── Off-by-one / off-by-null
│ │ ├── 空字节写入size字段 → House of Einherjar(向后合并)
│ │ └── 单字节写入size字段 → 缩小chunk,生成重叠
│ └── 任意写(来自重叠或毒化分配)
│ ├── glibc < 2.34 → __malloc_hook / __free_hook → one_gadget
│ ├── glibc ≥ 2.34 → _IO_FILE vtable, exit_funcs, TLS_dtor_list
│ └── Partial RELRO → GOT覆盖
│
├── 需要泄露libc?
│ ├── 释放chunk到unsortedbin(大小>0x410或填满7个tcache条目)
│ ├── 读取fd/bk → 计算main_arena偏移 → 得到libc基地址
│ └── 替代方案:部分覆盖stdout FILE结构实现泄露
│
└── 需要泄露堆地址? (glibc ≥ 2.32)
├── 从已释放chunk读取tcache fd
└── 解码: real_addr = stored_fd ^ (chunk_addr >> 12)