首页
社区
课程
招聘
[原创]逆向分析 C++继承内存分布(带虚函数) 及动态绑定实现
发表于: 2010-7-15 11:47 16314

[原创]逆向分析 C++继承内存分布(带虚函数) 及动态绑定实现

2010-7-15 11:47
16314

昨晚断网,于是乎闲来无事,想彻底研究一下C++的继承关系下的内存结构(带虚函数的),开IDA分析,经过不断的分析,发现C++的内存结构很是复杂,很多东西都是我以前完全不知道的,一下所有内容均是个人午夜至凌晨的原创逆向分析,有很多有用的细节,个人不敢藏着掖着,特攥写此文公布细节,有用的取之,不用的勿拍砖,本人菜鸟一个,大牛请飘过

C++继承关系(含虚函数) 内存结构分析 以及 动态绑定的细节分析
一.        说明
本文较为深入的研究C++的继承(含多重继承)情况下带虚函数时的实例内存结构,较为深入的剖析了继承实例间是如何组织的,以及动态绑定的实现细节。

二.        依据
以下阐述的细节均依据VS2005 生成的DEBUG模式程序在IDA Pro5.2反编译所得,部分数据结构根据程序分析得出。

三.        术语说明
对于以下将频繁用到的术语,我可能简写为如下
classX:RTTI_COL x 原语:classX:RTTI Complete Object Locator {for x}
classX:RTTI_CHD   原语:classX:RTTI class Hierarchy Descriptor
classX:RTTI_BCD   原语:classX:RTTI Base class Descriptor
以上简写均采用单词首字母缩写,其中Derive表示派生类,x表示Derive的一个基类,classX表示以上任意类

四.        分析
1.        派生类内存分布及虚表

                                                           Derive :RTTI_COL x
vfTable for base A        ---------------------------->>   ------------------------------------
                                                                                virtual Derive::fun1(overwrite
                                                                                by derive class) for base A
data of base A                                                        virtual A::fun2(inherit from
                                                                                 base A) for base A
  vfTable for base B                                                                  . . . . . .
                                                                                ------------------------------------
  data of base B                                                       virtual fun(if derive own virtual
  
data of Derive itself                                                  itslef) for derive * perhaps not

                         Figure 1:派生类实例对象内存分布及虚表映射

如上图,其中左部为派生类class的实例对象内存格局(继承来自A,B),其中第一项为base A的虚表地址(A,B数据在class实例中布局顺序是按照Derive在声明时继承顺序决定的),从图中箭头可知base A的虚表地址指向了一个数组,里面存放了Derive对应的虚函数地址:
① 首先存放重写过的base A声明的的虚函数地址(如果虚函数是从基类继承过来的,那么虚表中存放的也是base A中该函数地址)。
② 然后存放Derive自己声明的虚函数(可能没有)。

*注意:vfTable地址指向的虚表是从右边表格黄色以下位置开始,黄色以上是虚表附加信息,不为虚表所有,为本人IDA Pro反汇编分析所得。
其中Derive :RTTI_COL x是定位对象所使用的相关数据结构,其结构经反汇编如下:

2.        classX :RTTI Complete Object Locator {for x}
Field                        Length                 Remark
reserve_1                  4               not used,filled with 0x00
offset_x                     4               the offset of vfTable( for x) and
                                                 the base address of classX
reserve_3                  4                not used,filled with 0x00
pTypeDescriptor        4               address of classX:RTTI Type Descriptor
pHierarchyDescriptor 4               address of classX:RTTI class Hierarchy Descriptor
             Figure 2:  classX : RTTI Complete Object Locator {for x}反汇编结构

offset_x字段描述 基类x在classX实例内存空间中的起始地址相对于classX基址的偏移(也就是x的vfTable字段在classX内存布局中的偏移)
pHierarchyDescriptor字段存放classX:RTTI class Hierarchy Descriptor 结构的地址,该结构描述了Derive类的内存结构层次信息。

***Remark:对于classX为派生类Derive情况时,该结构描述了基类x相对于Derive的信息,此时表格中所有classX均应以Derive替换;但是当classX为基类x情况时,该结构描述了基类自身的信息,此时表格中所有classX均应以x替换

