首先声明,高手飘过。内容比较低层次,只是很有趣。
这几天接触了不少虚函数的概念,公司的软件就是使用了纯虚基类作为接口,派生子类增强软件适用性的。
于是,兴趣浓了起来,所以搞了一套很有趣的东西,和大家分享。希望大家不要扔臭鸡蛋砸了俺的摊子。
定义两个类,如下:
#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掉。
- 标 题:偷梁换柱虚函数
- 作 者:安摧
- 时 间:2008-08-31 17:53
- 链 接:http://bbs.pediy.com/showthread.php?t=71775