首先声明,高手飘过。内容比较低层次,只是很有趣。
   


这几天接触了不少虚函数的概念,公司的软件就是使用了纯虚基类作为接口,派生子类增强软件适用性的。

 

       于是,兴趣浓了起来,所以搞了一套很有趣的东西,和大家分享。希望大家不要扔臭鸡蛋砸了俺的摊子。

 

定义两个类,如下:

 

#include <iostream.h>

 

class A

{

public:

       int iVal1;

       int iVal2;

       virtual void print1()

       {

              cout<<"iVal1(A) = "<<iVal1<<endl;

       }

       virtual void print2()

       {

              cout<<"iVal2(A) = "<<iVal2<<endl;

       }

       virtual void print_all()

       {

              cout<<"iVal1(A) = "<<iVal1<<"\t\t"<<"iVal2(A) = "<<iVal2<<endl;

       }

       virtual void print_extern(int ext)

       {

              cout<<"ext(A) = "<<(ext+0)<<endl;

       }

};

 

 

class B

{

public:

       int iVal1;

       int iVal2;

       virtual void print1()

       {

              cout<<"iVal1(B) = "<<iVal1<<endl;

       }

       virtual void print2()

       {

              cout<<"iVal2(B) = "<<iVal2<<endl;

       }

       virtual void print_all()

       {

              cout<<"iVal1(B) = "<<iVal1<<"\t\t"<<"iVal2(B) = "<<iVal2<<endl;

       }

       virtual void print_extern(int ext)

       {

              cout<<"ext(B) = "<<(ext+100)<<endl;

       }

};

 

       然后实例化,打印一些结果。

 

void main(void)

{

       A a;

       a.iVal1 = 0;

       a.iVal2 = 1;

 

       B b;

       b.iVal1 = 1000;

       b.iVal2 = 1001;

 

       A* pA = &a;

       B* pB = &b;

 

       load_hook(pA);

 

       pA->print1();

       pA->print2();

       pA->print_all();

       pA->print_extern(-200);

 

       cout<<endl;

 

       pB->print1();

       pB->print2();

       pB->print_all();

       pB->print_extern(-200);

}

 

打印输出:

 

iVal1(A) = 0

iVal2(A) = 1

iVal1(A) = 0            iVal2(A) = 1

ext(A) = -200

 

iVal1(B) = 1000

iVal2(B) = 1001

iVal1(B) = 1000         iVal2(B) = 1001

ext(B) = -100

 

       大家应该比较熟悉这两个类的内存结构,如图:



类对象(a或b)中最前面四个字节为虚表指针,然后就是两个成员变量。

 

这些都不会出乎大家的意料,没有什么值得惊奇的。

 

Mission 1:对象a去调用类B的函数
方法:寄存器ecx中放入对象a的地址,然后找到类B相应的函数指针,调用之。

 

代码:

 

void main(void)

{

       A a;

       a.iVal1 = 0;

       a.iVal2 = 1;

 

       B b;

       b.iVal1 = 1000;

       b.iVal2 = 1001;

 

       A* pA = &a;

       B* pB = &b;

 

       _asm

       {

              push eax                              //暂存eax寄存器到堆栈中

              push ecx                              //暂存ecx寄存器到堆栈中

              mov eax,dword ptr [pB]        //调入b对象的地址

              mov ecx,dword ptr [pA]              //调入a对象的地址到ecx中,实际上就是引用a对象的变量

              mov eax,dword ptr [eax]              //取得b对象的虚表地址(b对象的第一个元素)

              add eax,8                             //虚表地址+8,即b对象的第三个函数B::print_all()的地址

              call dword ptr [eax]              //调用类B的函数B::print_all(),但是实际上传入a对象的变量,开始写错了,写成“调用b对象的函数B::print_all(),但是实际上传入a对象的变量”,感谢楼下的兄弟

              pop ecx                                      //从堆栈中弹回ecx

              pop eax                                      //从堆栈中弹回eax

       }

}

 

打印输出:

 

iVal1(B) = 0            iVal2(B) = 1

 

Mission 2:交换对象a和对象b的所有虚函数
方法:很简单,就是交换两个对象的虚表指针。




void main(void)

{

       A a;

       a.iVal1 = 0;

       a.iVal2 = 1;

 

       B b;

       b.iVal1 = 1000;

       b.iVal2 = 1001;

 

       A* pA = &a;

       B* pB = &b;

 

       _asm

       {

              push eax                              //将四个需要使用的寄存器压入堆栈

              push ecx

              push esi

              push edi

              mov esi,dword ptr [pA]        //取得a对象地址

              mov edi,dword ptr [pB]        //取得b对象地址

              mov eax,dword ptr [esi]        //读取a对象虚表地址到eax中

              mov ecx,dword ptr [edi]              //读取b对象虚表地址到ecx中

              mov dword ptr [edi],eax              //将a对象虚表地址写到b中

              mov dword ptr [esi],ecx        //将b对象虚表地址写到a中

              pop edi

              pop esi

              pop ecx

              pop eax

       }

 

       pA->print1();

       pA->print2();

       pA->print_all();

       pA->print_extern(-200);

 

       cout<<endl;

 

       pB->print1();

       pB->print2();

       pB->print_all();

       pB->print_extern(-200);

}

 

打印输出:

 

iVal1(B) = 0

iVal2(B) = 1

iVal1(B) = 0            iVal2(B) = 1

ext(B) = -100

 

iVal1(A) = 1000

iVal2(A) = 1001

iVal1(A) = 1000         iVal2(A) = 1001

ext(A) = -200

 

