学无止尽,积土成山,积水成渊-《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)

C++反汇编-继承和多重继承的更多相关文章

  1. C++ 深入理解 虚继承、多重继承和直接继承

    [摘要] 本文从5段代码实例出发,通过类中类的普通继承,类的虚继承,类的多重继承,多个虚函数类的普通继承.虚继承与多重继承,几个交叉概念,详细的阐释了继承.虚函数与虚继承的基本概念,深入剖析了继承于虚 ...

  2. Python 在子类中调用父类方法详解(单继承、多层继承、多重继承)

    Python 在子类中调用父类方法详解(单继承.多层继承.多重继承)   by:授客 QQ:1033553122   测试环境: win7 64位 Python版本:Python 3.3.5 代码实践 ...

  3. [javase学习笔记]-9.2 单继承与多重继承

    这一节我们来看java中的单继承和多重继承. 在java语言中,支持的是单继承,不直接支持多继承,可是对C++中的多继承进行了改良. 那么什么是单继承和多继承呢? 单继承:一个子类仅仅能有一个直接父类 ...

  4. 《挑战30天C++入门极限》C++类的继承与多重继承的访问控制

        C++类的继承与多重继承的访问控制 在前面的练习中我们一直在使用public的继承方式,即共有继承方式,对于protected和private继承方式,即保护继承与私有继承方式我们并没有讨论. ...

  5. JS中的原型继承和多重继承

    概念:1原型继承是创建新类型对象----子类型,子类型基于父类型,子类型拥有父类型所有的属性和方法(从父类型继承得到),然后修改其中的部分内容或者添加新的内容.继承最好在子类型模型可以被视为父类型对象 ...

  6. python 继承与多重继承

    当然,如果不支持python继承,语言特性就不值得称为“类”.派生类定义的语法如下所示: <statement-1> . . . <statement-N> 名称 BaseCl ...

  7. Golang之继承,多重继承(struct)

    热乎的代码来了 package main import "fmt" /* 继承 一个结构体嵌到另一个结构体,称作组合 匿名和组合的区别 如果一个struct嵌套了另一个匿名结构体, ...

  8. c#继承、多重继承

    c#类 1.c#类的继承 在现有类(基类.父类)上建立新类(派生类.子类)的处理过程称为继承.派生类能自动获得基类的除了构造函数和析构函数以外的所有成员,可以在派生类中添加新的属性和方法扩展其功能.继 ...

  9. C++继承,多重继承,虚继承的构造函数以及析构函数的调用顺序问题

    #include <iostream> using namespace std; class A{ int data_a; public: A(){ data_a = ; cout < ...

随机推荐

  1. PHP计算字符串的长度

    <?php /** * 计算字符串的长度(汉字按照两个字符计算) * * @param string $str 字符串 * * @return int */ function str_len($ ...

  2. 【前端】上拉加载更多dropload.min.js的使用

    代码如下:入职代码修改接口及html为自己的即可(下面主要展示js部分) <!DOCTYPE html><html> <head> <meta charset ...

  3. 一篇文章读懂开源web引擎Crosswalk-《转载》

    前言 Web技术的优势早已被广大应用开发者熟知,比如可与云服务轻松集成,基于响应式UI设计的精美布局,高度的开放性,跨平台能力, 高效的分发与部署等等.伴随着移动互联网的快速发展与HTML5技术的逐步 ...

  4. HTML网页自动跳转

    <meta http-equiv="refresh" content="3;URL=res.html">

  5. Ad Hoc Distributed Queries的启用与关闭

    启用Ad Hoc Distributed Queries: exec sp_configure 'show advanced options',1 reconfigure exec sp_config ...

  6. MySQL权限问题

    1.修改MySQL用户密码 .先来看一个PASSWORD()函数,MYSQL使用MD5加密 SELECT PASSWORD(‘root’); .使用mysql数据库,查看用户表 USE mysql; ...

  7. Spark(一)Spark简介

    一.官网介绍 1 什么是Spark 官网地址:http://spark.apache.org/ Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎.Spark是UC Berkel ...

  8. C语言:输入10个整数,找出其中绝对值最小的数

    1 输入10个整数,找出其中绝对值最小的数(10分) 题目描述 输入10个整数,找出其中绝对值最小的数 输入 十个整数 输出 绝对值最小的数 样例输入 -10 -2 30 40 50 60 70 80 ...

  9. 【LOJ】 #2540. 「PKUWC2018」随机算法

    题解 感觉极其神奇的状压dp \(dp[i][S]\)表示答案为i,然后不可选的点集为S 我们每次往答案里加一个点,然后方案数是,设原来可以选的点数是y,新加入一个点后导致了除了新加的点之外x个点不能 ...

  10. HDU - 4420 2013icpc长春A 函数离散化 + st表

    思路:我们定义F(x) 为以x点为起点,向后(a - b)个里面有多少个白球,虽然x的范围是LL范围内的,但是白球的 个数只有1e5, 那么我们可以把连续一段相同的离散化到一起, 对于一个确定的长度为 ...