《C++反汇编与逆向分析技术揭秘》之11——虚函数
- 虚函数的机制
当类中定义有虚函数时,编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表。编译器还会在类中添加一个虚表指针。
举例:
CVirtual类的构造函数中没有进行任何操作,但是我们来看构造函数内部,还是有一个赋初值的操作:
这个地址指向的是一个数组:
这些数组中的内容就是虚函数的指针:
值得注意的是,如果没有虚指针的存在,那么CVirtual大小就是4字节。有了这个指针存在就是8字节。
本例子中,使用了一个空的构造函数,但是编译器自己擅自插入了代码,实现了对虚表的初始化。如果我们没有提供任何构造函数的话,那么编译器就会提供一个默认的构造函数对虚表进行初始化。
当函数被调用时,会间接访问虚表,得到对应的虚函数的地址,并调用执行。这种通过虚表间接寻址访问的情况只有在使用对象的指针或引用来调用虚函数的时候才会出现。当直接使用对象调用自身的虚函数时,没有必要查表访问。因为已经明确调用的是自身的成员函数了,根本没有构成多态性。
举例:
直接通过对象调用虚函数的时候,就是直接用对象的地址作为隐含参数传递给这个虚函数:
这个虚函数此时和普通的成员函数没有区别。之所以要隐含传递对象的地址,是为了能够准确适用对象中所包含的数据成员。
但是如果构成了多态,调用方式就不同了:
因为你实际上不知道pcv指针指向的具体类型是什么,所以要到虚表中找到所指向的真正的对象的那个虚函数。
虚表指针的初始化,是判断一个函数是构造函数的充分条件。
析构函数对虚表如何操作?在考虑这个问题之前,我们先要知道,为什么析构函数要使用虚函数:
如果父类和子类的虚函数分别如下所示:
我们在执行delete指针之后,会是如下流程:
即只调用了父类的虚函数。而如果把析构函数设置为虚的:
则是如下调用流程:
delete删除指针的时候调用的是子类的虚函数,而子类的虚函数内部又调用了父类的虚函数。而调用父类的虚函数之前,ecx指针中仍保留的是子类对象的首地址:
子类的析构函数调用自身的虚成员函数:
随后调用父类的析构函数:
父类的析构函数中没有间接寻址,直接调用了Show1和Show2:
但是无论是子类还是父类的虚析构函数中都会有这么一步操作:把当前类的虚表的首地址赋值到虚表指针当中去。这是为了防止在析构函数中调用虚函数时取到非自身的虚表。为什么要这么做?举例说明:
先调用A的构造函数:
A类填充虚表:
调用虚函数:
调用完A的构造函数,继续往下执行B的构造函数中的其余部分,为了能够正常调用B的func2,这里必须要还原虚表:
析构函数中同理。
- 虚函数的识别
1)特征:
1、类中隐式定义了一个数据成员;
2、该数据成员在首地址处;
3、构造函数会将此数据成员初始化为某个数组的首地址;
4、这个地址属于数据区,是相对固定的地址;
5、数组内每个元素都是函数的指针;
6、数组中的这些函数被调用时,第一个参数必然是this指针;
7、这些函数内部有可能对this指针进行相对间接的访问。
2)验证父类和子类的虚表指针:
举例:
初始化父类之后:
父类的两个虚函数地址为:
调用完父类构造函数之后会重新赋值一个虚表:
我们发现,A的虚表中和B的虚表中的第一个函数地址是相同的,不同的是第二个函数的地址。在构造B的时候先构造A,而在构造A的时候要赋值一个虚表指针,是为了防止在A的构造函数中使用使用了虚函数,而无意间调用了B的虚函数。而实际上,构造完B之后,B中就不存在刚刚A的那个虚表指针了。
借助OD找到A虚表的地址和B虚表的地址:
于是,先根据交叉引用找到了A的构造函数:
再借助交叉引用找到B的构造函数:
当然如果B中有多个构造函数,和一个析构函数时是什么情况呢?会有两个构造函数引用它:
- 借助虚表识别全局对象:
举例:
全局对象的构造函数调用之后会调用一个函数来登记析构函数的地址:
此时push进去的参数是一个函数的地址41A4D0:
进入call 4110F5,这里边的call 4158A8就是等同于atexit的作用:
里边会把这个函数的地址传递给onexit:
参考:
http://bbs.csdn.net/topics/360161935
https://www.2cto.com/kf/201408/326530.html
参考这两个链接了解到,传递给onexit的函数,总会在main函数执行完毕之后执行。所以可以推断出41A4D0是个析构函数。
看下41A4D0中的内容:
进入被标记的那个call,发现这就是一个析构函数,并且有一个恢复虚表指针的操作:
交叉引用看一下哪些地方用到了这个虚表:
发现只有两个交叉引用,左边的那个是析构函数,右边的那个是构造函数。虽然这个全局类包括了三种形式的构造函数,但是我们的程序中只用了一种形式,所以只有其中一种形式的构造函数对虚表进行了操作:
《C++反汇编与逆向分析技术揭秘》之11——虚函数的更多相关文章
- 《C++反汇编与逆向分析技术揭秘》--认识启动函数,找到用户入口
<C++反汇编与逆向分析>和<程序员的自我修养>都是以VC6的代码作为例子讲解的.这里是在vs2017下,CRT代码有些区别,但整体流程上都是初始化环境,设置参数,最后转到用户 ...
- C++反汇编与逆向分析技术揭秘
C++反汇编-继承和多重继承 学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...
- 《C++反汇编与逆向分析技术揭秘》--算术运算和赋值
一.加法 1.Debug下: 14: int nVarOne0 = 1 + 5 - 3 * 6;//编译时计算得到结果 00C0550E C7 45 F8 F4 FF FF FF mov dword ...
- 《C++反汇编与逆向分析技术揭秘》之十——构造函数
对象生成时会自动调用构造函数.只要找到了定义对象的地方,就找到了构造函数调用的时机.不同作用域的对象的生命周期不同,如局部对象.全局对象.静态对象等的生命周期各不相同,只要知道了对象的生命周期,便可以 ...
- 《C++反汇编与逆向分析技术揭秘》——观察各种表达式的求值过程
---恢复内容开始--- 加法: 示例: 常量相加,则在编译期间就计算出两个常量相加后的结果,直接将这个结果参与运算,减少了运行期的计算.当有变量参与运算时,会先取出内存中的数据,放入通用寄存器中,再 ...
- 《C++反汇编与逆向分析技术揭秘》——基本数据类型的表现形式
---恢复内容开始--- 基本的浮点数指令 示例代码: Visual Studio 2013的反汇编代码是: 对于movss,表示移动标量单精度浮点值 将标量单精度浮点值从源操作数(第二个操作数)移到 ...
- 《C++反汇编与逆向分析技术揭秘》——函数的工作原理
各种调用方式的考察 示例: cdecl方式是调用者清空堆栈: 如果执行的是fastcall: 借助两个寄存器传递参数: 参数1和2借助局部变量来存储: 返回值 如果返回值是结构体: 返回值存放在eax ...
- 《C++反汇编与逆向分析技术揭秘》——流程控制语句的识别
if...else...语句 示例: if构成多分支语句 switch 有序线性的switch: 3E82D8位置存放了一个表,标明了要跳转到的地址: 这里的每四字节都标明的是每个case块的首地址: ...
- 《C++反汇编与逆向分析技术揭秘》之12——继承
识别类和类之间的关系 在父类中声明为私有的成员,虽然子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在. 在没有提供构造函数的时候,系统会尝试提供默认的构造函数: 当子类中没 ...
随机推荐
- css让元素不可点击 pointer-events: none;
张鑫旭大神:http://www.zhangxinxu.com/wordpress/2011/12/css3-pointer-events-none-javascript/ 我们知道form元素里的 ...
- controller中,Failed to Initialize. Reason: TimeOut虚拟用花初始化超时
1.调整 Run-timesetting->Internet Protocol->references->Advaanced->Options 将HTTP-request co ...
- LoadRunner去除事物中的程序的执行时间
大家在性能测试过程中,经常会用到程序处理或组织数据,以达到一定的测试目的,但是程序本身执行会消耗一些时间,这部分消耗的时间是包含在响应时间里面,此时,响应时间=正常响应时间+程序执行消耗时间.那么如何 ...
- Unity3D 向量运算
写在前面的话,前两天有个朋友在QQ上问我 如何获取主角面朝方向一定区域中的敌人对象.这个命题看似简单,其实里面蕴含了很多数学方面的东西.今天刚好有时间我就彻底的把这个疑问写在博客中.希望可以帮助到他. ...
- php中var_dump()函数
var_dump() void var_dump ( mixed expression [, mixed expression [, ...]] ) var_dump()方法是判断一个变量的类型与长度 ...
- Jenkins多选项框使用
多选框的使用场景还是挺多的,比如发布多个服务,或者选择哪些服务器 想要使用多选项,则需要安装插件extend choice parameter,然后在项目中配置参数化构建过程 配置完上面,我们就可以在 ...
- Redis学习篇(十一)之发布订阅
PUBLISH/SUBSCRIBE 发布订阅的原理 包含两个角色,一个是发布者, 一个是订阅者 订阅者可以订阅一个或者多个频道(channel) 发布者可以向指定的频道发布信息 通过SUBSCRIBE ...
- Hibernate iterate(迭代)
在Hibernate中,有它自己的iterate方法.这里是一个n+1的问题,n代表数据量,而1表示查询所有的主键. 这里与List进行比较. 这里使用list时会将所有数据查询出来. 而在使用ite ...
- PHP 笔记——Array 数组
要点 说明 数组构成 数组是由一个或多个数组元素组成的 数组元素 每个数组元素由键(Key)和值(Value)构成 键 元素的识别名称,也被称为数组下标 值 元素的内容 映射 键 和 值 之间存在一种 ...
- [HNOI2018]道路 --- 树形DP
[HNOI2018]道路 题目描述: W 国的交通呈一棵树的形状.W 国一共有 \(n-1\) 个城市和 \(n\) 个乡村, 其中城市从 \(1\) 到 \(n-1\) 编号,乡村从 \(1\) 到 ...