Skip to content

_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:

  1. Erişilebilir bir FILE'ı corrupt et ki vtable = &_IO_str_jumps (in range) olsun.
  2. _IO_str_overflow kullanmak için: buffer bounds'u öyle ayarla ki new_size == &"/bin/sh", _s._allocate_buffer = system olsun ve grow branch'inin alındığından emin ol. _IO_str_finish kullanmak için: _IO_USER_BUF'u temizle, _IO_buf_base = &"/bin/sh", _s._free_buffer = system ata.
  3. 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_jumps olan 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.

References