Introduction
This post is a continuation from Part 1’s “Obscure Members in MSVC’s Virtual Inheritance and Where to Find Them”, where I explained what’s Virtual Inheritance in MSVC, which class members are compiler generated along with their purpose and usage. It’s based on Jan Gray’s work explaining the technical details for Virttual Inheritance on Microsoft’s 1990’s patents. The previous post mostly covered the theoretical part, so feel free to check it if you feel like you need a refresher.
Today, we will be exposing the more practical side of reverse engineering PEs that uses this MSVC inheritance feature. We’ll expose the constructors for virtual, derived and virtually inheriting classes. Later, we’ll deduce the memory layout for a derived class along with the 2 different layouts for virtually inheriting classes, and we’ll expose the mechanism for virtual function members after overriding. Finally, we’ll also manage to see a glimpse of how RTTI is displayed for such a case.
Case Study: Chimera.exe
The example we’ll be using now is a little more convoluted, and it’s based on the greek tale of the Chimera1, a mythical animal that’s part lion, part goat and part snake. This makes a perfect example for virtual inheritance, as all species I mentioned are part animal, so they all must inherit from a common class.
Inheritance Tree
The Chimera class inherits from 4 different classes: Animal, Lion, Goat and Snake, however the last 3 of these classes are virtually inheriting from the Animal class. All of these classes implement their own custom constructor and destructor, having the last function be a virtual function member that will be inherited by all other classes below the inheritance tree.
The class inheritance tree and the class definition for this case would look like the following.

Another important detail is that there’s 2 unimplemented virtual functions (getType, showMemoryLayout) marked as __pure, which will make the Animal class an abstract class. These 2 virtual methods will still become inherited by their inheriting classes, which will make them become overridden when they actually become implemented. We’ll see more of this later.
Main
Starting the main function, this program initializes a Chimera class object, which receives all of the virtual inheriting classes members in order to set them at creation time.

Note that the first parameter corresponds to the this pointer, and the final argument for a virtual derived class flag is set to 1 and added to the Chimera constructor.
Chimera Constructor
Going into the Chimera constructor, we can see that the first code piece corresponds to the initial constructor for the virtual class in its nested version. Before reaching this inline constructor, it first needs to check the passed flag in case this class is being derived instead of inherited and decide then to whether construct the virtual class or skip it.

Animal Constructor
In this case, the virtual class corresponds to Animal. It doesn’t call another separate function to construct an instance of this class, but rather this inline code initializes its values along with all the vbtables for the other virtual inheriting classes. Afterwards, it sets the data members for Animal (species, health, speed) and calls “custom code” (printing "Animal Constructor: " and the species name to std::cout). This piece of code is basically a constructor function without being a separate function.

The vbtables, found in the .rdata segment, are unique for each virtually inheriting class and only have 2 offsets inside each one. The values are coherent with what we talked about in the previous post. These offsets give out information about the layout of these classes inside Chimera.

Looking at the ASM for this basic block, it’s more clear that the compiler knows the exact offset for the nested Animal class and for all the other classes when this branch is taken. Usually, the vftable assignation marks the start of a class, being its first member, while the vbtables marks the second member (if there’s vftables in place too) for a class.

Lion Constructor
We get into detail for the inlined Lion constructor. An interesting thing to note here, remember when we first talked in part 1 about trade-offs between solving redundancy but worsening execution time when using virtual classes? Well, here the pattern is more clear.
Each time we access a virtual class’s member, we first require getting the vbtable for it, adding an offset to get to the Nth member, dereference said member in the vbtable to get the class offset and add it to the this pointer. For a CPU, this translates in using precious cache to store dereferenced memory temporarily, and in the worse case (cache miss) it will have to calculate and dereference all again. If it just were register values getting moved around or arithmetically operated with, it wouldn’t be a big issue. Although this may seem small, the dereferencing overhead and calculations adds up on a larger scale.

Having showed the underlying offsets and dereferences, we’ll use a more palatable version for this ASM. The purpose of these intermediate calculations may now seem more clear, but the downside is that offsets to class members are not shown. Usually one would get to know and understand the structs used to represent these classes while reading the ASM representation of a program, and using the offset and operand type information to one’s advantage. For instance, we can deduce the Lion.strength field is a double as it’s assigned its value directly from a 64-bit floating point register (xmm0).

We can now move to the decompiled version to see what’s happening more clearly, assuming we already know how the underlying structs are laid out. At first, the vftables for Lion and Animal are assigned from 2 different vftables. The first one is for function members “introduced” in this class (Lion), while the second one corresponds to function members already introduced in Animal.
Shortly after, the construction displacement is set to the difference between what would be our dVA2 and dVA1, corresponding to the offset from the vbtable for the inherited version of the Lion class to the Animal class (0x68), and the offset from the vbtable of the non-inherited Lion class version to the Animal class (0x20), respectively.

The constructor then assigns the initial values to the data members for Lion (color, strength) and finally it calls custom code for the constructor, performing a write to std::cout for the string "Lion Constructor: Greek Chimaera" (the Animal.species field). As you may have noticed, all the initialization steps where done without an external constructor. This occurs because the compiler deduces we aren’t using the Lion class anywhere else on this program, so it doesn’t bother creating it’s own constructor for other use cases.
Before leaving, we’ll address the contents of the 2 vftables. The first one corresponds to the function members introduced in the Lion class (calculatePower, showColor), while the other one contains function members introduced in the Animal class (Destructor, showInformation, getType, showMemoryLayout). One thing to notice is that from these 4 methods, the 2 first ones are overridden in Lion, and thus correspond to adjustors, while the last 2 ones are represented with __purecall, meaning they are pure methods (they weren’t implemented in Animal).

Goat Constructor
The inlined constructor for Goat follows the same patterns as the inlined constructor for Lion, except it now uses other offset (this+0x20) to reach the inherited Goat class inside the derived Chimera class.

Looking at the decompiled version, we can see it mostly performs the same as before. It sets the vftable for the nested Goat class and the Animal class as the first fields using different vftables. Then, it sets the construction displacement as the difference between the offsets from before but now accounting for the Goat class (0x48 - 0x20). The constructor then assigns the initial values to the data members (sound, numHorns) for this class and as a final step, it calls the custom code for this constructor, which prints to std::cout a similar string as before along with the Animal.species field again.

The 2 vftables for Goat and Animal correspond to the following vftables.

The first one has the newly introduced function members for the Goat class (calculateDefense, talk), while the second one contains the 4 methods introduced in the Animal class. The 2 first ones (~Goat, showInformation) correspond to the overridden methods and thus are adjustors, while the last 2 ones (getType, showMemoryLayout) are pure methods.
Snake Constructor
The Snake constructor follows the exame same pattern as the previous 2 classes, so we may elegantly summarize it with the following picture.

The vftables for the Snake class correspond to: firstly the introduced function members (calculateRegenerationRate), while the other one would correspond to the overridden destructor (~Snake) and the overridden showInformation method introduced in the Animal class. The last 2 function members for the second vftable are the not-yet implemented getType and showMemoryLayout, shown as __purecall.

Data Members and Function Members
Shortly after finishing to initialize the nested Snake class inside Chimera, we finally manage to get to the final overwrite of the vftables for the inherited classes inside the derived class. Previously, all assignments were temporary, as they were only useful in case the virtually inheriting class’ methods were called prior to finishing construction for a Chimera object.
Continuing with this class’ constructor, the construction displacement ends its useful life and is set to 0, then the only data member (fireballs) for Chimera is set with an initial value passed as an argument, and finally the custom code is called. This follows the same patterns used with the inlined inherited class constructors of Lion, Goat and Snake.

The vftables for Chimera are related to the vftables from the inherited classes, were if a function corresponds to an adjustor, it’s because the function member was overridden by this current class or one of the previous classes (adjustors can be nested inside one another if necessary). If a method isn’t overriden, then the function member is called directly without and adjustment in-between, as is the case with Lion::showColor, Goat::talk and Snake::calculateRegenerationRate.
Note: If a new virtual function member was “introduced” on the
Chimeraclass, it would be appended to the firstvftable(Lion) that overlaps with the start ofChimera. That is the one considered as the “main”vftablefor the class.

