Skip to content

Working with C++ Types and Virtual Function Tables

Derived Classes

Structures in Binary Ninja can be derived from other structures, allowing C++ class hierarchies to be represented without having to redefine base class members. Defining the class hierarchy in Binary Ninja will also allow the cross references to reflect the hierarchy, such that base class members are cross referenced in all derived classes automatically.

In-memory layout of C++ classes is not defined by the standard and varies by compiler, so Binary Ninja provides a low-level representation for describing the class layout.

Defining a Derived Class or Structure

A derived class can be defined using the Create Structure dialog. From the Types sidebar this dialog can be opened with a default hotkey of s or by using the add menu on the right of the sidebar.

Create Structure menu

First, enter the offset and name of the base structure below the "Base structures" list. The offset should be where in the derived structure to place the base structure's members, which will usually be zero for single inheritance.

Adding a base structure

Click the add button to add the base structure to the new structure.

Base structure added

The offset will automatically update to be just after the added structure in case you need to add additional base structures. Click the Create button to create the new structure.

The new structure will appear in the types sidebar with special syntax to show the base structures and the inherited members:

struct __base(Foo, 0) Bar __packed
{
    __inherited int32_t Foo::field_0;
    __inherited int32_t Foo::field_4;
    __inherited int32_t Foo::field_8;
};

This syntax can also be used in the Create Types interface to create derived structures from C source. When creating types from the Create Types interface, __inherited members need only match the structure's size and can be more easily defined like this:

struct __base(Foo, 0) Bar
{
    __inherited Foo foo;
};

Derived structures can also be defined using the API. Use the base_structures property of the structure builder to do this. It takes a list of BaseStructure objects, which are defined using a Type and an offset.

s = TypeBuilder.structure()
s.base_structures = [BaseStructure(bv.types['Foo'], 0)]

Virtual Function Tables

Virtual functions are implemented by compilers using a virtual function table structure that is pointed to by instances of the class. The layout of these structures is compiler dependent, so when reverse engineering a C++ binary these structures must be created to match the in-memory layout used by the binary.

One of the most common tasks when reversing a C++ binary is discovering which functions a virtual function call can resolve to. Binary Ninja provides a "propagate data variable references" option in the Create Structure dialog to help with this. When this is enabled, pointers found in the data section that are part of any instance of the structure will be considered as cross references of the structure field itself. This allows you to click on the name of a virtual function and see which functions it can potentially call in the cross references view.

Defining a virtual function table

To create a virtual function table, select the "propagate data variable references" option when creating the structure in the Create Structure dialog. Alternatively, use the __data_var_refs or __vtable attributes (these are aliases of each other) when creating the structure from C source. Here is an example virtual function table structure:

struct __data_var_refs vtable_for_Foo
{
    void (* fizz)(struct Foo* this);
    void (* buzz)(struct Foo* this);
};

Now, apply the new virtual function table structure to each instance of the table in the data section. You can usually find pointers to them in the constructors.

Derived virtual function tables

Virtual functions are typically used in conjunction with derived classes. In order to get the best results from Binary Ninja, each class should have its own virtual function table structure, and the virtual function table structures should use the base structures feature described earlier to mirror the class hierarchy. For example, if the class Foo mentioned in the virtual function table above has a derived class of Bar, you would define a second virtual function table structure vtable_for_Bar that derives from vtable_for_Foo:

struct __base(vtable_for_Foo, 0) __data_var_refs vtable_for_Bar
{
    __inherited void (* vtable_for_Foo::fizz)(struct Foo* this);
    __inherited void (* vtable_for_Foo::buzz)(struct Foo* this);
    void (* beep)(struct Bar* this);
};

You should always define these derived virtual function table structures, even if they have the same members. This allows Binary Ninja to get more accurate results.

The last step is to ensure that the virtual function tables are pointed to correctly in the class itself. This is often the first member of the structure. Make sure that each class points to its own virtual function table structure. Derived structure members can be overridden simply by changing their type.

