Skip to content

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.

References