Something else we didn’t see in the nested classes is that in Chimera, the methods from Animal that were previously pure (getType, showMemoryLayout) are finally implemented, yet they are still considered as “overridden” (they are adjustors that jump to their new implementations). An ASM representation of the adjustor for Chimera::getType and its decompiled version are shown below. As you may notice, the this pointer passed to this function member marks the start of the Animal class, which introduced this method when declared.


Structs and Class Layouts
As of now, a skilled reverse engineer would have already taken into account the different offsets for the virtually inheriting and virtual classes, along with their vftables locations and contents. Besides this, the vbtables for each virtually inheriting class would have already be created as new structs in order to work with them more easily. Lastly, the skilled reverse engineer would also have the structs for each virtually inheriting class, including their data members and their corresponding types, just by using the constructor’s information and the program’s context where a field is being used.
If you haven’t got it yet, it’s okay, as learning this takes quite some time to get it right. In order to fasttrack things, I’ll briefly show the criteria used by MSVC to lay out a virtually inheriting class, taken straight from Jan Gray’s “C++ Under the Hood”2.
- The non-virtual inherited classes are put at the start of the derived class (this includes
vftablesand members). - A
vbtablemember is added if the previous non-virtual inherited classes didn’t have one. - Members for the derived class come right after.
- Padding or non-used fields will be used if
structsize is not aligned to 8 or 16 bytes. - Finally, all of the virtual classes are added (this includes their
vftablesand members).
Following the previous criteria, we manage to create the following layouts and IDA structs for each class. Notice how each virtually inheriting class has 2 sizes depending on where if it’s being inherited or if it’s the derived class. Also, all adjustors are only created for virtual classes, and not for any virtually inheriting class, even if it’s being overridden as it’s the case with Lion::calculatePower, Goat::calculateDefense and Snake::calculateRegenerationRate.
Chimera

Animal

Lion

Goat

Snake

Virtual Method Calls
Continuing with the execution flow on main for this program, we see it calls several function members of the Chimera object. The key thing to notice here is that the this pointers passed to the different methods all belong to the class that introduced them.

For instance, for a not-overridden method such as Goat::talk, the start of the nested Goat class inside Chimera is passed as its this pointer just adding the known offset 0x20. As Goat is not a virtual class it doesn’t require usage or access to the vbtable.

For functions that were overriden, such as Goat::calculateDefense, the this pointer is still offset to the start of the nested Goat class through this = this + 0x20 and it’s very similar to the previous example. No usage of a vbtable nor calls to an adjustor function.

However, when calling an overridden function of the virtual class Animal, the result is that it first fetches the offset to the nested Animal class through the vbtable to be able to call a method inside its vftable. That method is an adjustor function.

This fetching and dereferencing is repeated each time any data member or function member for this virtual class needs to be reached. The compiler doesn’t have any sort of contextual memory to re-use registers or store commonly used pointers (like the adjusted this pointer for Animal) somewhere else before interacting with a virtual class. It will perform the same vbtable fetching, dereferencing and calculation each single time.

Run-Time Type Information
Let’s have a look at the Run-Time Type Information for the 5 custom classes we have in this PE, in order to properly identify patterns for future references and see what class information we can get if RTTI is enabled.
The checks we’ll be making have the purpose of understanding the field values of the Class Hierarchy Descriptor, which contains the Base Class Array for a class. Then we will check the contained Base Class Descriptors on the previous struct.
The comparison will be performed between our virtual class (Animal), a virtually inheriting class (Lion) and the only derived class (Chimera).
Virtual Class
We can find the Complete Object Locator (COL) for a class through substracting 8 bytes from the start of a class’ vftable.

The address contained here will take us to the struct of the COL. This struct contains the Type Descriptor for a class, which holds the mangled name of said class. We are not interested in the name this time, so we go right to the address pointing to the Class Hierarchy Descriptor.

After reaching the full struct, we get to the field numBaseClasses, which holds the number of base classes that conform this class (1). This field tells us that RTTI counts the same class that contains itself as it’s own base class. Next to it we have the pointer to the Base Class Array for Animal.

The full struct for the Base Class Array just contains the pointers to the Base Class Descriptors for the Animal class. In this case is just one. The parameters inside it are the fine loot we are searching for. Let’s follow the pointer.

