掌握 C++ 对象模型底层知识的人都知道, C++ 利用虚函数的机制来实现运行期的多态。
例如一个类申明如下:
class A
{
public:
A(){}
~A(){}
virtual void f1(){ printf("Founction f1 called"); }
virtual void f2(){ printf("Founction f2 called"); }
virtual void f3(){ printf("Founction f3 called"); }
private:
int n;
};
那么 A 对象在内存中的结构图大概如下 :
如上图:可以看到
A
对象的前
4
个字节是虚函数表的指针
vptr
,而虚函数表本身又是一个数组。所以
vptr
可以看作一个指向指针的指针。
那么已知
pA
为
A
对象指针,我们如果想得到虚函数表的地址,只需要如下即可。
long** pplVrtable= (long**)(pA);
我们可以为类
A
添加一个成员函数确认一下。
void A::reset_f1()
{
long** pplVrtable= (long**)(this); //
取得虚函数表的指针
*pplVrtable = *pplVrtable +1;//
将虚函数表的指针指向虚函数表第二个值。
}
测试代码:
int main(int argc, char* argv[])
{
A* pA = new A;
pA->reset_f1();
printf("Begin to call founction f1./n");
pA->f1();
delete pA;
return 0;
}
运行输出:
Begin to call founction f1.
Founction f2 called.
结果证实虚函数表的指针已经被成功修改,对于成员函数
f1
的调用变成了对
f2
的调用
.
但是我们这里修改的只是虚函数指针,那么我们可不可以直接修改
虚函数表那?
试一下看看,修改代码如下:
void A::reset_f1()
{
long** pplVrtable= (long**)(this); //
取得虚函数表的指针
(*pplVrtable)[0]= (*pplVrtable)[1];//
将虚函数表的第一个值设置为虚函数表第二个值。
}
运行
,
结果程序
crash
了
,
看样子虚函数表这块内存是被系统保护了
,
看似山重水复疑无路了
,
不过没关系
,Windows
提供了一组针对内存保护的函数
:
VirtualQueryEx, VirtualProtectEx,(
相关定义和使用方法
,
可以看看
MSDN).
利用这两个函数我们可以实现修改
虚函数表的功能
,
再次修改代码如下
:
void reset_f1()
{
long** pplVrtable= (long**)(this);
HANDLE hProcess
= OpenProcess(PROCESS_ALL_ACCESS, FALSE, ::GetCurrentProcessId());
MEMORY_BASIC_INFORMATION mbi = {0};
if (VirtualQueryEx(hProcess, (LPVOID)(*pplVrtable), &mbi, sizeof(mbi)) != sizeof(mbi))
return;
DWORD dwOldProtect = 0;
if(!::VirtualProtectEx(hProcess, mbi.BaseAddress, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect))
return;
(*pplVrtable)[0] = (*pplVrtable)[1];
DWORD dwTemp = 0;
::VirtualProtectEx(hProcess, mbi.BaseAddress, 4, dwOldProtect, &dwTemp);
CloseHandle(hProcess);
}
运行输出:
Begin to call founction f1.
Founction f2 called.
结果与预期一样
,
虚函数表终于被成功修改了
.
因为
COM
接口是没有成员变量的只有纯虚函数的类
,
其虚函数指针在内存分配上具有唯一性
,
所以我们可以通过以上的技术实现对所有
COM
接口成员函数的
HOOK. ,