从逆向的角度去理解C++虚函数表
很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中,很多问题我们从表面上无法看出到底是为什么,只要用逆向工具一分析,很快就能知道其中的所以然来。我们都知道虚函数表是放在类对象的最前面,但是很多人并不知道虚函数表的第一项存放的是什么,下面我用IDA分析一下C++的虚函数调用,从汇编的角度去理解虚函数。此文只适合具有逆向分析基础的读者,如果没有逆向分析基础,请直接绕过。有如下C++代码:
class Test
{
public:
Test()
{
printf("Test::Test\n");
}
virtual ~Test()
{
printf("Virtual ~Test()\n");
}
virtual void prointer()=;
virtual void pointf()=;
};
class TestA:public Test
{
public:
TestA()
{
printf("TestA::TestA\n");
}
virtual ~TestA()
{
printf("TestA::TestA\n");
}
virtual void prointer()
{
printf("Derive Class TestA::Pointer\n");
}
virtual void pointf()
{
printf("Derive Class TestA::Pointf\n");
}
}; int _tmain(int argc, _TCHAR* argv[])
{
TestA *pTest=new TestA;
pTest->pointf();
pTest->prointer();
delete pTest;
return ;
}
用IDA打开EXE文件,用流程图模式显示:

运行到调用new的地方:

一层一层跟进去,我们会发现,new的内部其实就是调用的malloc进行内存分配,msvcr100d_malloc便是malloc函数:

往下走,发现如下指令:
text:00C3149F call j_??@YAPAXI@Z_0 ; operator new(uint)
.text:00C314A4 add esp,
.text:00C314A7 mov [ebp+this], eax
.text:00C314AD mov [ebp+var_4],
.text:00C314B4 cmp [ebp+this],
.text:00C314BB jz short loc_C314D0
.text:00C314BD mov ecx, [ebp+this] ; this
.text:00C314C3 call j_??0TestA@@QAE@XZ ; TestA::TestA(void)
.text:00C314C8 mov [ebp+var_10C], eax ; new的返回值赋给[epb+var_10C]
.text:00C314CE jmp short loc_C314DA ; eax=this
.text:00C314D0 ; ---------------------------------------------------------------------------
.text:00C314D0
.text:00C314D0 loc_C314D0: ; CODE XREF: _wmain+5Bj
.text:00C314D0 mov [ebp+var_10C],
.text:00C314DA
.text:00C314DA loc_C314DA: ; CODE XREF: _wmain+6Ej
.text:00C314DA mov eax, [ebp+var_10C] ; eax=this
.text:00C314E0 mov [ebp+var_104], eax ; [ebp+var_104]=this
.text:00C314E6 mov [ebp+var_4], 0FFFFFFFFh
.text:00C314ED mov ecx, [ebp+var_104] ; ecx=this
.text:00C314F3 mov [ebp+pTest], ecx ; [ebp+pTest]=this
.text:00C314F6 mov eax, [ebp+pTest] ; eax=this
.text:00C314F9 mov edx, [eax]
.text:00C314FB mov esi, esp
.text:00C314FD mov ecx, [ebp+pTest]
.text:00C31500 mov eax, [edx+] ; eax=this+
.text:00C31503 call eax ; 调用this+8处的代码
跟进去,发现如下指令:
text:00C31820 push ebp
.text:00C31821 mov ebp, esp
.text:00C31823 sub esp, 0CCh
.text:00C31829 push ebx
.text:00C3182A push esi
.text:00C3182B push edi
.text:00C3182C push ecx
.text:00C3182D lea edi, [ebp+var_CC]
.text:00C31833 mov ecx, 33h
.text:00C31838 mov eax, 0CCCCCCCCh
.text:00C3183D rep stosd
.text:00C3183F pop ecx
.text:00C31840 mov [ebp+this], ecx
.text:00C31843 mov esi, esp
.text:00C31845 push offset aDeriveClassT_0 ; "Derive Class TestA::Pointf\n"
.text:00C3184A call ds:__imp__printf
.text:00C31850 add esp,
.text:00C31853 cmp esi, esp
.text:00C31855 call j___RTC_CheckEsp
.text:00C3185A pop edi
.text:00C3185B pop esi
.text:00C3185C pop ebx
.text:00C3185D add esp, 0CCh
.text:00C31863 cmp ebp, esp
.text:00C31865 call j___RTC_CheckEsp
.text:00C3186A mov esp, ebp
.text:00C3186C pop ebp
.text:00C3186D retn
.text:00C3186D ?pointf@TestA@@UAEXXZ endp
这就是pTest->pointf();调用的汇编代码,继续往下走:

