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:
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.
- 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: - sysmalloc / top-chunk yöntemi: top chunk size'ını
MINSIZE(0x20) altına küçült veyaprev_inusebit'ini temizle, sonrasysmalloc'u top'u genişletmeye itecek kadar büyük bir allocation iste — yukarıdakisysmallocassert'i başarısız olur. -
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'takiassert (chunk_main_arena (bck->bk))kontrolü — yukarıdakisysmallocassert'inden farklı, ayrı bir assertion — başarısız olur. Her iki assert de aynı__malloc_assert→__fxprintf/fflushFSOP akışına düşer. -
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:
_IO_file_sync(fflush(stderr)tarafından ulaşılan slot) →setcontext+61.- Bu path'te
rdx_IO_helper_jumps'a iner vesetcontext+61yenirsp/rip'i[rdx+0xA0]/[rdx+0xA8]'ten okur. Yani offset'lerrdx(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:+0xA0→rsp(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.
- 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_vtablesaralığı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.