So far we have learned how to reconstruct an unknown Struct from Memory Accesses and Offsets
But when we deal with C++ programs, the matter becomes a bit more complicated
Because an Object may not have just a few simple fields
We may have:
Class
↓
Inheritance
↓
Virtual Functions
↓
vptr
↓
vtable
This is where we need to understand from the Assembly:
What Class is this Object?
What Class does it inherit from?
Where are the Virtual Functions
And which part of the Object do the different Offsets belong to?
Understand Virtual Functions
Suppose the Source Code is like this:
class Animal {
public:
virtual void speak();
};
And:
class Dog : public Animal {
public:
void speak() override;
};
Because "speak" is a Virtual Function, the Compiler usually considers a Pointer to the Virtual Function Table for this Object
We usually call this Pointer:
vptr
and the table:
vtable
Conceptually:
Object
│
├── vptr
│ │
│ ▼
│ vtable
│ │
│ ├── speak()
│ ├── ...
│
└── Other Fields
That is, the Object usually has a Pointer that points to the Virtual Function information related to the Dynamic Object type
Of course, the exact details of the Layout depend on the Compiler and ABI.
What should we look for inside the Assembly?
Suppose we see something like this inside the Constructor:
mov qword ptr [rcx], offset vtable_Dog
Here we have a very important clue
That is, the initial value of Object is set:
Object + 0x00
↓
vptr
↓
vtable_Dog
So we can say for now:
Dog Object
+0x00 vptr
+0x08 unknown
+0x10 unknown
We still don't know what the next fields are
But at least we know one important thing:
+0x00
is probably vptr
What exactly is vtable?
Very simply, if we want to look at it:
vptr
│
▼
vtable
│
├── Function Pointer
├── Function Pointer
├── Function Pointer
└──
Each entry in the vtable is usually a pointer to a function or part of the Virtual Dispatch mechanism
For example, suppose the assembly looks like this:
mov rax, [rcx]
mov rax, [rax+0x10]
call rax
Let's look at it step by step
First:
mov rax, [rcx
]
That is:
RCX
↓
Object
↓
+0x00
↓
vptr
Then:
mov rax, [rax+0x10
]
That is, an entry at a specific offset is read from the vptr table
Then:
call rax
That is, that value is used for Call
So we have a conceptual path:
Object
↓
+0x00
↓
vptr
↓
vtable
↓
Function Pointer
↓
call
This is one of the most important patterns in Reverse Engineering C++ programs.
How do we find the Virtual Function from this pattern?
Suppose we have this Assembly:
mov rax, [rcx]
mov rax, [rax+0x18]
call rax
We can create this model for now:
Object
|
+0x00 → vptr
|
↓
vtable
|
+0x18 → function pointer
But one important point:
We should not say quickly:
+0x18 = Function X
Because we still do not know exactly what Virtual Function Entry is related to
We should check the vtable itself and its References
Now Inheritance comes in
Suppose we have:
class Animal {
public:
virtual void speak();
};
class Dog : public Animal {
public:
void speak() override;
};
Conceptually:
Animal
↑
|
Dog
Dog
inherits parts of Animal
The Object might look something like this:
Dog Object
+0x00 vptr
+0x08 Animal-related field
+0x0C Dog-related field
This Layout is not exact and is just a conceptual model
Because the actual Layout depends on the Compiler, ABI, Alignment and Inheritance type
But the important point is that:
Base Class
+
Derived Class
can be present in a single Object
We can find the relationship from the Constructor
Suppose we have a Constructor:
mov qword ptr [rcx], offset vtable_Animal
And then in the Constructor we see another class:
call Animal::Animal
mov qword ptr [rcx], offset vtable_Dog
Here we have a very important clue
That is, the Constructor related to Dog first initializes the part related to the Base Class and then the Virtual Dispatch state related to Sets the Derived Class
Conceptually:
Dog Constructor
|
├── Animal Constructor
|
└── Dog-specific initialization