We finally got to the relevant part for extracting info about hierarchies. This will be our baseline for vainilla virtual classes. The relevant fields are where, which is a struct called PMD (we’ll show it soon),numContainedBases and attributes. These are defined in <rttidata.h> and PMD is defined on <ehdata_forceinclude.h>. From now on ignore the IDA auto-generated comments inside Base Class Descriptor, as they are misleading (sorry Igor!).

From inspecting <rttidata.h>, the see that the Base Class Descriptor is actually a struct called _RTTIBaseClassDescriptor. This one contains a field called numContainedBases that counts the number of inherited classes inside a derived class. For Animal it’s set to 0.

There’s another sub-struct inside _RTTIBaseClassDescriptor called Pointer-to-Member Data (PMD) which tells us the displacements or offsets to reach specific points of interest inside a class.
The first one (mdisp) indicates the offset from the start of the class to the first data member, the second one (pdisp) corresponds to the offset from the start of the class to the vbtable, and the third one (vdisp) corresponds to the offset from the start of the vbtable to the adequate offset for this class.
In this case, Animal as a single virtual class has mdisp = 0 because it first members start right away (after the vftable), pdisp = -1 which means that it doesn’t have a vbtable, and vdisp = 0 as the class doesn’t have a vbtable.

Finally, the attributes field consists of a bit-field where different bits correspond to different meanings (attributes) for the described class. Lukasz Lipski has a great post3 describing each flag’s purpose with more detail. In this case, Animal has the default 0x40 which means basically that this class has a pointer to class descriptor (something that’s very common).

Virtually Inheriting Class
Now that we covered the basics for RTTI, we can go faster and jump straight to the interesting bits.
First, if we remember how to find the COLs for a class, we’ll notice quickly that besides having 2 different vftables, it also has 2 different COLs. This may or may not come as a surprise, but the 2 COLs have a different purpose according to the class from which it is inherited from. Just as vftables are unique for a class while it’s derived or just virtually inheriting, the same happens for the COLs and some of their sub-structs, even if they’re for the same underlying class. Both Type Descriptors contain the mangled name Lion.

The first COL corresponds to the COL that Lion should have when it’s a 0x48 byte-sized struct (it’s the final derived class that virtually inherits from Animal) and it starts at offset 0. The second one corresponds to Lion but hinting at the nested Animal class, as the fields offset (0x28) and cdOffset (0x4) show. The actual definition for offset here in the COL is the offset from the start of the class to the start of the nested class (that means the start of the nested Animal), while cdOffset contains the actual offset to the construction displacement for this variation of Lion.

Luckily, that’s just it where things differ. The same Class Hierarchy Descriptor for Lion is pointed in both of their COLs. The numBaseClasses accurately tells the Lion class has 2 base classes (Lion and Animal).

Then, looking at the Base Class Array, we notice that it also references both Lion and Animal, however the Base Class Descriptor for Animal is different from the one we showed for the single Animal class.

The Base Class Descriptor for Lion has fairly regular values except for numContainedBases which is 1. This can be interpreted as Lion contains 1 class besides itself, which is Animal. The other where field values tell that this class has no vbtable.

However the Base Class Descriptor for Animal (nested inside Lion) shows the virtually inheriting characteristics. For instance, the numContainedBases is 0, same as the regular Animal class. The where field (struct PMD) has as its pdisp = 8, which is the accurate offset to the vbtable from the start of the Lion class. Then, vdisp = 4 which means that 4 bytes after the start of the vbtable one will reach the offset for the virtual class (Animal). Finally, we have the attributes bit-field set to 0x10 | 0x40 == BCD_VBOFCONTOBJ | BCD_HASPCHD. This means this current base class is virtually inheriting (this is correct).

Overall, we can trust the RTTI information for a virtually inheriting class without ambiguity or misleads, knowing that the COL related to the virtual class
vftablewill hold the offset to the start of the virtual class. The info RTTI gives for a virtually inheriting class is mostly about the class inheritance tree than for data member offsets or types, so you could reconstruct the tree from it without looking at the code, but will still need to read the constructors and other functions to understand which ones are the data members and function members.
Derived Class
Oh boy, let’s see how it goes for Chimera. We can already imagine that we’ll see 4 different COLs, and in that case… you were right.

