Skip to content

.dtors / .fini_array overwrite

Bir ELF destructor function-pointer array'ini overwrite ederek C runtime'ın program sonlandığında attacker-controlled kod çağırmasını sağla.

Mechanism

Note

Bir process normal olarak sonlandığında, C runtime termination section'larını execute eder. Eski gcc'de bu .dtors'tu; modern ELF'te .fini_array'dir (artı .fini). __libc_csu_fini, __fini_array_start ile __fini_array_end arasındaki function pointer'ları iterate eder ve her birini ters sırada, argümansız çağırır. Array, writable bir data section'ında yalnızca bir code pointer listesidir — dolayısıyla bir entry'ye ulaşan herhangi bir write-what-where, "program çıkar"ı "program benim pointer'ıma jump eder"e çevirir.

Invariant: exit'teki control flow, return-address integrity'si ile değil bir data tablosuyla sürülür. İki klasik kullanım:

  • Direct hijack — bir entry'yi shellcode / bir one_gadget ile overwrite et.
  • Re-entry / loop hilesi — bir entry'yi main'in adresiyle (ya da __libc_csu_fini'nin saklı return slot'uyla) overwrite et; böylece program exit'te etkin bir şekilde yeniden başlar ve one-shot bir bug'a (örn. bir format string) karşı ikinci bir geçiş sağlar — ASLR/NX bu şekilde iteratif olarak yenilir.

Warning

Full RELRO altında .fini_array section'ı (tıpkı GOT gibi) read-only map'lenir ve bu vektörü öldürür. Yalnızca Partial/No RELRO ile çalışır. Eski .dtors, 0xffffffff/0x00000000 sentinel marker'larını kullanırdı; klasik saldırı null sentinel'i bir shellcode adresiyle overwrite ederdi.

Walkthrough

Array'i bul ve writable olup olmadığını kontrol et:

$ readelf -SW ./vuln | grep -E 'fini_array|dtors'
  [19] .fini_array  FINI_ARRAY  080496dc 0006dc 000008 00  WA  0   0  4
$ checksec ./vuln
    RELRO:    Partial RELRO        # .fini_array is writable

Format-string re-entry payload'ı (CODEGATE-tarzı): bir %hn write'ını .fini_array entry'sine yönelt ve onun düşük 16 bit'ini main'in alt yarısıyla değiştir:

from pwn import *
fini = 0x080496dc                  # .fini_array[0]
# write low 2 bytes of main() into fini via positional %hn
payload  = b"%" + str(main_lo).encode() + b"c%11$hn"
payload  = payload.ljust(0x20, b'A') + p32(fini)
p.sendline(payload)

Beklenen davranış: exit'te, legitimate destructor yerine main() yeniden invoke edilir — binary "yeniden başlar" ve birinci geçişte libc leak'lemenize, ikinci geçişte printf@GOT'u system ile overwrite etmenize olanak tanır.

Ters sıra neden önemli

__libc_csu_fini index'ini __fini_array_end'den __fini_array_start'a doğru azaltır; son register edilen destructor ilk çalışır. İki entry ile loop'un stack'teki saklı return address'ini __libc_csu_fini'ye geri işaret edecek şekilde overwrite edebilir ve onu yeniden iterate ettirebilirsiniz — temiz, sonsuz bir exploitation loop'u.

Mitigation

  • Full RELRO (-Wl,-z,relro,-z,now) .fini_array'i read-only yapar.
  • PIE + ASLR, target adresini hesaplamanın maliyetini yükseltir.
  • Full RELRO'nun varsayılan olduğu hardened modern toolchain'lerde teknik ölüdür; bunun yerine exit-handler abuse veya FILE-stream teknikleri'ne pivot et.

References