Skip to content

House of Kiwi

Bir malloc assertion failure'ı zorla ki abort path stderr'in FILE vtable'ını bir FSOP chain'e sürüklesin ve __malloc_assert'ı kontrollü code execution'a çevirsin.

Mechanism

Suistimal edilen invariant: assert/abort path corrupt edilebilir bir stream üzerinde FILE I/O yapar

ptmalloc imkânsız bir invariant tespit ettiğinde sessizce çıkmaz — __malloc_assert'ı çağırır ki bu bir diagnostic formatlar ve onu glibc'nin standart I/O'su üzerinden flush eder. İlgili sysmalloc assertion'ı şudur:

assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));

Failure'da __malloc_assert şunu çalıştırır:

__fxprintf (NULL, "...Assertion `%s' failed.\n", ...);
fflush (stderr);

Hem __fxprintf (_IO_file_xsputn üzerinden) hem fflush(stderr) (sync slot üzerinden) stderr'in vtable'ı / _IO_helper_jumps table'ı üzerinden dispatch eder. Bu table'ların writable olduğu glibc sürümlerinde, birkaç arbitrary write yapabilen bir attacker dispatch edilen function pointer'ı bir setcontext-tarzı gadget'a yönlendirir. Kilit nokta: bu normal exploitation path'leri yok olduğunda bile erişilebilirdir — __malloc_hook/__free_hook gerekmez — ve bir corruption check tetiklendiği anda ateşlenir, dolayısıyla attacker failure'ın kendisini kontrol eder.

Walkthrough

Target: kabaca 2.29 – 2.34 (erken) glibc. glibc-2.34-0ubuntu3.2'de çalışmayı durdurur; orada I/O vtable bölgesi read-only yapılır ve jump table'lara write-anywhere başarısız olur. Gereklilikler: assert'i tetiklemek için bir heap-overflow/corruption yeteneği, artı az sayıda arbitrary-write primitive.

  1. Bir assert trigger'ı seç. Her iki path da __malloc_assert'ı tetikler (farklı assertion'lar üzerinden) ve böylece yukarıdaki abort/FSOP akışını açar:
  2. sysmalloc / top-chunk yöntemi: top chunk size'ını MINSIZE (0x20) altına küçült veya prev_inuse bit'ini temizle, sonra sysmalloc'u top'u genişletmeye itecek kadar büyük bir allocation iste — yukarıdaki sysmalloc assert'i başarısız olur.
  3. largebin yöntemi: en küçük largebin chunk'ının size field'ındaki NON_MAIN_ARENA (A) bit'ini set et; bir sonraki insertion sırasında _int_malloc'taki assert (chunk_main_arena (bck->bk)) kontrolü — yukarıdaki sysmalloc assert'inden farklı, ayrı bir assertion — başarısız olur. Her iki assert de aynı __malloc_assert__fxprintf/fflush FSOP akışına düşer.

  4. FSOP target'larını hazırla. Arbitrary write'ları kullanarak dispatch'i I/O path bir stack-pivot gadget'a inecek şekilde kur. Kanonik template'te:

  5. _IO_file_sync (fflush(stderr) tarafından ulaşılan slot) → setcontext+61.
  6. Bu path'te rdx _IO_helper_jumps'a iner ve setcontext+61 yeni rsp/rip'i [rdx+0xA0]/[rdx+0xA8]'ten okur. Yani offset'ler rdx (I/O state) tabanına görelidir — _IO_helper_jumps'a ayrıca yazılan bir hedef değil, aynı writable tablonun okunan slot'larıdır: +0xA0rsp (ROP chain / pivot), +0xA8 → yüklenen bir sonraki instruction pointer.

setcontext+61 gadget'ı esasen mov rsp, [rdx+0xa0]; ... [rdx+0xa8] yapar, dolayısıyla rdx'i (bu path'te kontrollü I/O state'ine işaret eder) artı o iki slot'u kontrol etmek bir ROP chain'e stack pivot verir.

!!! warning "Footgun: rdx kontrollü belleğe işaret etmeli" setcontext yeni context'i [rdx + ...]'tan okur. Tüm chain sadece şu yüzden çalışır: assert path'inde rdx attacker-etkili I/O state'ine iner. Eğer senin glibc build'inin akışı rdx'i başka yere bırakırsa, offset'ler ve gadget seçimi o tam libc için yeniden türetilmeli.

  1. Trigger. Assert'i zorlayan allocation'ı yap. __malloc_assert çalışır, __fxprintf/fflush(stderr) tahrif edilmiş vtable/jump table üzerinden dispatch eder, pivot gadget çalışır ve control ROP chain'e aktarılır.

??? example "Kavramsal call chain"

malloc(large)  ->  sysmalloc  ->  assert(...) fails
               ->  __malloc_assert
               ->  __fxprintf(NULL, ...)  ->  _IO_file_xsputn (stderr vtable)
               ->  fflush(stderr)         ->  sync slot  ->  setcontext+61
               ->  rsp = [rdx+0xa0]  ->  ROP

Detection

  • Anında attacker-kontrollü flow'a yol açan bir abort/assert gözlemlenebilirdir: __malloc_assert'taki bir crash'i takip eden libtext dışında execution bir kırmızı bayraktır.
  • I/O dispatch'inden önce _IO_helper_jumps / stderr vtable pointer'larının integrity'sini check etmek tamper'ı tespit eder.

Mitigation

  • glibc 2.34 malloc error path'ini ve I/O vtable bölgesini hardened yaptı; glibc-2.34-0ubuntu3.2'den itibaren jump table'lar writable değil, technique'i kırar.
  • _IO_vtable_check (vtable pointer meşru __libc_IO_vtables aralığında olmalı) zaten naif vtable swap'larını kısıtlar; House of Kiwi bunun yerine meşru helper-jump table'ının içeriğini hedefler — writable olduğu build'lerde.

References