But don’t worry, it’s just there that this same COLs differ implicitly. The actual Class Hierarchy Descriptor and Base Class Array are the same for all of these COLs. The first one, related to the Lion vftable has offset and cdOffset set to 0. While the second one, related to the vftable for Goat shows an offset = 0x20 and cdOffset = 0. This is accurate again, as that’s the correct offset to reach the nested Goat class inside Chimera. Still no indications of a vbtable.

Continuing, the third COL related to the Snake class (and vftable) contains offset = 0x40 and cdOffset = 0 again. The first field is again accurate to the offset to reach the Snake class inside Chimera. Then, following with the COL related to Animal shows that besides having offset = 0x70, it has cdOffset = 4. Finally an indicator of a vbtable, and again, accurate offset to the start of class Animal inside Chimera.

Continuing with the unique Class Hierarchy Descriptor, we find something unexpected yet funny. The numBaseClasses is 7, which means it’s over-counting Animal 3 times as if it were simple multi-class inheritance. However, we know from the compiled generated constructors, that the Animal class is always just included once inside Chimera.

The attributes bit-field for Class Hierarchy Descriptor is finally non-null, with 3 as its value. This is translated using the next flag definitions from <rttidata.h>. From it we can see 3 == 0x1 | 0x2 == CHD_MULTINH | CHD_VIRTINH. These 2 flags mean that this class hierarchy contains at least one virtual class somewhere (Virtual Inheritance), and that the derived class inherits from more than one class (Multi-Class Inheritance).

Remember how numBaseClasses in the Class Hierarchy Descriptor was 7? Now it makes sense. The Base Class Array holds pointers to 7 Base Class Descriptors, for which 3 are the same (Animal). The order of the classes in the Base Class Array makes it seem as if it’s always interpreted as multi-class inheritance, where non-unique classes that should be unique are repeated redundantly.

The Base Class Descriptor for Chimera has the common field values except for numContainedClasses, where it’s 6 (3 Animal, Lion, Goat and Snake).

The Base Class Descriptor for Lion, Goat and Snake look regular too, except for the mdisp sub-field where each one holds the offset to reach that class inside Chimera. These mdisp are 0, 0x20 and 0x40 respectively.

Finally, we can see that the Base Class Descriptor for Animal is the exact same one used when analyzing the virtually inheriting class. The attributes point to virtual inheritance for this specific class (BCD_VBOFCONTOBJ | BCD_HASPCHD), the vbtable offset is 8 (pdisp = 8) and the correct index offset in the vbtable is 4 (vdisp = 4). Nothing new here.

What we can reconstruct from this analysis corresponds to the complete inheritance tree for a derived class, and the inheritance characteristics. We saw where the inherited classes are laid out in the derived class (located through their
Base Class Descriptoroffsets or their COLoffset). We saw which of these inherited classes virtually inherit from another class (through contiguity of the virtual class to their inheriting classes in theBase Class Array), and which of these classes is the virtual class (through theBase Class Descriptorfields likeattributes). For the virtual class, we can get itsvbtablelocation (pdisp) and the offset to the correctvbtableindex (vdisp). We would still have to look at the constructors and other functions to get a class data and function members.
Closing Remarks
Wow, that surely was a lot! With these words we conclude the Virtual Inheritance series until a new feature arrives. Although, such a change in the MSVC algorithms related to inheritance is very unlikely. The theory for it has remained the same through the decades, so learning this “Old New Thing” comes with its own rewards for analysing more complex C++ programs.
I hope these 2 posts helped you understand this not-so-documented feature in a didactic way. See you on the next series!
-
Chimera (mythology). (2026, March 25). In Wikipedia. https://en.wikipedia.org/wiki/Chimera_(mythology) ↩︎
-
Gray, J. (1994, March). C++: Under the Hood. https://www.openrce.org/articles/files/jangrayhood.pdf ↩︎
-
Lipski, L. (2021, June 6). RTTI Internals in MSVC. https://www.lukaszlipski.dev/post/rtti-msvc/ ↩︎