Fake vtable¶
Bir vtable pointer'ını overwrite ederek bir sonraki virtual/handler call'unun attacker koduna dispatch olmasını sağlama.
Mechanism¶
Hem C++ object'leri hem de glibc _IO_FILE_plus, method/handler call'larını
object'in ilk qword'ü olarak saklanan bir vtable pointer üzerinden indirect
olarak resolve eder. O pointer'ı attacker-controlled bir function pointer tablosuna
(ya da mevcut bir tabloya) işaret edecek şekilde overwrite etmek, bir sonraki
virtual/handler dispatch'inin kontrolü attacker-chosen bir adrese aktarması demektir.
Bu, hem COOP (C++ object'leri) hem de FSOP (_IO_FILE)'ın altında yatan ortak
primitive'dir.
Warning
glibc >= 2.24'te tamamen forge edilmiş bir tablo, vtable pointer'ının read-only
__libc_IO_vtables section'ı içinde bulunduğunu kontrol eden
IO_validate_vtable tarafından bloklanır. Pratik varyant, vtable'ı slot'u faydalı
bir şey yapan mevcut, geçerli bir vtable'a işaret ettirir (forge edilmiş değil
"bulunmuş" bir vtable).
Walkthrough¶
Generic layout ve overwrite:
// Object/FILE layout: [ vptr ] -> [ fn0, fn1, fn2, ... ]
fake_obj->vptr = &fake_vtable; // pre-2.24, or any controlled table
fake_vtable[__overflow_slot] = target_func; // one_gadget / system / _IO_str_overflow
// Dispatch obj->method() / _IO_OVERFLOW(fp) -> calls target_func
2.24 sonrası check (orijinal glibc 2.24–2.27 formu; section boundary
__start/__stop symbol'leri __libc_IO_vtables section adından türer):
uintptr_t off = (char*)vtable - __start___libc_IO_vtables;
if (__glibc_unlikely(off >= __stop___libc_IO_vtables - __start___libc_IO_vtables))
_IO_vtable_check(); // aborts on out-of-range vtable
Note
glibc >= 2.28'de aynı check &__io_vtables array adresi ve IO_VTABLES_LEN
sabiti üzerinden yeniden yazıldı (offset = ptr - (uintptr_t)&__io_vtables;
offset >= IO_VTABLES_LEN); mantık aynıdır, bkz.
io-vtable-check-bypass.
Bypass, vptr'yi _IO_str_jumps gibi legitimate, in-range bir vtable'a işaret
ettirir; onun __overflow slot'u _IO_str_overflow'dur. O fonksiyon
(*fp->_s._allocate_buffer)(new_size)'ı çağırır; _allocate_buffer = system ve
new_size = "/bin/sh" forge etmek, dispatch'i system("/bin/sh")'e çevirir — hepsi
geçerli, in-bounds bir vtable üzerinden.
Mitigation¶
IO_validate_vtable (glibc >= 2.24) section dışı tabloları bloklar; clang CFI vcall
check'leri ve vtable-pointer integrity (PAC ile imzalanmış vptr'ler) C++ formunu
yener. In-range _IO_str_jumps hilesi basit bounds check'i atlatır.