Mission 3:交换类A和类B的第二个和第四个函数
方法:需要修改虚表,交换类A和类B的第二、四个函数。




void main(void)

{

       A a;

       a.iVal1 = 0;

       a.iVal2 = 1;

 

       B b;

       b.iVal1 = 1000;

       b.iVal2 = 1001;

 

       A* pA = &a;

       B* pB = &b;

 

       _asm

       {

              push eax                              //压入需要使用的寄存器到堆栈中

              push ecx

              push esi

              push edi

              mov esi,dword ptr [pA]        //a对象指针

              mov edi,dword ptr [pB]        //b对象指针

              mov esi,dword ptr [esi]         //a对象虚表地址

              mov edi,dword ptr [edi]        //b对象虚表地址

              add esi,4

              add edi,4

              mov eax,dword ptr [esi]        //交换第二个函数地址:print2

              mov ecx,dword ptr [edi]

              mov dword ptr [edi],eax

              mov dword ptr [esi],ecx

              add esi,8

              add edi,8

              mov eax,dword ptr [esi]        //交换第四个函数地址:print_extern

              mov ecx,dword ptr [edi]

              mov dword ptr [edi],eax

              mov dword ptr [esi],ecx

              pop edi

              pop esi

              pop ecx

              pop eax

       }

 

       pA->print1();

       pA->print2();

       pA->print_all();

       pA->print_extern(-200);

 

       cout<<endl;

 

       pB->print1();

       pB->print2();

       pB->print_all();

       pB->print_extern(-200);

}

 

但是这样还是不行,运行时会产生异常,因为虚表在.rdata区段,而此区段默认是只读的,所以在代码中添加:

 

#pragma comment(linker,"/SECTION:.rdata,RW")

 

打印输出:

 

iVal1(A) = 0

iVal2(B) = 1

iVal1(A) = 0            iVal2(A) = 1

ext(B) = -100

 

iVal1(B) = 1000

iVal2(A) = 1001

iVal1(B) = 1000         iVal2(B) = 1001

ext(A) = -200

 

Mission 4:hook类A的第三个函数
方法:比较麻烦,但是也是不难弄的。将类A的第三个虚函数指针替换成我们自己定义的地址。




首先先写两个函数:load_hook和unload_hook

 

void* g_pAddr = 0;

int g_count = 0;

 

void load_hook(A* pA)

{

       _asm

       {

              push eax

              push ecx

              mov eax,dword ptr [pA]              //获取对象指针

              mov eax,dword ptr [eax]              //获取虚表地址

              add eax,8                             //获取虚表中类A第三个函数指针的地址

              mov ecx,dword ptr [eax]              //取出类A第三个函数指针

              mov dword ptr [g_pAddr],ecx      //保存到g_pAddr变量中

              mov ecx,offset hook_proc     //替换为hook_proc指针

              mov dword ptr [eax],ecx

              pop ecx

              pop eax

              jmp hook_end

       }

hook_proc:

       _asm

       {

              push ecx

       }

 

       g_count++;

       cout<<g_count<<" time(s) to invoke A::print_all()"<<endl;//A::print_all的记数

 

       _asm

       {

              pop ecx

              jmp dword ptr [g_pAddr]

       }

hook_end:

       cout<<"A::print_all() hooked!"<<endl;

}

 

void unload_hook(A* pA)

{

       _asm

       {

              push eax

              push ecx

              mov eax,dword ptr [pA]              //获取对象指针

              mov eax,dword ptr [eax]              //获取虚表地址

              add eax,8                             //获取虚表中类A第三个函数指针的地址

              mov ecx,dword ptr [g_pAddr]      //取出事先保存的A::print_all()地址

              mov dword ptr [eax],ecx

              pop ecx

              pop eax

       }

       g_count = 0;

       cout<<"A::print_all() unhooked!"<<endl;

}

 

主程序中:

 

void main(void)

{

       A a;

       a.iVal1 = 0;

       a.iVal2 = 1;

 

       B b;

       b.iVal1 = 1000;

       b.iVal2 = 1001;

 

       A* pA = &a;

       B* pB = &b;

 

       load_hook(pA);

 

       pA->print1();

       pA->print_all();

       pA->print_all();

       pA->print_all();

 

       unload_hook(pA);

 

       pA->print1();

       pA->print_all();

       pA->print_all();

       pA->print_all();

}

 

自然,这里也需要添加:

 

#pragma comment(linker,"/SECTION:.rdata,RW")

 

打印输出:

 

A::print_all() hooked!

iVal1(A) = 0

1 time(s) to invoke A::print_all()

iVal1(A) = 0            iVal2(A) = 1

2 time(s) to invoke A::print_all()

iVal1(A) = 0            iVal2(A) = 1

3 time(s) to invoke A::print_all()

iVal1(A) = 0            iVal2(A) = 1

A::print_all() unhooked!

iVal1(A) = 0

iVal1(A) = 0            iVal2(A) = 1

iVal1(A) = 0            iVal2(A) = 1

iVal1(A) = 0            iVal2(A) = 1

 

结论:

一、上面的东西作为大家学习虚函数后的习题更恰当一些。没有什么难度,但是很有意思,呵呵。

二、Mission4中的hook还有其他一些办法。

三、还有一个Mission,没有时间做了。

Mission5:自己建立一张虚表,将对象的虚表指针指向自建的表,将指定对象的整张虚表hook掉。

  • 标 题:upload the code
  • 作 者:安摧
  • 时 间:2008-08-31 19:54

上传一下代码
:)


四个Mission

上传的附件 Mission.rar