The example Foo and Bar classes would look like this:

struct Foo
{
    struct vtable_for_Foo* vtable;
    // Foo members
};

struct __base(Foo, 0) Bar
{
    struct vtable_for_Bar* vtable; // Overridden Foo::vtable
    // __inherited Foo members
    // Additional Bar members
};
Tip

Do not leave the vtable members deriving from the base class. You should always make a derived virtual function table structure for each new class, and override the vtable member to point at the corresponding structure. This will significantly improve Binary Ninja's cross references for virtual function calls.

Template Simplifier

The analysis.types.templateSimplifier setting can be helpful when working with C++ symbols.

Example

Consider the following C++ program:

class Animal
{
public:
    const char* name;

    virtual void make_sound() = 0;

    virtual void approach()
    {
        make_sound();
    }

    void greet()
    {
        printf("You:\n");
        printf("Hello %s!\n", name);
        printf("%s:\n", name);
        make_sound();
    }
};

class Flying
{
public:
    int max_airspeed;

    virtual void fly()
    {
        puts("Up, up, and away!");
    }
};

class Dog: public Animal
{
public:
    int bark_count;

    virtual void make_sound() override
    {
        puts("Woof!");
        bark_count++;
    }
};

class Cat: public Animal
{
public:
    int nap_count;

    virtual void make_sound() override
    {
        puts("Meow!");
    }

    virtual void approach() override
    {
        if (nap_count)
            puts("Zzzz...");
        else
            make_sound();
    }

    virtual void nap()
    {
        nap_count++;
    }
};

class Lion: public Cat
{
public:
    virtual void make_sound() override
    {
        puts("Roar!");
    }
};

class Bird: public Animal, public Flying
{
public:
    int song_length;

    virtual void make_sound() override
    {
        for (int i = 0; i < song_length; i++)
            puts("Tweet!");
    }

    virtual void approach() override
    {
        fly();
    }
};

Defining the base classes

First, we define the base classes Animal and Flying. We leave the vtable member as a void* for now, since we will need these structures defined first:

struct Animal
{
    void* vtable;
    char* name;
};

struct Flying
{
    void* vtable;
    int32_t max_airspeed;
};

We can use these structures in the member functions to see better results in the decompiler:

int64_t Animal::greet(struct Animal* this)
{
    _printf("You:\n");
    _printf("Hello %s!\n", this->name);
    _printf("%s:\n", this->name);
    return *(int64_t*)this->vtable();
}
Tip

this is a keyword in the type parser, so if you try to define the function prototype with this as the name of a parameter, you will get an error. You can enclose the parameter name with backticks to work around this. Here, the parameter declaration would need to be Animal* `this` to pass the type parser.

Defining the derived classes

The Dog, Cat, and Lion classes can be defined by using the derived structure feature, with the base class at offset zero. These classes will look like this at this point:

struct __base(Animal, 0) Dog
{
    __inherited void* Animal::vtable;
    __inherited char* Animal::name;
    int32_t bark_count;
};

struct __base(Animal, 0) Cat
{
    __inherited void* Animal::vtable;
    __inherited char* Animal::name;
    int32_t nap_count;
};

struct __base(Cat, 0) Lion
{
    __inherited void* Animal::vtable;
    __inherited char* Animal::name;
    __inherited int32_t Cat::nap_count;
};

Applying these structures to the parameter types will improve decompilation of these classes:

void Dog::make_sound(struct Dog* this)
{
    _puts("Woof!");
    this->bark_count = (this->bark_count + 1);
}

void Cat::nap(struct Cat* this)
{
    this->nap_count = (this->nap_count + 1);
}

Multiple inheritance

The Bird class derives from both Animal and Flying. The typical in-memory layout of this class is to place Animal at offset zero, Flying just after Animal, and any members from the derived class after both. We set up this structure in the Create Structure dialog by first adding Animal as a base structure and then adding Flying. The Create Structure dialog will automatically adjust the offset as you add structures. The dialog should look like this after the base structures are added:

