1.关于虚函数
在名函数前加关键字“virtual”的函数为虚函数;
例如:class Base
{ public: void Function_1() { printf("Function_1... "); } virtual void Function_2() //虚函数 { printf("Function_2... "); } };
反汇编分析:
可以看到:
通过对象调用时,virtual函数与普通函数都是E8 Call
通过指针调用时,virtual函数是FF Call,也就是间接Call
pe的IAT表使用的就是FF Call ;
使用间接call的原因是因为虚函数的地址不靠谱,可能被改掉;
比如,子类覆盖父类函数时;
1)虚函数的特点
#include "stdafx.h" class Base { public: int x; int y; Base(){ x=1; y=2; } void fun(){ } virtual fun2(){ } }; int main(int argc, char* argv[]) { Base b; Base* pb = &b; pb ->fun2(); getchar(); return 0; }
测试上面结构的大小:Base base;
printf("%x ",sizeof(base));
结果是:0x0c;
普通函数放结构体里面时,不会影响结构体大小,结构大小应该是两个int的大小8个字节;
但在结构体中有一个或多个虚函数时,多了4个字节;
反汇编分析:
调用结构体里面的函数时,会用ecx传递this指针;
this指针指向的就是对象的结构体;
可以看到:有虚函数的对象的结构体中在结构体开始处多了4个字节,存放的是42501c是一个地址,这个地址就是虚函数表的地址;
将42501c存入edx,然后FF call这个地址;
如果有多个虚函数反汇编:
可以看到,第一个虚函数call的是[edx],第二个虚函数call的是[edx+4];
总结:
1、当类中有虚函数时,会多一个属性,4个字节
2、多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址
2.解析虚表信息
class Base { public: int x; int y; virtual void Function_1() { printf("Function_1... "); } virtual void Function_2() { printf("Function_2... "); } virtual void Function_3() { printf("Function_3... "); } }; void TestMethod() { //查看 Sub 的虚函数表 Base base; //对象的前四个字节就是虚函数表 printf("base 的虚函数表地址为:%x ",*(int*)&base); //通过函数指针调用函数,验证正确性 typedef void(*pFunction)(void); pFunction pFn; for(int i=0;i<3;i++) { int temp = *((int*)(*(int*)&base)+i); pFn = (pFunction)temp; pFn(); } }
虚表的结构: