Skip to content

Vtable hijacking

Bir C++ object'inin vtable pointer'ını (ya da vtable'ındaki bir entry'yi) overwrite ederek bir virtual call'un attacker'ın seçtiği koda dispatch etmesini sağlamak.

Mechanism

Virtual method'ları olan bir class'ın her C++ object'i, offset 0'da gizli bir vptr taşır — class'ın vtable'ına bir pointer, yani her virtual method için bir tane olmak üzere function pointer'larından oluşan bir array. Bir virtual call, hedefi runtime'da çözer: object'ten vptr'ı okur, vtable'ı index'ler ve o pointer'ı çağırır:

"Every class with virtual methods has a hidden member variable: a pointer to a vtable, which is basically just an array of function pointers, one element for each of the class's virtual methods." — defuse.ca

Dispatch kabaca şuna derlenir:

mov  rax, [obj]        ; rax = vptr (object's first 8 bytes)
call [rax + offset]    ; call vtable[index]

vptr writable object belleğinde yaşadığı ve call ona körü körüne güvendiği için, vptr'ı bir fake vtable'ı gösterecek şekilde corrupt etmek, sonraki her virtual call'u attacker-controlled function pointer'ları üzerinden yönlendirir.

Note

vptr'a iki yol ulaşır. (1) Direct overwrite — bir buffer overflow veya arbitrary write, object'in ilk 8 byte'ını ezer. (2) Use-after-free — object'i free et, slot'unu attacker-controlled veriyle geri al, ve dangling virtual call artık bir attacker vptr'ı okur (use-after-free, heap-grooming-feng-shui). Fikir rix'in "Smashing C++ VPTRs" (Phrack 56) yazısına dayanır.

Walkthrough

vptr'ı bir kardeş class'ın vtable'ına corrupt edilen minimal bir tip; böylece zararsız bir call tehlikeli bir method'u çağırır (defuse.ca'nın Greeter/CommandExecutor biçimi):

struct Greeter        { virtual void sayHello(); char buf[0x40]; };
struct CommandExecutor{ virtual void execute();  /* runs system()  */ };

Greeter *g = new Greeter();
/* vulnerability: overflow into g->buf overwrites g's vptr (first 8 bytes) */
overflow(g, /*new vptr=*/ &CommandExecutor_vtable);

g->sayHello();   /* dispatches through the swapped vtable -> execute() runs */

Tamamen attacker-controlled fake vtable (genel primitive):

void *fake_vtable[1] = { (void*)attacker_gadget };  /* in writable, leaked mem */
*(void**)victim = fake_vtable;                       /* set victim->vptr */
victim->any_virtual_method();                        /* calls attacker_gadget */

Fake vtable bilinen bir adreste bulunmalıdır (dolayısıyla önceden bir address-leak) ve attacker_gadget tipik olarak bir stack-pivot ya da bir return-oriented-programming chain'ine devreden bir one-shot'tır.

Detection / Mitigation

Compiler/runtime CFI birincil savunmadır: Clang/GCC -fsanitize=cfi-vcall her virtual call'da vtable'ın yasal bir tipe ait olduğunu doğrular; MSVC Control Flow Guard ve VTable-protection (vtguard) da indirect hedefleri benzer şekilde kısıtlar. Araştırma savunmaları (VTV "vtable verification", VTrust, VTable interleaving, VTPin), bir vptr'ın static tip için geçerli bir vtable'ı gösterdiğini zorunlu kılar. Hardware CET IBT her indirect hedefte bir endbr landing pad ister, mid-gadget adreslerine jump'ları kırar. Heap hardening (segregated cache'ler, MTE, quarantine), UAF reclaim yolunu zorlaştırır.

References