Multiple base structures

After defining and adding the additional member, the Bird structure looks like this:

struct __base(Animal, 0) __base(Flying, 0x10) Bird
{
    __inherited void* Animal::vtable;
    __inherited char* Animal::name;
    __inherited void* Flying::vtable;
    __inherited int32_t Flying::max_airspeed;
    int32_t song_length;
};

Base class virtual function tables

Now it is time to start adding the virtual function tables, starting with the base classes. Add the virtual function table structures for Animal and Flying. Ensure to check the "propagate data variable references" option when creating any virtual function table. The virtual function table structures will look like this:

struct __data_var_refs vtable_for_Animal
{
    void (* make_sound)(struct Animal* this);
    void (* approach)(struct Animal* this);
};

struct __data_var_refs vtable_for_Flying
{
    void (* fly)(struct Flying* this);
};

Change the vtable members in the base classes to point to the virtual function table structures:

struct Animal
{
    struct vtable_for_Animal* vtable;
    char* name;
};

struct Flying
{
    struct vtable_for_Flying* vtable;
    int32_t max_airspeed;
};

Now, go to the data section and apply the virtual function table structures to the virtual function tables themselves:

00004020  struct vtable_for_Animal _vtable_for_Animal = 
00004020  {
00004020      void (* make_sound)(struct Animal* this) = nullptr
00004028      void (* approach)(struct Animal* this) = Animal::approach()
00004030  }

00004050  struct vtable_for_Flying _vtable_for_Flying = 
00004050  {
00004050      void (* fly)(struct Flying* this) = Flying::fly
00004058  }

Derived class virtual function tables

The virtual function tables for the derived classes should be derived from the virtual function tables for the base classes using the derived structure feature. Define the Dog and Cat virtual function tables with a base of vtable_for_Animal:

struct __base(vtable_for_Animal, 0) __data_var_refs vtable_for_Dog
{
    __inherited void (* vtable_for_Animal::make_sound)(struct Animal* this);
    __inherited void (* vtable_for_Animal::approach)(struct Animal* this);
};

struct __base(vtable_for_Animal, 0) __data_var_refs vtable_for_Cat
{
    __inherited void (* vtable_for_Animal::make_sound)(struct Animal* this);
    __inherited void (* vtable_for_Animal::approach)(struct Animal* this);
    void (* nap)(struct Cat* this);
};

Define the Lion virtual function table with a base of vtable_for_Cat:

struct __base(vtable_for_Cat, 0) __data_var_refs vtable_for_Lion
{
    __inherited void (* vtable_for_Animal::make_sound)(struct Animal* this);
    __inherited void (* vtable_for_Animal::approach)(struct Animal* this);
    __inherited void (* vtable_for_Cat::nap)(struct Cat* this);
};

Change the vtable inherited members in the derived classes to point to the correct virtual function table structure for that class:

struct __base(Animal, 0) Dog
{
    struct vtable_for_Dog* vtable;
    __inherited char* Animal::name;
    int32_t bark_count;
};

struct __base(Animal, 0) Cat
{
    struct vtable_for_Cat* vtable;
    __inherited char* Animal::name;
    int32_t nap_count;
};

struct __base(Cat, 0) Lion
{
    struct vtable_for_Lion* vtable;
    __inherited char* Animal::name;
    __inherited int32_t Cat::nap_count;
};

Apply the new virtual function table structures to the virtual function tables in the data section:

00004078  struct vtable_for_Dog _vtable_for_Dog = 
00004078  {
00004078      void (* make_sound)(struct Animal* this) = Dog::make_sound()
00004080      void (* approach)(struct Animal* this) = Animal::approach()
00004088  }

