House of Obstack¶
vtable'ı glibc'nin meşru
_IO_obstack_jumps'ına işaret eden bir FILE forge et; obstack flush path_obstack_newchunk'ınCALL_CHUNKFUNmacro'suna ulaşır ki bu attacker-kontrollü birchunkfunfunction pointer'ını çağırır —_IO_vtable_checkwhitelist'ini bypass ederek.
Mechanism¶
Suistimal edilen invariant
glibc 2.24'ten beri _IO_vtable_check bir FILE'ın vtable'ının read-only __libc_IO_vtables section'ı içinde olmasını gerektirir ki bu arbitrary fake vtable'ları nötralize eder. House of Obstack, _IO_obstack_jumps'ın kendisinin o section içinde meşru, whitelist'lenmiş bir vtable olması gerçeğini suistimal eder. Forge edilmiş bir FILE'ın vtable'ını ona yönlendirmek check'i geçer, ama onun _IO_obstack_xsputn slot'u — obstack_grow → _obstack_newchunk üzerinden — CALL_CHUNKFUN macro'suna götürür:
#define CALL_CHUNKFUN(h, size) \
(((h)->use_extra_arg) ? (*(h)->chunkfun)((h)->extra_arg, (size)) \
: (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))
chunkfun, extra_arg ve use_extra_arg hepsi heap'teki attacker-kontrollü _IO_obstack_file yapısının içinde yaşar. Yani whitelist'lenmiş obstack vtable, read-only section dışına hiç vtable yerleştirmeden, attacker-kontrollü bir argument'la attacker-kontrollü bir function pointer üzerinden dolaylı çağrı yapar — esasen system("/bin/sh").
Walkthrough¶
_IO_obstack_xsputn/obstack_grow path'i üzerinden glibc 2.24–2.36'ya uygulanır (bu aralıkta _IO_vtable_check mevcuttur ve whitelist bypass gerçekten gereklidir). Obstack code path'inin kendisi 2.23'te de var, ama orada henüz _IO_vtable_check yok — dolayısıyla whitelist bypass'a ihtiyaç duyulmadan sade FSOP zaten çalışır. 2.37+'da eşdeğer flush path __printf_buffer_as_file_overflow → __printf_buffer_flush → __printf_buffer_flush_obstack → __obstack_newchunk çalıştırır ve aynı CALL_CHUNKFUN'a ulaşır.
-
Bir FILE pointer'ını kontrol altına al. Heap corruption üzerinden (örn.
_IO_list_all'dan erişilebilir bir overlapping/UAF chunk veya doğrudan kontrol edilen bir stream), bir_IO_FILE/_IO_obstack_filedeğerinde byte'ı kontrol edecek şekilde ayarla. -
vtable'ı obstack jumps table'a set et. FILE'ın vtable field'ına
&_IO_obstack_jumps(artı_IO_OVERFLOW/xsputndispatch'inin_IO_obstack_xsputn'a ulaşması için uygun slot offset'i) yaz. Bu__libc_IO_vtablesiçine işaret ettiği için_IO_vtable_check'i geçer. -
Obstack field'larını forge et.
_IO_obstack_filebirstruct obstackgömer. Heap'i yapı base'ine göre şöyle yerleştir: obstackpointer field'ı senin forge ettiğin obstack'e referans verir;obstack->chunkfun=system(veya bir gadget);obstack->extra_arg="/bin/sh"'a pointer;obstack->use_extra_argset edilir kiCALL_CHUNKFUN'un iki-arg dalı alınsın;- size/
next_free/chunk_limitfield'larıobstack_grow'un yeni bir chunk gerektiğine karar verip_obstack_newchunk'ı çağıracağı şekilde düzenlenir.
forged FILE:
vtable -> &_IO_obstack_jumps (+slot)
obstack ptr -> &fake_obstack (heap)
fake_obstack:
chunkfun -> system
extra_arg -> "/bin/sh"
use_extra_arg -> 1
(size fields) -> force _obstack_newchunk
- Flush'ı tetikle.
_IO_flush_all_lockp'a ulaş (örn.exit, abort/_IO_cleanupveya bir stdio çağrısı üzerinden). Obstackxsputnçalışır,obstack_growmevcut obstack chunk'ını overflow eder,_obstack_newchunkCALL_CHUNKFUN'ı ateşler vesystem("/bin/sh")çalışır.
Footgun'lar
- Slot offset'i önemli: dispatch edilen virtual function
_IO_obstack_xsputn'a inmeli; off-by-slot bir vtable pointer farklı bir obstack rutini dispatch eder. - Obstack size hesabı gerçekten bir yeni chunk zorlamalı; eğer
obstack_growyer bulursa,_obstack_newchunkasla çağrılmaz vechunkfunasla invoke edilmez. - Field offset'leri glibc sürümleri arasında ve pre-2.37 ile 2.37+ path'leri arasında farklıdır — target'ın
struct obstack/_IO_obstack_file'ına karşı doğrula.
Detection¶
- Program meşru obstack stream I/O yapmazken vtable'ı
_IO_obstack_jumpsolan bir FILE. - libc/programın kendi chunk allocator'ının dışına işaret eden bir obstack
chunkfun.
Mitigation¶
_IO_obstack_jumpswhitelist'lenmiş olsa da, obstack stream desteğini düşüren veya per-vtable argument validation ekleyen hardened build'ler gadget'ı kaldırır.- Genel FSOP hardening'i (örn.
_IO_cleanup/exit'in attacker-erişilebilir stream'leri flush etmemesi) trigger yüzeyini azaltır.