如何在 IDA Pro 中组织 vtables?

逆向工程 艾达 C++ 六线谱
2021-06-15 02:15:33

我正在使用 IDA Pro 来反汇编具有 1600 多个类的 C++ 庞然大物,其中许多具有纯虚方法。

一些类也由多个基类组成,层次结构深达 5 级以上。

Ida PRO 支持创建指针结构来处理 vtables,但是由于重度多态性,一些最终类可以在同一个“槽”中有多个不同的 vtables,那么你如何组织 vtables?你如何告诉 IDA 在这个方法或那个方法中,实际上引用的是什么 vtable?

1个回答

这取决于最初使用的编译器,因为每个编译器都会创建略有不同的布局。您可以在网上找到大多数编译器的教程,我将重点关注 MSVC,因为这是我的经验,并且因为它提供了一个隐藏的编译器开关,用于打印我将要使用的类在内存中的布局方式用于说明。

正如您可能已经猜到的那样,您必须使用 C 结构重新创建 C++ 类。这是可能的,但很麻烦,特别是如果你有你说的那么多课程。一些减少烦人的提示:

  • 专注于你感兴趣的那些。
  • 从“最基础”的类开始,例如层次树顶部的类。如果你在那里搞砸了一些东西,以后修复它可能会很昂贵:; 想象一下,将一个被遗忘的成员添加到由数百个类继承的智能指针基类之类的东西中,现在您必须调整所有这些。
  • 您可能希望首先structLocal Types子视图中而不是在Structures子视图中使用类似 C 的代码定义s 以下示例可以粘贴为本地类型。
  • 如果您不确定是否反转结构,构造函数通常会破坏 vftable 和某些成员的布局;调用它的函数可以通过为其保留足够的字节来产生对象的总大小。
  • IDA 7.0 通过自动识别和标记破坏类层次结构和 vftables 的RTTI 数据(如果可用)为您提供了很多帮助,但它错误地标记了哪个 vftable 在多重继承的情况下属于哪个基类,这是它所基于的 IDA6 脚本的问题没有。在 7.0 的 IDAPython 中重写了它来解决这个问题和其他与 MSVC 相关的问题,它还可以自动创建结构,如下所述。
  • IDA 7.2对自动检测和重新创建 C++ 结构更好的支持,但以下答案是在考虑 7.0 并使用 7.2 命名的情况下编写的。

根据您的多态性的花哨程度,您的 C 结构也必须或多或少花哨。因此,让我们先从简单的案例开始,然后逐步处理更复杂的案例。


无继承(基类)

最简单的情况根本不使用任何继承,只有一些成员和(纯)虚方法开始:

class Animal {
    int _age;
    Animal() { _age = 0; }
    int getAge() { return _age; }
    virtual void setAge(int value) { _age = value; }
    virtual void makeSound() = 0;
};

MSVC 布局:

class Animal    size(8):
        +---
 0      | {vfptr} // pointer to the vftable (s. below)
 4      | _age    // members of Animal
        +---

Animal::$vftable@:
        | &Animal_meta    // ignore meta for our examples
        |  0              // the (pure) virtual method follow
 0      | &Animal::setAge    
 1      | &Animal::makeSound

国际开发协会代表:

struct Animal;
struct Animal_vtbl {
  void (__thiscall *setAge)(Animal *this, int value);
  void (__thiscall *makeSound)(Animal *this);
};
struct Animal_mbrs {
  int _age;
};
struct Animal {
  Animal_vtbl *__vftable;
  Animal_mbrs __members;
};
  • 转发声明类结构以this在 vftable参数中使用它
  • __thiscall调用约定,需要在MSVC里创建类的方法。ecx除了所有其他参数外,它还隐式传递指向寄存器中类实例的指针
  • 不需要为参数提供名称。
  • makeSound将是一个purecall,并且setAge将是一个典型的未知子修改我们的成员。
  • 将成员放在单独的结构中以进行继承(见下文)。

单继承

让我们快速繁殖一个Dog继承自Animal,实现该makeSound方法,并添加一个新的虚拟方法来设置毛发颜色:

class Dog : public Animal {
    int _furColor;
    virtual void setAge(int value) { _age = value; }
    virtual void makeSound() { cout << "Woof Woof"; }
    virtual void setFurColor(int color) { _furColor = color; }
};

MSVC 布局:Animal基类只是嵌入在Dog类中。嵌入式Animalvftable 还获取所有虚拟Dog方法并将它们添加到其末尾。的成员Dog出现在Animals成员的后面

class Dog       size(12):
        +---
 0      | +--- (base class Animal)
 0      | | {vfptr}
 4      | | _age
        | +---
 8      | _furColor
        +---

Dog::$vftable@:
        | &Dog_meta
        |  0
 0      | &Dog::setAge
 1      | &Dog::makeSound
 2      | &Dog::setFurColor // Added behind the Animal methods!

IDA 表示:Animal保持结构不变,我们添加以下内容:

struct Dog;
struct Dog_vtbl : Animal_vtbl {
  void (__thiscall *setFurColor)(Dog *this, int color);
};
struct Dog_mbrs : Animal_mbrs {
  int _furColor;
};
struct Dog {
  Dog_vtbl *__vftable;
  Dog_mbrs __members;
};
  • Animal通过让Dogvftable 继承它来重用vftable (在 IDA 中继承结构只是意味着在前面添加它),然后添加Dog特定的虚拟函数。
  • 同样的事情发生在成员身上,这就是我早些时候将他们分开的原因。

多重继承

这需要一些头脑风暴。为此,我们可以杀死我们的狗(对不起,如果这对你很残忍,我不擅长创造快乐的例子):

class Killable {
    bool _isDead;
    virtual void kill() { makeDeathSound(); _isDead = true; }
    virtual void makeDeathSound() = 0;
};

class Dog : public Animal, public Killable {
    int _furColor;
    virtual void setAge(int value) { _age = value; }
    virtual void makeSound() { cout << "Woof Woof"; }
    virtual void setFurColor(int color) { _furColor = color; }
    virtual void makeDeathSound() { cout << "I'll call WWF, bark-blerg"; }
};

MSVC 布局:与Animal基类一样,它也将第二个Killable基类嵌入Dog类中,并将Dogs 成员分开。虽然Dog-specific 虚拟方法仍然Animalvftable(也就是第一个基类)合并,但Killable相关的虚拟方法在一个单独的Killable-related vftable 中,所以我们现在有:

class Dog       size(20):
        +---
 0      | +--- (base class Animal)
 0      | | {vfptr}
 4      | | _age
        | +---
 8      | +--- (base class Killable)
 8      | | {vfptr}
12      | | _isDead
        | | <alignment member> (size=3) // since _isDead is a 1-byte bool
        | +---
16      | _furColor
        +---

Dog::$vftable@Animal@:
        | &Dog_meta
        |  0
 0      | &Dog::setAge
 1      | &Dog::makeSound
 2      | &Dog::setFurColor // Dog methods still merged with Animal!

Dog::$vftable@Killable@: // All the Killable-related methods in here
        | -8 // offset for `this` pointer in Killable methods to get a Dog pointer
 0      | &Killable::kill
 1      | &Dog::makeDeathSound

IDA 表示:我们Dog在开始时保留了一个特定的 vftable,它在Animal内部重用vftable,因为Dog虚拟方法仍然附加到Animals vftable。然后Animals成员照常跟随。现在,未触及的Killable结构紧随其后,因为没有任何内容合并到它们中。最后,我们的Dog成员跟随。如果您将其与 MSVC 打印的偏移量进行比较,这是有道理的:

struct Killable;
struct Killable_vtbl {
    void (__thiscall *kill)(Killable *this);
    void (__thiscall *makeDeathSound)(Killable *this);
};
struct Killable_mbrs {
    bool _isDead;
};
struct Killable {
  Killable_vtbl* __vftable;
  Killable_mbrs __members;
};