调用了[edx+4]其实也就是this+4处的代码,跟进去,代码如下:
.text:00C517B0 push ebp
.text:00C517B1 mov ebp, esp
.text:00C517B3 sub esp, 0CCh
.text:00C517B9 push ebx
.text:00C517BA push esi
.text:00C517BB push edi
.text:00C517BC push ecx
.text:00C517BD lea edi, [ebp+var_CC]
.text:00C517C3 mov ecx, 33h
.text:00C517C8 mov eax, 0CCCCCCCCh
.text:00C517CD rep stosd
.text:00C517CF pop ecx
.text:00C517D0 mov [ebp+this], ecx
.text:00C517D3 mov esi, esp
.text:00C517D5 push offset aDeriveClassTes ; "Derive Class TestA::Pointer\n"
.text:00C517DA call ds:__imp__printf
.text:00C517E0 add esp,
.text:00C517E3 cmp esi, esp
.text:00C517E5 call j___RTC_CheckEsp
.text:00C517EA pop edi
.text:00C517EB pop esi
.text:00C517EC pop ebx
.text:00C517ED add esp, 0CCh
.text:00C517F3 cmp ebp, esp
.text:00C517F5 call j___RTC_CheckEsp
.text:00C517FA mov esp, ebp
.text:00C517FC pop ebp
.text:00C517FD retn
.text:00C517FD ?prointer@TestA@@UAEXXZ endp
以上其实就是调用pTest->prointer();的汇编代码,继续往下执行:

这里调用的就是this处的指令,跟进去,有如下指令:
text:011D1890 push ebp
.text:011D1891 mov ebp, esp
.text:011D1893 sub esp, 0CCh
.text:011D1899 push ebx
.text:011D189A push esi
.text:011D189B push edi
.text:011D189C push ecx
.text:011D189D lea edi, [ebp+var_CC]
.text:011D18A3 mov ecx, 33h
.text:011D18A8 mov eax, 0CCCCCCCCh
.text:011D18AD rep stosd
.text:011D18AF pop ecx
.text:011D18B0 mov [ebp+this], ecx
.text:011D18B3 mov ecx, [ebp+this] ; this
.text:011D18B6 call j_??1TestA@@UAE@XZ ; TestA::~TestA(void)
.text:011D18BB mov eax, [ebp+arg_0]
.text:011D18BE and eax,
.text:011D18C1 jz short loc_11D18CF
.text:011D18C3 mov eax, [ebp+this]
.text:011D18C6 push eax ; void *
.text:011D18C7 call j_??@YAXPAX@Z_0 ; operator delete(void *)
.text:011D18CC add esp,
此处就是析构函数调用的地方,综上所述,我们可以得出结论,new最终是调用的malloc进行内存分配,在虚函数表中,虚析构函数是放在最前面的。当然这只是最简单的类继承体系,而且有些地方写得不够详细,大家可以自己动手写代码用IDA去进行逆向分析,探索其中的奥秘,以上只是本人的一些理解,如果有不合理的地方欢迎大家指正,希望能和大家共同学习,共同进步。
从逆向的角度去理解C++虚函数表的更多相关文章
- 深入理解C++虚函数表
虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要. 本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局. CL使用方 ...
- 我理解的C++虚函数表
今天拜读了陈皓的C++ 虚函数表解析的文章,感觉对C++的继承和多态又有了点认识,这里写下自己的理解.如果哪里不对的,欢迎指正.如果对于C++虚函数表还没了解的话,请先拜读下陈皓的C++ 虚函数表解析 ...
- 从需求的角度去理解Linux系列:总线、设备和驱动
笔者成为博客专家后整理以前原创的嵌入式Linux系列博文,现推出以让更多的读者受益. <从需求的角度去理解linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文 ...
- 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)
本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上? 成员函数可以被看作是类 ...
- 对C++虚函数、虚函数表的简单理解
一.虚函数的作用 以一个通用的图形类来了解虚函数的定义,代码如下: #include "stdafx.h" #include <iostream> using name ...
- C++虚函数表理解
一,思维模式图 二,代码验证 class A { public: A(int x) { fProtected = x; } float GetFProtected() { return fProtec ...
- C++ 类的存储方式以及虚函数表
一.C++成员函数在内存中的存储方式 用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就 ...
- C++虚函数表和对象存储
C++虚函数表和对象存储 C++中的虚函数实现了多态的机制,也就是用父类型指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种技术可以让父类的指针有"多种形态",这 ...
- C++迟后联编和虚函数表
先看一个题目: class Base { public: virtual void Show(int x) { cout << "In Base class, int x = & ...
随机推荐
- [ACM_图论] The Perfect Stall 完美的牛栏(匈牙利算法、最大二分匹配)
描述 农夫约翰上个星期刚刚建好了他的新牛棚,他使用了最新的挤奶技术.不幸的是,由于工程问题,每个牛栏都不一样.第一个星期,农夫约翰随便地让奶牛们进入牛栏,但是问题很快地显露出来:每头奶牛都只愿意在她们 ...
- Linux:文件权限
使用较长格式列出文件:ls -l显示除了 '.'(当前目录),'..' 上一级目录之外的所有包含隐藏文件(Linux 下以 '.' 开头的文件为隐藏文件): ls -Al查看某一个目录的完整属性,而不 ...
- HTML5播放器FlowPlayer的极简风格效果
在线演示 本地下载 使用Flowplayer生成的极简风格的播放器效果.
- atitit.web 推送实现方案集合(2)---百度云,jpush 极光推送 ,个推的选型比较.o99
atitit.web 推送实现方案集合(2)---百度云,jpush 极光推送 ,个推的选型比较.o99 1.1. 云推送有推送次数或频率的限制吗? 1 1.2. 推送的消息长度 1 1.3. 离线消 ...
- Revit利用对正工具快速修改风管对齐方式
绘制风管的时候,可以对风管的对正方式进行设置,水平方向可以设置左对齐或者中心对齐或者右对齐,垂直方向可以设置为顶对齐或者中心对齐或者低对齐,如果需要对原来的对齐方式进行修改的时候应该如何操作呢?比如, ...
- 解决Visual Studio 2010/2012的RC4011 warnings
如果在vc10/11工程的rc文件中有以下任意一行代码: #include <winuser.h> #include <richedit.h> 那么vc将会给出一对警告: C: ...
- 【Vegas原创】vlookup的使用方法
情景: 1,当月移动话单,没有姓名,只有手机号码:(用户费用sheet) 2,IT部自己整理的手机号历史记录,有姓名,有手机号码:(历史信息sheet) 3,要求:需要从历史记录中,透视出当月所有手机 ...
- python报错:SyntaxError: Non-ASCII character '\xe5'的解决方法
最近在学习机器学习,上面的代码都是一些python代码,对于python只是会一些基础性的东西,刚才就遇到了一个比较low的问题,但是还是记录一下吧. 在python代码中出现了中文,但是我又把# - ...
- Oracle中Kill session的研究(转 出自eagle)
itpub link: http://www.itpub.net/235873.html 我们知道,在Oracle数据库中,可以通过kill session的方式来终止一个进程,其基本语法结构为: a ...
- javascript一种新的对象创建方式-Object.create()
1.Object.create() 是什么? Object.create(proto [, propertiesObject ]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不 ...