000040b0  struct vtable_for_Cat _vtable_for_Cat = 
000040b0  {
000040b0      void (* make_sound)(struct Animal* this) = Cat::make_sound
000040b8      void (* approach)(struct Animal* this) = Cat::approach()
000040c0      void (* nap)(struct Cat* this) = Cat::nap()
000040c8  }

000040f0  struct vtable_for_Lion _vtable_for_Lion = 
000040f0  {
000040f0      void (* make_sound)(struct Animal* this) = Lion::make_sound
000040f8      void (* approach)(struct Animal* this) = Cat::approach()
00004100      void (* nap)(struct Cat* this) = Cat::nap()
00004108  }

At this point virtual method calls in the decompilation should start looking more correct:

void Animal::greet(struct Animal* this)
{
    _printf("You:\n");
    _printf("Hello %s!\n", this->name);
    _printf("%s:\n", this->name);
    this->vtable->make_sound(this);
}

Virtual function tables with multiple inheritance

The Bird class is derived from both Animal and Flying, so it has two different virtual function tables, one for each base class. Define both virtual function table structures, each deriving from the base class that it came from:

struct __base(vtable_for_Animal, 0) __data_var_refs vtable_for_Bird_as_Animal
{
    __inherited void (* vtable_for_Animal::make_sound)(struct Animal* this);
    __inherited void (* vtable_for_Animal::approach)(struct Animal* this);
};

struct __base(vtable_for_Flying, 0) __data_var_refs vtable_for_Bird_as_Flying
{
    __inherited void (* vtable_for_Flying::fly)(struct Flying* this);
};

There were two inherited vtable members in the Bird structure. Replace the types of these with the new derived virtual function table types:

struct __base(Animal, 0) __base(Flying, 0x10) Bird
{
    struct vtable_for_Bird_as_Animal* animal_vtable;
    __inherited char* Animal::name;
    struct vtable_for_Bird_as_Flying* flying_vtable;
    __inherited int32_t Flying::max_airspeed;
    int32_t song_length;
};

Apply the virtual function table structures to the corresponding virtual function tables in the data section:

00004130  struct vtable_for_Bird_as_Animal _vtable_for_Bird_as_Animal = 
00004130  {
00004130      void (* make_sound)(struct Animal* this) = Bird::make_sound()
00004138      void (* approach)(struct Animal* this) = Bird::approach()
00004140  }

00004150  struct vtable_for_Bird_as_Flying _vtable_for_Bird_as_Flying = 
00004150  {
00004150      void (* fly)(struct Flying* this) = Flying::fly
00004158  }

Now the decompiler is able to determine the destination of a virtual function call in Bird:

void Bird::approach(struct Bird* this)
{
    this->flying_vtable->fly(&this->flying_vtable);
}

The transformation on the this parameter is the compiler obtaining a pointer to Flying from the Bird instance so that it can be used by the Flying class.

Cross references

Now that the data structures have been set up, Binary Ninja can provide helpful cross references when navigating the C++ program. For example, clicking on the make_sound member in the Animal structure will populate the cross references view with the list of functions that implement the method:

Virtual function table references

These cross references are aware of context when viewed from code. For example, in the following function:

void Cat::approach(struct Cat* this)
{
    if (this->nap_count == 0)
    {
        this->vtable->make_sound();
    }
    else
    {
        _puts("Zzzz...");
    }
}

Clicking on make_sound will populate the cross references with only the functions that are in Cat or derived classes of Cat:

Virtual function table references with context

Offset Pointers

When implementing multiple inheritance, sometimes compilers will define a pointer to a class as pointing into the middle of the memory layout of that class. If this is the case, you can use the __ptr_offset attribute on a structure to tell Binary Ninja that pointers to the structure are actually pointing at the given offset into the structure. For example, consider the following definition:

struct __ptr_offset(4) Foo
{
    int fizz;
    int buzz;
};

When dereferencing Foo*, Binary Ninja will treat buzz as the member being pointed to, with fizz being at offset -4.