struct Dog;
struct Dog_vtbl : Animal_vtbl {
  void (__thiscall *setFurColor)(Dog *this, int color);
};
struct Dog_mbrs { // No more base Animal members as they're split up now!
  int _furColor; 
};
struct Dog {
  Dog_vtbl *__vftable; // Still contains animal methods.
  Animal_mbrs __members_Animal; // Animal members come here separately.
  Killable_vtbl *__vftable_Killable;
  Killable_mbrs __members_Killable;
  Dog_mbrs __members;
};

IDA 7.2 对参与多重继承的 vftable 使用稍微不同的命名。我觉得手动处理比较麻烦,所以这里就不用了。

让我们看看它在Dog::ctor伪代码中的样子

Dog *__thiscall Dog::ctor(Dog *this)
{
  j_Animal::ctor((Animal *)this);
  j_Killable::ctor((Killable *)&this->__vftable_Killable);
  this->__vftable = (Dog_vftable *)&Dog::`vftable';
  this->__vftable_Killable = (Killable_vftable *)&Dog::`vftable';
  return this;
}

与构造函数一样,基类构造函数首先被调用。然后,Dog设置所需的 vftables 但有些事情没有意义:为什么Dog::vftable分配给我们的__vftable_Killable好吧,我在这里使用了 IDA 7.0 的命名,我之前提到的是它不再标记哪个 vftable 映射到哪个基类(与脚本不同)。使用 IDA 6.0 或我的 IDAPython 脚本,它会说:

Dog *__thiscall Dog::ctor(Dog *this)
{
  j_Animal::ctor((Animal *)this);
  j_Killable::ctor((Killable *)&this->__vftable_Killable);
  this->__vftable = (Dog_vftable *)&Dog::`vftable for Animal';
  this->__vftable_Killable = (Killable_vftable *)&Dog::`vftable for Killable';
  return this;
}

现在名称不一样并且更有意义,所以不要被这个 IDA 7 的烦恼所困扰,双击名称来检查它们实际上会在哪里。

我不建议将 vftables 的实际类型设置为它们的位置/名称:你什么也得不到,它只会使反汇编输出变得混乱。


奖励:从使用多重继承的类继承的类(wew)

这让我有一段时间有点困惑,也许是因为它没有包含在我非常依赖的网络上的大多数教程中,或者也许是因为我只是愚蠢。让我们继承自Dog我们的Terrier班级。

class Terrier : public Dog {
    int _annoyanceLevel;
    virtual void setAge(int value) { _age = value; }
    virtual void makeSound() { cout << "Bark Bark not Woof Woof"; }
    virtual void annoy() { _annoyanceLevel++; }
};

MSVC 布局:看起来并不太特别。Animalvftable融合了我们对新的虚拟方法Terrier,以及其他一切有其独立的vftable:

class Terrier   size(24):
        +---
 0      | +--- (base class Dog)
 0      | | +--- (base class Animal)
 0      | | | {vfptr}
 4      | | | _age
        | | +---
 8      | | +--- (base class Killable)
 8      | | | {vfptr}
12      | | | _isDead
        | | | <alignment member> (size=3)
        | | +---
16      | | _furColor
        | +---
20      | _annoyanceLevel
        +---

Terrier::$vftable@Animal@:
        | &Terrier_meta
        |  0
 0      | &Terrier::setAge
 1      | &Terrier::makeSound
 2      | &Dog::setFurColor
 3      | &Terrier::annoy // Animal even takes the Terrier methods (greedy!)

Terrier::$vftable@Killable@:
        | -8
 0      | &Killable::kill
 1      | &Dog::makeDeathSound

IDA 表示:它与我们的第一个Dog结构体创建非常相似,只是我们需要尊重Dogaswell的第二个基类

struct Terrier_vtbl : Dog_vtbl {
  void (__thiscall *annoy)(Terrier *this);
};
struct Terrier_mbrs : Dog_mbrs {
  int _annoyanceLevel;
};
struct Terrier {
  Terrier_vtbl *__vftable;
  Animal_mbrs __members_Animal;
  Killable_vtbl *__vftable_Killable;
  Killable_mbrs __members_Killable;
  Terrier_mbrs __members;
};