掌握 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. ,