3.        classX:RTTI class Hierarchy Descriptor
Field                     Length          Remark
reserve_1                4              unknown
reserve_2                4              unknown
dwArrayElemCount        4              the count of the element inside pBaseClassArray
pBaseClassArray     4              address of classX:RTTI Base class Array
                  Figure 3:  classX:RTTI class Hierarchy Descriptor反汇编结构

该结构详细描述了类实例中相关重要信息的的索引
pBaseClassArray字段存放classX:RTTI Base class Array数组基址。
dwArrayElemCount字段存放classX:RTTI Base class Array数组项数,包括自身一项和每个基类一项。

***Remark:对于classX为派生类Derive情况时,该结构描述了派生类Derive的内存结构层次信息,此时表格中所有classX均应以Derive替换;但是当classX为基类x情况时,该结构描述了基类自身的内存结构层次信息,此时表格中所有classX均应以x替换

4.        classX:RTTI Base class Array
此数组存放了该类实例对象所需的所有布局信息。数组中每项均存放了一个类描述符classX:RTTI_BCD的地址,其中实例自身的类描述符地址放在数组第一项,然后存放基类描述符地址

5.        classX:RTTI Base class Descriptor
Field                     Length            Remark
pTypeDescriptor       4              address of classX:RTTI Type Descriptor
dwBaseClassCount        4              the count of Base class of classX
Offset_X                  4              the offset of vfTable( for classX)                              
                                                and the   base    address of Derive
reserve_1                4              unknown,filled with 0xffffffff
reserve_2                4              unknown,filled with 0x00000000
reserve_3                4              unknown,filled with 0x00000040
       Figure 4:  classX:RTTI Base class Descriptor反汇编结构
        该结构中有几个特别需要注意的地方,首先该结构是从classX:RTTI class Hierarchy Descriptor –>classX:RTTI Base class Array引出的,所以此结构中的Offset_X数据都是相对于classX:RTTI class Hierarchy Descriptor中的classX对象而言

举例说明,从Derive:RTTI class Hierarchy Descriptor中引出的x:RTTI Base class Descriptor中的Offset_X 字段是x相对于Derive而言的;但是如果从x:RTTI class Hierarchy Descriptor中引出的x:RTTI Base class Descriptor中的Offset_X 字段则是x相对于x而言的

小结:根据上述数据结构,我们可以清晰的将一个类继承关系描述出来,当然只包括虚函数部分,我们甚至可以基于此信息来完成一个PE的类继承视图的扫描器,我们甚至有可能通过对一个基类的扫描而窥知其派生类的继承视图,对,这是有可能的,以上结构信息我相信还有其他用途可供开发研究,再次本人不予赘述。

补充部分(关于C++的虚函数的调用过程细节):
以下结果是根据IDA PRO5.2逆向反汇编分析出的,分析版本为Debug
        根据逆向分析,可知编译器在虚函数处理过程中完成了不少的工作,当然是编译时工作,根据分析发现虚函数表中函数分布是根据函数声明顺序来存放的,类本身的虚函数置于第一虚表中的末尾位置,这在派生类内存分布及虚表小节中有详细说明,而虚表地址字段又是根据派生类对基类的引用顺序来存放的,那么分析可知,对于某个虚函数而言,无论动态绑定后的结果如何,此虚函数在虚表中的偏移在编译时就能被确定了,那么我们可以认为编译器在对虚函数的绑定技术是基于 虚表基址+偏移地址 取内容来完成的。

        虚函数的编码问题,根据Debug版本逆向分析可知,对于虚函数中的this引用,编译器采用的是该函数所在虚表地址字段 – 虚表地址字段相对于本实例内存基址的偏移,这样就可得出该实例的内存基址,其中虚表地址字段相对实例基址的偏移在编译时就能确定。

        thiscall问题,根据被调用的虚函数所在的虚表来确定传入的this指针,虚函数所在虚表在编译时就能确定,此时,将虚表地址字段的地址作为this指针传入(其实也可理解为此虚函数声明所在的类实例的起始地址)。通过传入的this指针,虚函数根据基址相对偏移可以轻松定位到对象的真正基址,从而就能达到通过指针实现动态绑定技术。

