一、直接调用、间接调用
-
面向对象的三大特性:封装、继承、多态
-
多态是最重要的,如果把虚函数掌握好了,多态就容易理解了
-
看反汇编的硬编码:
E8 ...
:直接调用;FF15 ...
:间接调用注入shellcode那章学过直接调用;IAT表那章学过间接调用
-
通过对象直接去调用普通成员方法或者虚函数:从反汇编层面讲,没有任何区别。即都是传入this,接着使用
E8
,直接调用#include "stdafx.h" class Person{ public: void method1(){ printf("method1\n"); } virtual void method2(){ //虚函数 printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; person.method1(); person.method2(); return 0; }
-
如果用指针调用普通成员方法,和用对象调用没有区别;如果用指针调用虚函数:发现调用之前先做了一些额外的操作,而使用的是
FF12
间接调用!#include "stdafx.h" class Person{ public: void method1(){ printf("method1\n"); } virtual void method2(){ printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; Person* p = &person; //用指针的方式调用 p->method1(); p->method2(); return 0; }
二、虚函数表反汇编分析
1.研究大小
-
不管一个类中有多少虚函数,都只多出来4字节大小
#include "stdafx.h" class Person{ int x; int y; public: virtual void method1(){ printf("method1\n"); } virtual void method2(){ printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; printf("%d",sizeof(person)); //12 return 0; }
2.虚函数表地址
-
既然有虚函数就会多出来4字节,我们反汇编分析一下这4字节是什么,在哪里?
#include "stdafx.h" class Person{ int x; int y; public: Person(){ x = 1; y = 2; } virtual void method1(){ printf("method1\n"); } virtual void method2(){ printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; printf("%d",sizeof(person)); return 0; }
- person对象首地址在0x12FF74,所以发现:这个多出来的4字节所在位置,就是类的对象的首地址,再往后才是x和y。所以对象的首地址中存储的就是虚函数表的地址值
3.调用虚函数的反汇编
-
我们来分析一下使用指针调用虚函数时编译器做了什么:
#include "stdafx.h" class Person{ int x; int y; public: Person(){ x = 1; y = 2; } virtual void method1(){ printf("method1\n"); } virtual void method2(){ printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; Person* p = &person; p->method1(); p->method2(); return 0; }
-
先把
[ebp-0x10]
存到ecx,ebp-0x10中的值即为0x12FF74(即对象首地址),所以现在ecx中的值为0x12FF74 -
接着把[ecx]存到edx,即把0x12FF74中的值存到edx,所以现在edx中年的值为0x42202C(虚函数表地址)
-
最后
call [edx]
,即call的是0x42202C地址中的值!(间接调用)上述反汇编中一定要注意到底取的是地址还是地址中的值!即要注意有没有中括号
[]
-
那我们就看看0x422202C地址中的值到底是什么!发现调用虚函数method1时,call的实际上是0x401064;调用虚函数method2时,call的实际上是0x40105A。(这两个值就是虚函数的真正地址!而所有虚函数的地址集中存放的地方就是虚函数表!)
-
-
综上:类中有虚函数,多出的4字节属性是一个地址,指向一张虚函数表,里面存储了所有虚函数的地址
-
为了验证这是不是真的虚函数地址,可以使用函数指针的方法验证:定义一个函数指针,将这个虚函数地址值赋给函数指针,接着调用一下,看是否真的完成了method1该干的事情即可验证
#include "stdafx.h" class Person{ int x; int y; public: Person(){ x = 1; y = 2; } virtual void method1(){ printf("method1\n"); } virtual void method2(){ printf("method2\n"); } }; int main(int argc, char* argv[]){ Person person; typedef void (*pMethod)(void); //函数指针声明 pMethod pm1 = (pMethod)(*(int*)(*(int*)&person)); //定义pm1函数指针并将虚函数表中第一个地址值赋给pm1 pMethod pm2 = (pMethod)(*((int*)(*(int*)&person) + 1)); //定义pm2函数指针并将虚函数表中第二个地址值赋给pm2 //接着用函数指针的方式调用 pm1(); //method1 pm2(); //method2 return 0; }
发现:这两个地址,真的是虚函数的地址!
-
综上,有虚函数的类的对象的内存空间是这样的:
三、作业
1.重载和重写
- 重载:一个类中,函数名一样,参数的个数或类型不同
- 重写:子类中的函数和其父类中的函数名字、参数、返回值一模一样(函数覆盖)
2.继承与虚函数表
-
单继承无函数覆盖(打印Sub对象的虚函数表):
#include "stdafx.h" class Base{ public: virtual void Function_1(){ printf("Base:Function_1...\n"); } virtual void Function_2(){ printf("Base:Function_2...\n"); } virtual void Function_3(){ printf("Base:Function_3...\n"); } }; class Sub:public Base{ public: virtual void Function_4(){ printf("Sub:Function_4...\n"); } virtual void Function_5(){ printf("Sub:Function_5...\n"); } virtual void Function_6(){ printf("Sub:Function_6...\n"); } }; int main(int argc, char* argv[]){ Sub sub; Sub* subp = ⊂ subp->Function_1(); subp->Function_4(); return 0; }
sub对象的虚函数表如下:
-
单继承有函数覆盖(打印Sub对象的虚函数表)
#include "stdafx.h" class Base{ public: virtual void Function_1(){ printf("Base:Function_1...\n"); } virtual void Function_2(){ printf("Base:Function_2...\n"); } virtual void Function_3(){ printf("Base:Function_3...\n"); } }; class Sub:public Base{ public: virtual void Function_1(){ printf("Sub:Function_1...\n"); } virtual void Function_2(){ printf("Sub:Function_2...\n"); } virtual void Function_6(){ printf("Sub:Function_6...\n"); } }; int main(int argc, char* argv[]){ Sub sub; Sub* subp = ⊂ subp->Function_1(); //重写,则最终调用子类中的Function1() subp->Function_2(); //重写 subp->Function_3(); subp->Function_6(); return 0; }
sub对象的虚函数表如下:(只有4个了)