您的位置:首页 > 其它

查看虚函数表和类内存布局,以及使用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文件中。

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: