Vtable Hack

Exploring vtable internals.

1. Introduction

In C++, the virtual table (vtable) is an implementation detail used by compilers to support dynamic polymorphism through virtual functions. In one of my previous blogs, we discussed about how vptr and vtable work to achieve dynamic polymorphism. In short, each derived object has a hidden virtual table pointer that points to a vtable. Vtable is just an array of function pointers.

The C++ standard doesn’t expose the vtable or vptr as part of the language. Its layout and existence depend on the compiler (e.g., GCC, MSVC, Clang) and platform. We can approximate access to the vtable by dereferencing the vptr manually, but this is undefined behavior in strict C++ terms and should only be done for learning or debugging purposes.

vptr Location: The vptr is typically the first hidden member of the object (on most compilers like GCC and MSVC), but this isn’t guaranteed by the standard.

vtable Layout: The vtable is an array of function pointers. The order corresponds to the declaration order of virtual functions, but again, this is compiler-specific.

2. Access Vptr and Vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class Base {
public:
virtual void func1() { std::cout << "Base::func1\n"; }
virtual void func2() { std::cout << "Base::func2\n"; }
};

class Derived : public Base {
public:
void func1() override { std::cout << "Derived::func1\n"; }
void func2() override { std::cout << "Derived::func2\n"; }
};

int main() {
Base* obj = new Derived(); // Polymorphic object

// Step 1: Get the vptr (assumes it's the first hidden member)
uintptr_t* vptr = reinterpret_cast<uintptr_t*>(obj);

// Step 2: Dereference vptr to get the vtable address
uintptr_t* vtable = reinterpret_cast<uintptr_t*>(*vptr);

// Step 3: Access function pointers from the vtable
// (Assuming func1 is at index 0, func2 at index 1)
using FuncPtr = void (*)();
FuncPtr f1 = reinterpret_cast<FuncPtr>(vtable[0]);
FuncPtr f2 = reinterpret_cast<FuncPtr>(vtable[1]);

// Step 4: Call the functions
f1(); // Should call Derived::func1
f2(); // Should call Derived::func2

delete obj;
return 0;
}
Author

Joe Chu

Posted on

2025-03-02

Updated on

2025-03-02

Licensed under

Comments