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

某些类还由多个基类组成,层次结构深达5层以上。 br />
Ida PRO支持制作指针结构以处理vtable,但是由于繁杂的多态性,某些最终类在同一“插槽”中可以具有多个不同的vtable,那么如何组织vtable?您如何告诉IDA,在此方法或该方法中,实际上引用了哪个vtable?

评论

有趣的是,我通过编写自己的反汇编程序为类繁重的可执行文件解决了一次。在当前状态下,它可以从几个DLL中收集类成员和函数。但不是一个通用的解决方案:该代码非常依赖于编写该代码的特定编译器。

为什么我不赞成投票?

尽管这个问题已经很老了,请尝试对Hexrays使用HexRaysPy工具

#1 楼

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

您可能已经猜到了,必须使用C结构重新创建C ++类。这是可能的,但是很麻烦,尤其是如果您有多少门课。减少烦人的一些技巧:


关注您感兴趣的人。
从“最基础”类开始,例如在层次结构树顶部的那些。如果您在那里弄乱了东西,以后修复它可能会很昂贵:想象将一个被遗忘的成员添加到像智能指针基类这样的东西中,该类被数百个类继承,现在您必须调整所有这些。
您可能想在Local中使用类似C的代码来定义struct首先键入子视图,而不是在“结构”子视图中。以下示例可以粘贴为本地类型。
如果不确定反转结构,则构造函数通常会破坏某些成员的vftable和布局。调用它的函数可以通过为对象保留足够的字节来产生对象的总大小。




IDA 7.0通过自动识别和标记RTTI数据(如果可用)破坏了类层次结构和vftable,但是在多重继承的情况下,它错误地标记了哪个vftable属于哪个基类,这是它基于的IDA6脚本所没有的问题。我在IDAPython for 7.0中重写了它,以解决此问题以及与其他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;
};

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


IDA表示形式:



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;
};




转发在vftable的this参数中声明要使用它的类结构。
__thiscall调用约定是在MSVC中创建类方法所必需的。除了所有其他参数外,它隐式传递一个指针到ecx寄存器中的类实例。
不需要提供参数名称。

makeSound将是purecall,而setAge将成为修改我们成员的典型未知子对象。
将成员置于单独的结构中以进行继承(见下文)。 br />
让我们快速繁殖一个继承自DogAnimal,实现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的成员之后:

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 vftable继承Dog vftable来重用Dog vftable(在IDA中继承结构只是意味着将其前缀),然后添加Animal特定的虚拟功能。
成员发生相同的事情,这就是为什么我较早分离它们的原因。



多重继承

这需要一点点令人心碎。为此,我们可以杀死我们的狗(对不起,对您来说很残酷,我不好创造快乐的例子):



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布局:与Killable基类一样,它也将第二个Dog基类嵌入到Dog类中,并使Dog的成员分开。虽然Animal特定的虚拟方法仍与Killable vftable(又称第一基类)合并,但与Killable相关的虚拟方法位于单独的与Dog相关的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表示形式:我们在开始时就保留了一个特定于Animal的vftable,它在内部重用了Dog vftable,因为Animal虚拟方法仍然附加到Animal的vftable上。然后Killable的成员照常跟随。现在,由于没有任何内容合并到它们中,因此未修改的Dog结构随之出现。最后,我们的Dog::ctor成员将关注。如果将此与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的命名方式略有不同vftables参与多重继承。我发现手动处理很麻烦,因此我们在这里不再使用它。


让我们看看Dog伪代码中的样子: br />
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::vftable所需的vftable。但是没有什么意义:为什么将__vftable_Killable分配给我们的Dog?好吧,我在这里使用了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烦恼所困扰,双击这些名称以检查它们的实际位置。


我不建议将vftable的实际类型设置为它们的位置/名称:您一无所获,这只会使反汇编输出混乱。




奖金:从使用多重继承(ww)的类继承的类

这使我有些困惑,这也许是因为我依赖的网络上的大多数教程都没有涵盖它,或者可能是因为我只是傻瓜让我们从Terrier继承我们的Animal类。



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布局:似乎不太特殊。 Terrier vftable仍合并了我们Dog的新虚拟方法,其他所有东西都有其单独的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结构创建非常相似,只是我们还需要尊重q4312079q的第二个基类。



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;
};


评论


工作需要“ __thiscall”吗?

– savram
17-10-30在12:01

是的,否则,类方法和反编译器输出的调用约定格式不正确。 (评论已回答)。

–雷
18/12/17在11:16

作为参考,这里是7.2+生成的内容:hex-rays.com/products/ida/support/idadoc/1691.shtml

–Trass3r
10月1日10:23

这是一个传奇的答案,谢谢

–GuidedHacking
11月7日20:21