Heap Exploitation
Heap Exploitation โจมตีช่องโหว่ในการจัดการ dynamic memory (malloc/free) ของ glibc — use-after-free, double-free, heap overflow, tcache poisoning ที่ทำให้ควบคุม chunk metadata หรือ pointer จนได้ arbitrary read/write บทนี้ลงลึกโครงสร้าง chunk, bins, ช่องโหว่หลักแต่ละแบบพร้อมแนวทาง, tcache poisoning, และการ debug (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)
1. โครงสร้าง heap chunk
glibc malloc (ptmalloc2) จัดการ memory เป็น chunk แต่ละ chunk มี metadata: prev_size (8 byte), size (8 byte, 3 bit ล่างเป็น flag: PREV_INUSE/IS_MMAPED/NON_MAIN_ARENA) ตามด้วย user data เมื่อ chunk ถูก free, พื้นที่ user data ส่วนต้นถูกใช้เก็บ pointer (fd/bk) ที่ลิงก์ chunk ใน bin (free list) — จุดนี้คือเป้าของการโจมตีส่วนใหญ่
2. Bins (free lists)
| bin | ขนาด chunk | ลักษณะ |
|---|---|---|
| tcache | ≤ 0x410 (64 bin/ขนาด) | LIFO, เร็ว, ตรวจน้อย (glibc 2.26+) |
| fastbin | 0x20–0x80 | LIFO, single-linked (fd) |
| unsorted bin | ชั่วคราว | ก่อนจัดเข้า small/large |
| small bin | < 0x400 | double-linked, ขนาดเดียว |
| large bin | ≥ 0x400 | double-linked, ช่วงขนาด |
tcache เป็นเป้าหลักในโจทย์สมัยใหม่ (glibc 2.26+) เพราะเร็วและตรวจสอบน้อย — chunk ที่ free ขนาดเล็กจะเข้า tcache ก่อน เป็น single-linked list (LIFO) ที่ next pointer อยู่ต้น user data
3. ช่องโหว่หลัก
| ช่องโหว่ | เกิดจาก | นำไปสู่ |
|---|---|---|
| Use-After-Free | ใช้ pointer หลัง free | อ่าน/เขียน chunk ที่ถูกจองใหม่ |
| Double Free | free chunk เดิม 2 ครั้ง | ป่วน freelist → arbitrary alloc |
| Heap Overflow | เขียนเกิน chunk | ทับ metadata/fd ของ chunk ถัดไป |
| Off-by-one / null byte | เขียนเกิน 1 byte | ทับ size/PREV_INUSE → overlap |
| UAF write | เขียน chunk หลัง free | แก้ fd → tcache poisoning |
4. tcache poisoning (เทคนิคหลักสมัยใหม่)
tcache เก็บ free chunk เป็น single-linked list ผ่าน next pointer (ต้น user data) ถ้ามี UAF/overflow แก้ next ได้ → ทำให้ malloc ครั้งถัดๆ คืน address ที่เราเลือก = arbitrary write (เขียนที่ไหนก็ได้)
- 1malloc 2 chunk (A, B) ขนาดเท่ากัน (เข้า tcache ได้)
- 2free A, free B → tcache: B → A
- 3ใช้ UAF/overflow แก้ next ของ B ให้ชี้ target address
- 4malloc (ได้ B กลับ) → malloc อีกครั้ง = ได้ chunk ที่ target!
- 5เขียน target ได้ (เช่น __free_hook, GOT, return address)
from pwn import *
# ฟังก์ชัน helper (ขึ้นกับ menu ของโจทย์)
def alloc(idx, size, data): ...
def free(idx): ...
def edit(idx, data): ...
target = 0x... # เช่น libc.sym['__free_hook']
alloc(0, 0x30, b'A') # chunk A
alloc(1, 0x30, b'B') # chunk B
free(0); free(1) # tcache: 1 -> 0
# glibc 2.32+ มี pointer mangling: next = (addr >> 12) ^ target
edit(1, p64(target)) # แก้ next ของ chunk 1 → target
alloc(2, 0x30, b'X') # ได้ chunk 1
alloc(3, 0x30, p64(system_addr)) # ได้ chunk ที่ target → เขียน!5. ผลของเวอร์ชัน glibc
- < 2.26: ยังไม่มี tcache — ใช้ fastbin/unsorted bin attack
- 2.26–2.28: มี tcache, ตรวจสอบน้อยมาก — tcache poisoning ง่ายสุด
- 2.29+: เพิ่ม tcache key (ตรวจ double-free), ตรวจ count
- 2.32+: safe-linking (mangle next pointer ด้วย >>12 XOR) — ต้อง leak heap
- 2.34+: ลบ __malloc_hook/__free_hook — เปลี่ยนไปใช้ FSOP/IO_FILE, __exit_funcs
- เช็คเวอร์ชัน glibc ก่อนเลือกเทคนิค:
./libc.so.6หรือstrings libc | grep 'GNU C'
6. Debug heap
gdb ./vuln
# gef
gef> heap chunks # ดู chunk ทั้งหมด + metadata
gef> heap bins # ดู bin (tcache/fastbin/...) มี chunk อะไร
gef> heap chunk 0x... # ดู chunk เฉพาะ
# pwndbg
pwndbg> heap
pwndbg> bins
pwndbg> vis_heap_chunks # มองเห็นภาพ heap
# เรียนรู้จาก how2heap (ตัวอย่างทุกเทคนิค)
# github.com/shellphish/how2heap7. Quick Reference
- chunk: prev_size, size|flags, user data (freed → fd/bk)
- bins: tcache (เป้าหลัก 2.26+), fastbin, unsorted/small/large
- ช่องโหว่: UAF, double-free, heap overflow, off-by-one
- tcache poisoning: free 2 → แก้ next → malloc ได้ target → arbitrary write
- glibc 2.32+ safe-linking (next XOR addr>>12) → leak heap ก่อน
- glibc 2.34+ ลบ hook → ใช้ FSOP/GOT/return
- debug: gef heap chunks/bins; เรียนจาก how2heap
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติโจทย์เป็นโปรแกรม menu (add/edit/delete/view) ที่ใช้ malloc/free — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อหาช่องโหว่ heap แล้วไปให้ถึง arbitrary write
- 1เช็คเวอร์ชัน glibc ก่อน:
./libc.so.6(รันตรงๆ) หรือstrings libc.so.6 | grep 'GNU C'— เวอร์ชันกำหนดเทคนิคที่ใช้ได้ - 2อ่านโค้ด/decompile ด้วย Ghidra ดูฟังก์ชัน add/edit/delete/view ว่าแต่ละอันเช็ค index/size ครบไหม
- 3หา double-free: ลอง free chunk เดิม 2 ครั้งติดกัน ดูว่าโปรแกรมเช็คไหม (glibc ใหม่มี tcache key check)
- 4หา use-after-free: free แล้วยัง view/edit chunk นั้นได้อีกไหม
- 5หา heap overflow: edit ใส่ data ยาวกว่าขนาดที่ malloc ไว้ ดูว่าทับ chunk ถัดไปไหม
- 6เปิด
gdb ./vulnพร้อม gef/pwndbg แล้วดูโครงสร้างจริง:gef> heap chunks,gef> heap bins - 7ลองทำ tcache poisoning: malloc 2 chunk ขนาดเท่ากัน, free ทั้งคู่, แก้ next ของ chunk บนสุดด้วย UAF/overflow
- 8ถ้า glibc ≥ 2.32 ต้อง leak heap address ก่อน demangle next pointer (safe-linking: next XOR (addr>>12))
- 9เลือกเป้าหมายเขียนตามเวอร์ชัน: < 2.34 ใช้ __free_hook/__malloc_hook, ≥ 2.34 ใช้ FSOP/GOT/exit handler
- 10ดูตัวอย่างเทคนิคที่ตรงกับที่เจอได้จาก how2heap (github.com/shellphish/how2heap) ก่อนเขียน exploit จริง
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ดูโครงสร้าง chunk/bins | gdb + gef/pwndbg | bash -c "$(curl -fsSL https://gef.blah.cat/sh)" | - |
| decompile ดู add/edit/delete | Ghidra | apt install ghidra (ถ้าไม่มี) | dogbolt.org |
| เขียน exploit script | pwntools | pip install pwntools | - |
| ดูตัวอย่างเทคนิคก่อนเขียนเอง | how2heap (git clone) | git clone shellphish/how2heap | - |
| เช็คเวอร์ชัน glibc | strings libc.so.6 | - | - |
| ระบุเวอร์ชัน libc จาก leak | libc-database | git clone niklasb/libc-database | libc.rip, libc.blukat.me |
| patch binary ให้โหลด libc เฉพาะ | patchelf | apt install patchelf | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้