以上内容均为原创分析,如需引用请注明出处。
                                     by kalrey   2010/07/15午夜至凌晨

        不知道看雪的文章怎么支持word文档格式,所以上面的格式很乱,勉强把格式调整的能看
        里面可能有部分数据有误,如发现请告知,不甚感激,文后附有word文档供下载


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 7
支持
分享
最新回复 (18)
雪    币: 146
活跃值: (33)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
哇,下载都不留名,真伤我心。。。。
2010-7-15 13:15
0
雪    币: 95
活跃值: (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
下来看看,谢谢。。。。。。。
2010-7-15 15:23
0
雪    币: 249
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
这个
Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI

struct RTTICompleteObjectLocator
{
    DWORD signature; //always zero ?
    DWORD offset;    //offset of this vtable in the complete class
    DWORD cdOffset;  //constructor displacement offset
    struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class
    struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy
};

struct RTTIClassHierarchyDescriptor
{
    DWORD signature;      //always zero?
    DWORD attributes;     //bit 0 set = multiple inheritance, bit 1 set = virtual inheritance
    DWORD numBaseClasses; //number of classes in pBaseClassArray
    struct RTTIBaseClassArray* pBaseClassArray;
};

struct RTTIBaseClassDescriptor
{
    struct TypeDescriptor* pTypeDescriptor; //type descriptor of the class
    DWORD numContainedBases; //number of nested classes following in the Base Class Array
    struct PMD where;        //pointer-to-member displacement info
    DWORD attributes;        //flags, usually 0
};

struct PMD
{
    int mdisp;  //member displacement
    int pdisp;  //vbtable displacement
    int vdisp;  //displacement inside vbtable
};
2010-7-15 15:44
0
雪    币: 146
活跃值: (33)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
额。。。。感谢,不过我确实没参考这个,昨晚断网的说。。。。
2010-7-15 16:15
0
雪    币: 191
活跃值: (95)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
不错,team509有一篇讲c++逆向的比楼主要具体一点点。
不过实际分析好像带有RTTI信息的类好像在整个工程只是很少一部分,比较遗憾呐。
2010-7-30 22:23
0
雪    币: 231
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
不下载也顶,多谢共享
2010-8-3 17:05
0
雪    币: 206
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢了,下载来看看...
2010-8-4 11:34
0
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
多谢分析,共享!
2010-8-5 10:12
0
雪    币: 189
活跃值: (16)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
写得挺好的,不过要是能够结合实例去逆向分析,这样效果会更好些。
2010-8-10 09:39
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
多谢分析共享,下载来看看
2010-8-10 14:17
0
雪    币: 129
活跃值: (1095)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
非常好!!!
2010-8-10 20:35
0
雪    币: 7
活跃值: (333)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
13
虚函数核心在于寻址。

两次寻址:
1、识别对象  (ecx作用  this指针)
2、查虚函数表  (edx作用  虚函数指针)

call edx     (为什么动态绑定呢?  普通成员函数都是call 0x????????这个地址通常是在编译期间决定的,但对于虚函数来说,是call edx,至于EDX里面到底放什么是由运行时决定的。)

就这两句话,真正理解了天下无敌。以上都是基于VISUAL C++ 6.0编译器。
2010-8-13 13:05
0
雪    币: 95
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
哇  太强悍了。偶来虚心学习下。
2010-8-14 17:09
0
雪    币: 209
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
用VS调试,进反汇编视图跟踪不就行了么,怎么还用IDA呢
2010-8-15 00:04
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
谢谢分享 支持一下
2010-8-15 09:12
0
雪    币: 74
活跃值: (27)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
下载看看有什么
2010-8-15 12:47
0
雪    币: 236
活跃值: (21)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
下载留名,支持原创!
2010-8-15 14:19
0
雪    币: 8847
活跃值: (4158)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
谢谢,今天正好在找这方面的东西!
2010-8-21 15:13
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
谢谢了,下载来看看...
2010-8-23 03:26
0
游客
登录 | 注册 方可回帖
返回
//