我正在使用 IDA Pro 来反汇编具有 1600 多个类的 C++ 庞然大物,其中许多具有纯虚方法。
一些类也由多个基类组成,层次结构深达 5 级以上。
Ida PRO 支持创建指针结构来处理 vtables,但是由于重度多态性,一些最终类可以在同一个“槽”中有多个不同的 vtables,那么你如何组织 vtables?你如何告诉 IDA 在这个方法或那个方法中,实际上引用的是什么 vtable?
我正在使用 IDA Pro 来反汇编具有 1600 多个类的 C++ 庞然大物,其中许多具有纯虚方法。
一些类也由多个基类组成,层次结构深达 5 级以上。
Ida PRO 支持创建指针结构来处理 vtables,但是由于重度多态性,一些最终类可以在同一个“槽”中有多个不同的 vtables,那么你如何组织 vtables?你如何告诉 IDA 在这个方法或那个方法中,实际上引用的是什么 vtable?
这取决于最初使用的编译器,因为每个编译器都会创建略有不同的布局。您可以在网上找到大多数编译器的教程,我将重点关注 MSVC,因为这是我的经验,并且因为它提供了一个隐藏的编译器开关,用于打印我将要使用的类在内存中的布局方式用于说明。
正如您可能已经猜到的那样,您必须使用 C 结构重新创建 C++ 类。这是可能的,但很麻烦,特别是如果你有你说的那么多课程。一些减少烦人的提示:
struct
在Local Types子视图中而不是在Structures子视图中使用类似 C 的代码定义s 。以下示例可以粘贴为本地类型。
- 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
类中。嵌入式Animal
vftable 还获取所有虚拟Dog
方法并将它们添加到其末尾。的成员Dog
出现在Animal
s成员的后面:
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
通过让Dog
vftable 继承它来重用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
类中,并将Dog
s 成员分开。虽然Dog
-specific 虚拟方法仍然与Animal
vftable(也就是第一个基类)合并,但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
虚拟方法仍然附加到Animal
s vftable。然后Animal
s成员照常跟随。现在,未触及的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 布局:看起来并不太特别。该Animal
vftable还融合了我们对新的虚拟方法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
结构体创建非常相似,只是我们需要尊重Dog
aswell的第二个基类。
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;
};