スキル:ヒープエクスプロイト — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:glibc ヒープエクスプロイトのエキスパート向けテクニックです。ptmalloc2 の内部構造、ビンの構造、tcache の仕組み、libc/ヒープのリーク方法、glibc のバージョンに応じた攻撃の選択を扱います。ctf-wiki のヒープ関連セクション、how2heap、実際のエクスプロイト事例をもとに要点をまとめています。ベースモデルは glibc のバージョンによる制約を混同しがちで、2.32 で導入された safe-linking(PROTECT_PTR)を見落とすことがよくあります。
0. 関連するルーティング
高度なリファレンス
1. PTMALLOC2 の構造クイックリファレンス
malloc_chunk のレイアウト(64-bit)
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)│
└──────────────────────────┘
ビンの種類
| ビン | サイズ範囲(64-bit) | 構造 | LIFO/FIFO |
|---|
| tcache(スレッドごと) | ≤ 0x410(サイズごとに 7 エントリー) | 単方向リンク(next ポインター) | LIFO |
| fastbin | ≤ 0x80(デフォルト) | 単方向リンク(fd) | LIFO |
| unsortedbin | 任意の解放済みサイズ | 双方向循環リスト | FIFO |
| smallbin | < 0x400 | 双方向循環リスト | FIFO |
| largebin | ≥ 0x400 | 双方向リンク+サイズ順 | ソート順 |
主なグローバル構造体
| 構造体 | 場所 | 目的 |
|---|
main_arena | libc の .data セグメント | ビンの先頭、top chunk、system_mem を保持 |
mp_ | libc の .data | malloc のパラメーター(tcache の設定、mmap のしきい値) |
tcache_perthread_struct | ヒープ(最初の割り当て時) | スレッドごとの tcache ビンとカウント |
2. リーク方法
Libc ベースアドレスのリーク
| 方法 | 前提条件 | テクニック |
|---|
| Unsortedbin の fd/bk | tcache の範囲より大きいチャンクを解放する(または tcache を埋める) | fd/bk → main_arena + 0x60(バージョンによっては +0x70)→ libc ベースアドレス |
| Smallbin の fd/bk | チャンクが unsortedbin から smallbin に移動している | unsortedbin のリークと同じ |
| stdout FILE のリーク | _IO_2_1_stdout_ に書き込む | _IO_write_base を破壊して libc データをリークする(IO_FILE を参照) |
ヒープベースアドレスのリーク
| 方法 | 前提条件 | テクニック |
|---|
| Tcache の fd ポインター | tcache のチャンクを 2 つ解放し、先のチャンクの fd を読み取る | fd → ヒープアドレス(≥ 2.32 では XOR 化) |
| Fastbin の fd | fastbin のチャンクを 2 つ解放する | fd → ヒープアドレス |
| UAF 読み取り | 解放済みチャンクに対する Use-After-Free | 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 | 二重解放 | 任意の割り当て |
| Unsortedbin attack | unsortedbin の bk を破壊 | ターゲットに main_arena のアドレスを書き込む(__malloc_hook 付近の上書きに使用) |
| Unlink attack | ヒープオーバーフローで prev_size と fd/bk に到達 | 任意書き込み(ヒープポインターが既知の場合) |
| House of Force | top chunk のサイズを上書き | 任意の割り当て |
| House of Spirit | 偽のチャンクヘッダーを書き込む | 偽チャンク上で fastbin 割り当て |
| Off-by-one null | 次のチャンクのサイズへのヌルバイトオーバーフロー | チャンクの重複 |
glibc 2.26–2.28(tcache あり、キーなし)
| 攻撃 | 備考 |
|---|
| Tcache poisoning | tcache の fd を上書き → 任意の割り当て。サイズチェックなし |
| Tcache dup | tcache への二重解放(まだ二重解放の検出なし) |
| 以前のすべての攻撃 | 引き続き有効。ただし、チャンクはまず tcache に入ります |
glibc 2.29–2.31(tcache key の導入)
| 攻撃 | tcache key の回避方法 |
|---|
| Tcache dup | 2 回目の free の前に key フィールド(chunk+0x18)を破損させる |
| House of Botcake | Double free:unsortedbin に 1 つ、tcache に 1 つ → 重なり合うチャンク |
| Tcache stashing unlink | smallbin→tcache の補充を悪用して任意のチャンクを取得する |
glibc 2.32–2.33(safe-linking / PROTECT_PTR)
| 攻撃 | 対応方法 |
|---|
| Tcache poisoning | (chunk_addr >> 12) ^ target でターゲットをエンコードする |
| ヒープリークが必須 | safe-linked ポインターのデコード/エンコードにはヒープアドレスが必要です |
| Fastbin dup | 同じエンコードが必要です |
glibc ≥ 2.34(フックの削除)
| 変更 | 影響 |
|---|
__malloc_hook が削除 | one_gadget のためにフックを上書きできません |
__free_hook が削除 | フックを上書きできません |
__realloc_hook が削除 | one_gadget の制約に realloc トリックを使用できません |
2.34 以降のターゲット:_IO_FILE、exit_funcs、TLS_dtor_list、_dl_fini については、arbitrary-write-to-rce を参照してください。
4. よくある脆弱性パターン
| 脆弱性 | 説明 | 悪用方法 |
|---|
| UAF(Use-After-Free) | free 後にチャンクへアクセスする | 読み取り:fd/bk をリークする。書き込み:fd を破損させて tcache poisoning を行う |
| Double Free | 同じチャンクを 2 回 free() する | Tcache dup(key を回避)または fastbin dup |
| ヒープオーバーフロー | チャンクの境界を越えて書き込む | 次のチャンクのメタデータ(size、fd、bk)を破損させる |
| Off-by-one | 1 バイトのオーバーフロー | ヌルバイト → 次のチャンクのサイズを縮小 → チャンクを重ね合わせる |
| Off-by-null | 特定の \x00 オーバーフロー | PREV_INUSE をクリア → 後方統合を発生させる |
| 未初期化読み取り | クリアせずにヒープメモリを読み取る | 再利用されたチャンクから 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)