คลัง
pwn

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/ระบบที่ได้รับอนุญาต)

Advanced#heap#pwn#use-after-free#double-free#tcache#fastbin#glibc#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) — จุดนี้คือเป้าของการโจมตีส่วนใหญ่

Heap chunk: allocated vs freed
allocated chunk prev_size size | flags (PREV_INUSE) user data freed chunk prev_size size | flags fd → chunk ถัดไปใน bin bk → chunk ก่อนหน้า free free → user data กลายเป็น fd/bk pointer // แก้ fd → ควบคุมสิ่งที่ malloc คืน (tcache poisoning)
เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab) เท่านั้น; heap exploitation ขึ้นกับเวอร์ชัน glibc มาก

2. Bins (free lists)

binขนาด chunkลักษณะ
tcache≤ 0x410 (64 bin/ขนาด)LIFO, เร็ว, ตรวจน้อย (glibc 2.26+)
fastbin0x20–0x80LIFO, single-linked (fd)
unsorted binชั่วคราวก่อนจัดเข้า small/large
small bin< 0x400double-linked, ขนาดเดียว
large bin≥ 0x400double-linked, ช่วงขนาด

tcache เป็นเป้าหลักในโจทย์สมัยใหม่ (glibc 2.26+) เพราะเร็วและตรวจสอบน้อย — chunk ที่ free ขนาดเล็กจะเข้า tcache ก่อน เป็น single-linked list (LIFO) ที่ next pointer อยู่ต้น user data

3. ช่องโหว่หลัก

ช่องโหว่เกิดจากนำไปสู่
Use-After-Freeใช้ pointer หลัง freeอ่าน/เขียน chunk ที่ถูกจองใหม่
Double Freefree 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 (เขียนที่ไหนก็ได้)

  1. 1malloc 2 chunk (A, B) ขนาดเท่ากัน (เข้า tcache ได้)
  2. 2free A, free B → tcache: B → A
  3. 3ใช้ UAF/overflow แก้ next ของ B ให้ชี้ target address
  4. 4malloc (ได้ B กลับ) → malloc อีกครั้ง = ได้ chunk ที่ target!
  5. 5เขียน target ได้ (เช่น __free_hook, GOT, return address)
โครง tcache poisoning (pwntools)Linux
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 → เขียน!
glibc 2.32+ มี safe-linking (pointer mangling): next ถูก XOR ด้วย (chunk_addr>>12) — ต้อง leak heap address มา demangle ก่อน

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'
glibc 2.34+ ลบ hook ที่เคยเป็นเป้ายอดนิยม — โจทย์ใหม่ต้องใช้ FSOP (_IO_FILE exploitation), __exit_funcs, หรือ overwrite GOT/return ผ่าน tcache แทน

6. Debug heap

ดูโครงสร้าง heap ด้วย gef/pwndbgLinux
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/how2heap
heap ต้อง debug เยอะ — gef/pwndbg แสดง chunk/bin ให้เห็นภาพ; how2heap มีตัวอย่างรันได้ทุกเทคนิค

7. 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. 1เช็คเวอร์ชัน glibc ก่อน: ./libc.so.6 (รันตรงๆ) หรือ strings libc.so.6 | grep 'GNU C' — เวอร์ชันกำหนดเทคนิคที่ใช้ได้
  2. 2อ่านโค้ด/decompile ด้วย Ghidra ดูฟังก์ชัน add/edit/delete/view ว่าแต่ละอันเช็ค index/size ครบไหม
  3. 3หา double-free: ลอง free chunk เดิม 2 ครั้งติดกัน ดูว่าโปรแกรมเช็คไหม (glibc ใหม่มี tcache key check)
  4. 4หา use-after-free: free แล้วยัง view/edit chunk นั้นได้อีกไหม
  5. 5หา heap overflow: edit ใส่ data ยาวกว่าขนาดที่ malloc ไว้ ดูว่าทับ chunk ถัดไปไหม
  6. 6เปิด gdb ./vuln พร้อม gef/pwndbg แล้วดูโครงสร้างจริง: gef> heap chunks, gef> heap bins
  7. 7ลองทำ tcache poisoning: malloc 2 chunk ขนาดเท่ากัน, free ทั้งคู่, แก้ next ของ chunk บนสุดด้วย UAF/overflow
  8. 8ถ้า glibc ≥ 2.32 ต้อง leak heap address ก่อน demangle next pointer (safe-linking: next XOR (addr>>12))
  9. 9เลือกเป้าหมายเขียนตามเวอร์ชัน: < 2.34 ใช้ __free_hook/__malloc_hook, ≥ 2.34 ใช้ FSOP/GOT/exit handler
  10. 10ดูตัวอย่างเทคนิคที่ตรงกับที่เจอได้จาก how2heap (github.com/shellphish/how2heap) ก่อนเขียน exploit จริง
จับมือทำ: heap — จากอ่านโค้ดถึง arbitrary write
เช็คเวอร์ชัน glibc
หาช่องโหว่: UAF? double-free? heap overflow?
✅ เจอช่องโหว่อย่างใดอย่างหนึ่ง→ ลอง tcache poisoning
❌ ไม่เจอช่องโหว่ heap เลย→ อาจไม่ใช่โจทย์ heap จริง กลับไปดู stack/format string
free 2 chunk ขนาดเท่ากัน แล้วแก้ next ด้วย UAF/overflow
✅ glibc < 2.32 (ไม่มี safe-linking)→ เขียน next ตรงๆ ได้เลย
✅ glibc ≥ 2.32 (safe-linking)→ ต้อง leak heap address มา demangle ก่อน
leak heap address (view chunk ที่ยังไม่ถูกเขียนทับ)
✅ leak heap address ได้→ demangle next แล้วเขียนต่อ
❌ leak heap ไม่ได้→ ลองหา info leak อื่น (format string)
glibc เวอร์ชันไหน เลือกเป้าหมายเขียน
✅ < 2.34 (มี hook)→ เขียน __free_hook/__malloc_hook = system/win
✅ ≥ 2.34 (ไม่มี hook)→ ใช้ FSOP/GOT/exit handler แทน
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดูโครงสร้าง chunk/binsgdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
decompile ดู add/edit/deleteGhidraapt install ghidra (ถ้าไม่มี)dogbolt.org
เขียน exploit scriptpwntoolspip install pwntools-
ดูตัวอย่างเทคนิคก่อนเขียนเองhow2heap (git clone)git clone shellphish/how2heap-
เช็คเวอร์ชัน glibcstrings libc.so.6--
ระบุเวอร์ชัน libc จาก leaklibc-databasegit clone niklasb/libc-databaselibc.rip, libc.blukat.me
patch binary ให้โหลด libc เฉพาะpatchelfapt install patchelf-
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) format-string — ถ้าต้องการ info leak มาช่วย demangle safe-linking หรือ leak libc/heap base (2) got-overwrite — ถ้า glibc ใหม่ไม่มี hook ให้เขียนแล้ว ต้องใช้ GOT แทน (3) ret2libc — ถ้าเขียน __free_hook สำเร็จแล้วอยากเรียก system("/bin/sh") (4) aslr — ถ้า heap/libc base สุ่มและต้อง leak มาคำนวณก่อน

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้