_IO_str_overflow / _IO_str_jumps abuse¶
Forge edilmiş bir FILE'ın vtable'ını legitimate, in-range
_IO_str_jumps'a çevir ki bir flush'ın ulaştığı slot (__overflow→_IO_str_overflow, ya da__finish→_IO_str_finish) attacker-controlled bir call gerçekleştirsin.
Mechanism¶
Note
Bu, string-stream primitive'inin vtable-swap çerçevesidir. Tam bir
fmemopen/open_memstream objesi forge etmek yerine, attacker kontrol edebildiği
herhangi bir FILE'ı alır, vtable'ını _IO_str_jumps'a yöneltir (bu __io_vtables
içinde durduğu için glibc 2.24 range check'ini geçer) ve buffer field'larını öyle
ayarlar ki FSOP flush'ının tetiklediği fonksiyon _IO_str_overflow ya da
_IO_str_finish olur. İkisi de strfile'ın allocate/free buffer pointer'ını
(_s._allocate_buffer) dereference edip onu buffer bounds'tan hesaplanan bir size ile
çağırır — yani range-valid bir vtable'ı arbitrary bir call'a dönüştürür. Kırılan
invariant şu: range check vtable'ın konumunu doğrular, obje için hangi table'ın
semantik olarak doğru olduğunu değil; dolayısıyla normal bir FILE bir string stream
"haline gelebilir".
Field naming'e dikkat: klasik (exploit edilebilir) glibc'de buffer function pointer'ları
_s._allocate_buffer ve _s._free_buffer adlarını taşır ve indirect olarak çağrılır.
Hardened/modern glibc bu field'ları _allocate_buffer_unused/_free_buffer_unused
olarak yeniden adlandırdı ve _IO_str_overflow/_IO_str_finish içindeki indirect
call'ları doğrudan malloc/free ile değiştirdi — yani aşağıdaki reçete klasik
(indirect-call'lu) sürümlerde geçerlidir; modern sürümlerde bu path körelir.
Walkthrough¶
_IO_str_jumps slot'ları (glibc libio/strops.c):
const struct _IO_jump_t _IO_str_jumps = {
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow,_IO_str_underflow),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(seekoff, _IO_str_seekoff),
...
};
_IO_str_finish free path'i (klasik/indirect-call form, alternatif trigger):
void _IO_str_finish (FILE *fp, int dummy) {
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); /* call(ptr) */
...
}
Swap reçetesi:
- Erişilebilir bir FILE'ı corrupt et ki
vtable = &_IO_str_jumps(in range) olsun. _IO_str_overflowkullanmak için: buffer bounds'u öyle ayarla kinew_size == &"/bin/sh",_s._allocate_buffer = systemolsun ve grow branch'inin alındığından emin ol._IO_str_finishkullanmak için:_IO_USER_BUF'u temizle,_IO_buf_base = &"/bin/sh",_s._free_buffer = systemata.- Flush'ı
_IO_flush_all_lockpüzerinden tetikle (fsop-via-io-list-all-io-flush-all-lockp.md).
Warning
Tam field offset'leri ve hangi branch'in alındığı glibc versiyonları arasında farklılık gösterir; modern libc buffer-function field'larını yeniden adlandırdı ve bazı indirect call'ları kaldırdı, bu da işi wide-path House of Apple 2'ye doğru itiyor (io-wfile-jumps.md).
Detection¶
_IO_str_overflow/_IO_str_finish'ten non-allocator adreslere giden indirect call'lar.- Vtable'ı
_IO_str_jumpsolan ama hiçbir zaman string stream olmamış bir FILE.
Mitigation¶
- glibc 2.24+ range check (burada reuse ile bypass ediliyor), sonradan indirect buffer call'larının kaldırılması, full RELRO, pointer guard.