学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记
一、单类继承
- 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问限制仅限于编译层面,在编译过程中进行语法检查,因此访问控制不会影响对象的内存结构。
- 子类未提供构造函数或析构函数,而父类却需要构造函数与析构函数时,编译器会为子类提供默认的构造函数与析构函数。但是子类有构造函数,而父类不存在构造函数,且没有虚函数,则编译器不会为父类提供默认的构造函数。
1. 内存结构:
①先安排父类的数据
②后安排子类新定义的数据
说明:基于上述的内存排列方法,即父类数据成员被安排前面。不管是父类的对象,还是子类的对象,父类的数据成员在内存中相对于对象的首地址的偏移值都是一样的。而且成员数据访问都是基于this指针间接寻址的。所以,对于子类对象而言,使用父类指针或者子类指针都可以正确访问其父类数据。
2. 虚表:
- 虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的。
- 只要继承了父类,其派生类的虚函数表中的父类部分的排列就与父类一样。
- 被重载的虚函数在虚函数表中得到了更新(即override)。
- 子类新定义的虚函数会按照声明顺序紧跟其后。
3. 构造函数:
①先调用父类构造函数
②然后按照声明顺序调用成员数据变量的构造函数和初始化列表中指定的成员
③最后再执行子类构造函数的函数体。
说明:
①父类构造函数,虚表指针修改为指向父类的虚表,所以在父类构造函数内调用虚函数,调用的是父类的虚函数。
②子类构造函数,虚表指针修改为指向子类的虚表
4. 析构函数:
①先调用子类析造函数
②然后成员对象的析构函数,按照声明的顺序以倒序方式依次调用成员对象的析构函数。
③再执行父类构造函数
说明:
- 析构函数执行会首先设置虚表指针为自身虚表,再调用自身的析构函数。防止父类析构函数内调用子类对象的虚函数。
- 类有派生与继承关系,需要声明析构函数为虚函数。若析构函数不是虚函数时,当使用父类指针指向堆中的子类对象,并使用delete释放对象空间时,编译器会按照指针类型调用父类的析构函数,从而引发错误。
识别类之间的关系:
先定位构造函数,根据构造先后顺序得到与之相关的其他类。
再根据虚表,利用IDA中使用引用参考功能可得到所有的构造和析构函数。
二、多重继承
1. 内存排列:
- 数据成员的排列顺序由继承父类的先后顺序所决定,从左向右依次排列。
- 子类虚表指针的个数取决于所继承的父类的个数,有几个父类便对应几个虚表指针(虚基类除外)。
2.虚表
- 每个父类都有一个虚表,有几个父类便对应几个虚表;
- 子类的成员函数被放到了第一个父类的表中;
- 如果某个父类的虚函数都被子类重写(override),则该父类的虚表中会更新为子类重写(override)后的函数地址。
3.其他
- 将一个子类对象赋值给某个父类指针时,该父类指针便指向该父类所对应的虚表指针。
三、单类继承与多重继承比较:
- 单继承类
- 在类对象占用的内存空间中,只保存一份虚表指针
- 只有一个虚表指针,对应的也只有一个虚表
- 虚表中各项保存了类中各虚函数的首地址
- 构造时先构造父类,再构造自身,并且只调用一次父类构造函数
- 析构时限析构自身,再析构父类,并且只调用一次父类析构函数
- 多重继承类
- 在类中所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
- 根据所保存的虚表指针的个数,对应产生相应个数的虚表。
- 转换父类指针时,需要跳转到对象的首地址。
- 构造时需要按照继承顺序调用多个父类构造函数。
- 析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
- 当对象作为成员时,整个类对象的内存结构和多重继承相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可酌情还原;当父类或成员对象存在虚函数时,通过过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。
四、 示例
1. 单类继承:
C++源码
1 #include <iostream>
2 using namespace std;
3
4 class Base {
5 public:
6 Base(){ nBase= ;printf("CBase"); }
7 ~Base(){ printf("~CBase"); }
8 virtual void f() { printf("Base:f()");}
9 virtual void g() { printf("Base:g()");}
private:
int nBase;
};
class Derive : public Base {
public:
Derive(){ nDerive=;printf("Derive"); }
~Derive(){ printf("~Derive"); }
virtual void g(){ printf("Dervie:g()");}
virtual void h(){ printf("Dervie:h()");}
private:
int nDerive;
};
int main()
{
Derive d;
Base *b = &d;
b->g();
return ;
34 }
汇编代码(VS2010编译)
1. 内存分布
类Derive对象
0019FD30 0139583C =>.rdata:const Derive::`vftable'
0019FD34 00000001 =>Base.nBase
0019FD38 00000002 =>Derive.nDerive 虚函数表
0139583C 01391163 Base::f(void)
01395840 0139110E Derive::g(void) ;Derive类重写(override)后的函数地址
01395844 013911AE Derive::h(void) ;Derive类重写(override)后的函数地址
2. 构造函数
pop ecx ;=>this指针出栈
mov [ebp+this], ecx ;=>保存this指针
mov ecx, [ebp+this]
call j_??0Base@@QAE@XZ ;=>调用基类构造函数Base::Base(void)
mov eax, [ebp+this] ;=>eax=this指针
mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>初始化虚表指针为const Derive::`vftable'
3. 析构函数
pop ecx ;=>this指针出栈
mov [ebp+this], ecx ;=>保存this指针
mov eax, [ebp+this]
mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>重置虚表指针为const Derive::`vftable'
mov esi, esp
push offset aDerive ; "~Derive"
call ds:__imp__printf
add esp,
cmp esi, esp
call j___RTC_CheckEsp
mov ecx, [ebp+this] ;=>ecx传参this指针
call j_??1Base@@QAE@XZ ;=>调用基类析构函数 Base::~Base(void)
2. 多重继承:
C++源码
#include <iostream>
using namespace std; class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
Base1(){b1 = ; printf("Base1"); }
~Base1(){ printf("~Base1"); }
private:
int b1; }; class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
Base2(){b2 = ; printf("Base2"); }
~Base2(){ printf("~Base2"); }
private:
int b2;
}; class Derive : public Base1, public Base2{
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
Derive(){ d1 = ; printf("Derive"); }
~Derive(){ printf("~Derive"); }
private:
int d1; }; typedef void(*Fun)(void); int main()
{ Derive d;
Base1 *b1 = &d;
b1->f();
b1->g();
Base2 *b2 = &d;
b2->f();
b2->g();
return ;
}
汇编代码(VS2010编译)
1.内存分布
;内存布局
0019FA0C 008F584C ;=>.rdata:const Derive::`vftable'{for `Base1'} 第一个虚表
0019FA10 ;=>Base1.b1
0019FA14 008F583C ;=>.rdata:const Derive::`vftable'{for `Base2'} 第二个虚表
0019FA18 ;=>Base2.b2
0019FA1C ;=>Derive.d1 ;第一个虚函数表Derive::`vftable'{for `Base1'}
00FB584C 00FB1041 ;=>Derive::f(void), Dervie类重写(override)后的函数地址
00FB5850 00FB1118 ;=>Base1::g(void)
00FB5854 00FB111D ;=>Derive::g1(void), Derive类中新添加的虚函数地址 ;第二个虚函数表Derive::`vftable'{for `Base2'}
00FB583C 00FB1113 ;=>Base2::g(void)
00FB5840 00FB1028 ;=>[thunk]:Derive::f`adjustor{8}' (void)
↓
;追踪地址:00FB1028
00FB1028 jmp ?f@Derive@@W7AEXXZ ;=>[thunk]:Derive::f`adjustor{8}' (void)
↓
;追踪函数:?f@Derive@@W7AEXXZ
00FB1F30 ?f@Derive@@W7AEXXZ proc near
00FB1F30 sub ecx, ;=>调整this指针为this+8,即ecx-->Derive::`vftable'{for `Base2'}
00FB1F33 jmp j_?f@Derive@@UAEXXZ ;=>Derive::f(void),即Derive类重写后的函数地址
2.构造函数
00FB14D9 mov [ebp-], ecx ;=>this指针保存在esp-4处
00FB14DC mov ecx, [ebp-] ;=>ecx获得this指针
00FB14DF call j_??0Base1@@QAE@XZ ;=>调用构造函数 Base1::Base1(void)
00FB14E4 mov ecx, [ebp-] ;=>ecx获得this指针
00FB14E7 add ecx, ;=>ecx获得this+8
00FB14EA call j_??0Base2@@QAE@XZ ;=>调用构造函数 Base2::Base2(void)
00FB14EF mov eax, [ebp-] ;=>eax获得this指针
00FB14F2 mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>初始化第一个虚表指针为const Derive::`vftable'{for `Base1'}
00FB14F8 mov eax, [ebp-] ;=>eax获得this指针
00FB14FB mov dword ptr [eax+], offset ??_7Derive@@6BBase2@@@ ;=>初始化第二个虚表指针const Derive::`vftable'{for `Base2'}
00FB1502 mov eax, [ebp-] ;=>eax获得this指针
00FB1505 mov dword ptr [eax+10h],
00FB150C push offset Format ; "Derive"
00FB1511 call ds:__imp__printf
00FB1517 add esp,
3.析构函数
00FB17C9 mov [ebp-], ecx ;=>this指针保存在esp-4处
00FB17CC mov eax, [ebp-] ;=>ecx获得this指针
00FB17CF mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>重置第一个虚表指针为const Derive::`vftable'{for `Base1'}
00FB17D5 mov eax, [ebp-]
00FB17D8 mov dword ptr [eax+], offset ??_7Derive@@6BBase2@@@ ;=>重置第二个虚表指针为const Derive::`vftable'{for `Base2'}
00FB17DF push offset aDerive ; "~Derive"
00FB17E4 call ds:__imp__printf
00FB17EA add esp,
00FB17ED mov ecx, [ebp-] ;=>ecx获得this指针
00FB17F0 add ecx, ;=>ec;得this+8
00FB17F3 call j_??1Base2@@QAE@XZ ;=>调用虚构函数Base2::~Base2(void)
00FB17F8 mov ecx, [ebp-] ;=>ecx获得this指针
00FB17FB call j_??1Base1@@QAE@XZ ;=>调用虚构函数Base1::~Base1(void)
4.虚函数调用
;Base1 *b1 = &d;
00FB1431 lea eax, [ebp-14h] ;=>eax获得this指针
00FB1434 mov [ebp-18h], eax ;=>局部变量b1赋值为this指针 ;b1->f();
00FB1437 mov eax, [ebp-18h] ;=>eax获得b1值
00FB143A mov edx, [eax] ;=>edx指向第一个虚表
00FB143C mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB143F mov eax, [edx] ;=>eax获得成员函数Derive::f(void)的地址
00FB1441 call eax ;=>调用成员函数Derive::f(void) ;b1->g();
00FB1443 mov eax, [ebp-18h] ;=>eax获得b1值
00FB1446 mov edx, [eax] ;=>edx指向第一个虚表
00FB1448 mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB144B mov eax, [edx+] ;=>eax获得成员函数Derive::g(void)的地址
00FB144E call eax ;=>调用成员函数Derive::g(void) ;Base2 *b2 = &d;
00FB1457 lea ecx, [ebp-14h] ;=>ecx获得this指针
00FB145A add ecx, ;=>ecx=this+8指向第二个虚表
00FB145D mov [ebp-64h] ;=>保存ecx到临时变量中
00FB1460 jmp short loc_FB1469;----|
;|
00FB1469 mov edx, [ebp-64h];<----|
00FB146C mov [ebp-1Ch], edx ;=>局部变量b2在栈[ebp-1Ch],赋值为this+8,指向第二个虚表 ;b2->f();
00FB146F mov eax, [ebp-1Ch] ;=>eax获得b2值
00FB1472 mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1474 mov ecx, [ebp-1Ch] ;=>ecx传参this+8
00FB1477 mov eax, [edx+] ;=>eax为第二个虚表的第二个元素=>[thunk]:Derive::f`adjustor{8}' (void),然后间接调用Derive::f(void)
00FB147A call eax ;b2->g();
00FB147C mov eax, [ebp+b2] ; =>eax获得b2值
00FB147F mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1481 mov ecx, [ebp+b2] ;=>ecx传参this+8
00FB1484 mov eax, [edx] ;=>eax为第二个虚表的第一个元素=>Base2::g(void)
00FB1486 call eax ;=>调用Base2::g(void)