查看虚函数表和类内存布局,以及使用MSVC与GCC hack验证
2015-11-16 18:22
579 查看
方法其实很简单。
1.打开VS自带的命令行工具。当然,你把cl.exe的目录写到环境变量中,直接在cmd中也能用。
这是VS2012的。
下面这个是VS2013的。都一样。
2.使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。
其中,/d1reportSingleClassLayoutXXX 显示指定XXX类的内存布局
/d1reportAllClassLayou 显示所有类的内存布局
举个例子。注意,这个例子中为了简便,没有将类的析构函数定义为虚的。在下面的实例验证环节,我会将析构函数也定义成虚的。
下述两个类存储在test.cpp文件中。
在命令行中输入 cl /d1reportSingleClassLayoutB test.cpp
结果如下所示:
在命令行中输入 cl /d1reportSingleClassLayoutA test.cpp
结果如下所示:
3.实际操作来验证
根据上面得到的虚函数表所在的位置,我们来进行一下hack行为。
使用MSVC++ 12 编译器和gcc version 4.7.1编译器,两个编译器输出结果相同,
表明这两个编译器,都是将虚表的指示指针,放在了对象的头部。如下所示:
//MSVC compiler
class B size(8):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::{dtor}
1 | &B::showMe
B::showMe this adjustor: 0
B::{dtor} this adjustor: 0
B::__delDtor this adjustor: 0
B::__vecDelDtor this adjustor: 0
//GCC compiler
class B size(8):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::showMe
1 | &B::{dtor}
B::showMe this adjustor: 0
B::{dtor} this adjustor: 0
B::__delDtor this adjustor: 0
B::__vecDelDtor this adjustor: 0
1.打开VS自带的命令行工具。当然,你把cl.exe的目录写到环境变量中,直接在cmd中也能用。
这是VS2012的。
下面这个是VS2013的。都一样。
2.使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。
其中,/d1reportSingleClassLayoutXXX 显示指定XXX类的内存布局
/d1reportAllClassLayou 显示所有类的内存布局
举个例子。注意,这个例子中为了简便,没有将类的析构函数定义为虚的。在下面的实例验证环节,我会将析构函数也定义成虚的。
下述两个类存储在test.cpp文件中。
class A{ public: void doX(){ std::cout << "A function of X"; } private: int a; }; class B: public A{ public: virtual void doX(){ std::cout << "B function of X"; } };
在命令行中输入 cl /d1reportSingleClassLayoutB test.cpp
结果如下所示:
class B size(8): +--- 0 | {vfptr} | +--- (base class A) 4 | | a | +--- +--- B::$vftable@: | &B_meta | 0 0 | &B::doX B::doX this adjustor: 0
在命令行中输入 cl /d1reportSingleClassLayoutA test.cpp
结果如下所示:
class A size(4): +--- 0 | a +---
3.实际操作来验证
根据上面得到的虚函数表所在的位置,我们来进行一下hack行为。
#include <iostream> #include <cstdlib> class A{ public: void showMe(){ std::cout << "A showMe() is called" << std::endl; } virtual ~A() { std::cout << "A destructor is called" << std::endl; } private: int a; }; class B : public A{ public: virtual void showMe(){ std::cout << "B showMe() is called" << std::endl; } virtual ~B() { std::cout << "B destructor is called" << std::endl; } }; int main(){ #ifdef _MSC_VER std::cout << "MSVC++ Compiler Version:" << (_MSC_VER - 600) / 100.0 << std::endl; #elif __GNUC__ std::cout << "GCC Compiler Version:" << __GNUC__ << std::endl; #endif //获取基类 showMe()的内存调用地址 typedef void (A::* Aptr)(); Aptr A_memberFuncPtr = &A::showMe; void* tempPtr = &A_memberFuncPtr; int* A_showMe_FuncAddr = (int*)*(int*)tempPtr; //获取派生类对象中指向虚函数表的 指针地址 //注意,该指针里面存着虚表的入口地址 B* bPtr = new B(); void* bAddr = bPtr; int* BVT_Indicator = (int*)(bAddr); int* BVTaddr = (int*)(*BVT_Indicator); //因为虚表是位于只读内存中,所以 //我们不能修改原来的虚表 //而 只能 让程序执行我们自定义的虚表 //注意,这里并没有修改原来的虚表 //构造自己的虚表 //因为有两个虚函数,所以虚表有两项 int* selfmadeVirtualTable[2]; //对于GCC编译器来说,虚表的最后一项为虚析构函数的地址 //对于MSVC编译器来说,虚表的第一项为虚析构函数的地址 #ifdef _MSC_VER selfmadeVirtualTable[0] = (int*)(*BVTaddr); selfmadeVirtualTable[1] = A_showMe_FuncAddr; #elif __GNUC__ selfmadeVirtualTable[0] = A_showMe_FuncAddr; selfmadeVirtualTable[1] = (int*)*(BVTaddr + 1); #endif //将虚表入口指针 指向自定义的虚表 *BVT_Indicator = (int)selfmadeVirtualTable; //通过指针调用虚函数,hack成功 //注意,只有通过指针或者引用,才能实现多态调用 bPtr->showMe(); //释放内存,避免泄漏 delete bPtr; system("pause"); }
使用MSVC++ 12 编译器和gcc version 4.7.1编译器,两个编译器输出结果相同,
MSVC++ Compiler Version:12 A showMe() is called B destructor is called A destructor is called Press any key to continue . . .
GCC Compiler Version:4 A showMe() is called B destructor is called A destructor is called Press any key to continue . . .
表明这两个编译器,都是将虚表的指示指针,放在了对象的头部。如下所示:
//MSVC compiler
class B size(8):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::{dtor}
1 | &B::showMe
B::showMe this adjustor: 0
B::{dtor} this adjustor: 0
B::__delDtor this adjustor: 0
B::__vecDelDtor this adjustor: 0
//GCC compiler
class B size(8):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::showMe
1 | &B::{dtor}
B::showMe this adjustor: 0
B::{dtor} this adjustor: 0
B::__delDtor this adjustor: 0
B::__vecDelDtor this adjustor: 0
相关文章推荐
- 织梦dedecms后台增加多媒体后缀上传无法选择原因
- LeetCode -- Subsets II
- iOS 开发相关
- 常见思维总结
- 题记——千里杀一人
- Caffe Install again and again
- java环境配置
- LeetCode -- Single Number III
- jQuery-1.9.1源码分析系列(八) 属性操作
- 解决ntp的错误 no server suitable for synchronization found
- 第一题、查找给定数
- java的i++问题
- linux命令修改IP信息
- 软件磁盘阵列(Software RAID)
- 循环队列的应用--舞伴配对
- php curl请求
- javaEE开发工作中遇到的笔记
- LeetCode -- Serialize and Deserialize Binary Tree
- SpringMVC使用PDF模板生成PDF文件
- 语言的二义性 和 文法的二义性