虚表hook

虚表hook

虚表hook是一种address hook,通过修改虚表中的项实现hook。实际操作的时候还是有一些需要注意的地方。

技术原理

对象指针中保存着虚表的地址,我们取出这个地址,就得到了虚表。

cpp 复制代码
#include <Windows.h>

#include <iostream>

class TargetClass
{
public:
	virtual void vfunc()
	{
		std::cout << "TargetClass vfunc: " << this->number << std::endl;
	}

	virtual void vfunc2()
	{
		std::cout << "TargetClass vfunc2" << std::endl;
	}

protected:
	int number = 10;
};

class SubClass :public TargetClass
{
public:
	virtual void vfunc()
	{
		std::cout << "SubClass vfunc: " << this->number << std::endl;
	}

	virtual void vfunc2()
	{
		std::cout << "SubClass vfunc2" << std::endl;
	}
};

// detour函数需要一个参数保存this
void MyVFunc(PVOID rcx)
{
	PBYTE obj = (PBYTE)rcx;
	int number = *(int*)(obj + sizeof(ULONGLONG)); // 跳过隐式的vtable成员,得到number
	std::cout << "the number is " << number << std::endl;
}

void InstallHook()
{
}

void UninstallHook()
{

}

int main(int argc, char* argv[])
{
	TargetClass* obj = new SubClass;

	obj->vfunc();

	// 取出虚表地址,这里直接把虚表当作数组来访问
	PULONGLONG vtable = (PULONGLONG)(*(PULONGLONG)obj);

	// 需要修改虚表所在页的权限
	DWORD dwOld = 0;
	BOOL res =VirtualProtect((PVOID)vtable, sizeof(ULONGLONG), PAGE_READWRITE, &dwOld);
	if (!res)
	{
		std::cerr << "VirtualProtect Failed: " << GetLastError() << std::endl;
		return -1;
	}
	vtable[0] = (ULONGLONG)MyVFunc; // 修改虚表中的项
	res = VirtualProtect((PVOID)vtable, sizeof(ULONGLONG), dwOld, &dwOld);
	if (!res)
	{
		std::cerr << "VirtualProtect Failed: " << GetLastError() << std::endl;
		return -1;
	}

	obj->vfunc();

	return 0;
}

运行效果:

在我们的detour函数中的第一个参数需要用于接收this,这点是需要注意的。

两种实现方式

虚表hook有两种方式:

  1. 修改虚表
  2. 创建代理虚表

修改虚表

就像例子中的代码一样,通过修改特定的虚表项实现hook。这种方式实现简单,直接修改目标虚表中的函数指针即可。但它的缺点是全局生效------一旦修改了虚表,所有使用该虚表的对象实例都会受到影响,因为虚表本身是类级别的共享资源。

此外,修改虚表需要先通过 VirtualProtect 修改虚表所在内存页的权限(通常为 PAGE_READWRITE),修改完成后再恢复原始权限,否则可能引发访问违例或安全软件告警。

适用场景:临时hook、调试、或明确知道目标对象是唯一实例时使用。

创建代理虚表

创建代理虚表(Proxy VTable)是一种更隐蔽、更灵活的hook方式。其核心思路是:

  1. 分配一块新的内存,大小与原始虚表相同。
  2. 将原始虚表的内容复制到新内存,形成一份"代理虚表"。
  3. 修改代理虚表中的目标函数指针,指向我们的detour函数。
  4. 将目标对象的虚表指针(vptr)指向代理虚表

这样做的优势在于:只影响被修改了vptr的特定对象实例,而不会影响类中其他对象的虚函数调用,因此更加精准和隐蔽。

cpp 复制代码
void InstallProxyHook(TargetClass* obj)
{
    // 1. 获取原始虚表地址
    PULONGLONG originalVtable = (PULONGLONG)(*(PULONGLONG)obj);
    
    // 2. 计算虚表大小(假设有2个虚函数)
    const int vfuncCount = 2;
    SIZE_T vtableSize = sizeof(ULONGLONG) * vfuncCount;
    
    // 3. 分配代理虚表内存
    PULONGLONG proxyVtable = (PULONGLONG)VirtualAlloc(
        NULL, vtableSize, MEM_COMMIT, PAGE_READWRITE);
    if (!proxyVtable)
    {
        std::cerr << "VirtualAlloc Failed: " << GetLastError() << std::endl;
        return;
    }
    
    // 4. 复制原始虚表内容
    memcpy(proxyVtable, originalVtable, vtableSize);
    
    // 5. 修改代理虚表中的目标函数
    DWORD dwOld = 0;
    VirtualProtect(proxyVtable, vtableSize, PAGE_READWRITE, &dwOld);
    proxyVtable[0] = (ULONGLONG)MyVFunc; // hook vfunc
    VirtualProtect(proxyVtable, vtableSize, dwOld, &dwOld);
    
    // 6. 将对象的vptr指向代理虚表
    *(PULONGLONG*)obj = (ULONGLONG)proxyVtable;
}

注意事项

  • 代理虚表需要手动释放(VirtualFree),否则会造成内存泄漏。
  • 卸载hook时,需要将对象的vptr恢复为原始虚表地址,再释放代理虚表内存。
  • 如果对象在hook期间被析构,务必先恢复vptr,否则析构函数可能调用到错误的虚函数。

适用场景:需要精确控制单个对象行为、避免影响同类其他实例、或需要绕过某些反hook检测时使用。

相关推荐
阿昭L2 小时前
Windows用户态下常见的DLL注入技术总结
windows·逆向工程·dll注入
阿昭L3 天前
I Pack You加密壳:实现页粒度的动态解密和惰性加密
加密解密·逆向工程·加壳脱壳·软件保护
Lang-12105 天前
Frida + Android Hook 完整指南
android·逆向·hook·frida
RSCompany6 天前
Frida 17 以后 Python API 跑旧版 JS 报 Java is not defined ?一行 import 直接恢复 Frida 16 体验
开发语言·python·逆向·hook·frida·android逆向·frida17
caicongyang9 天前
Superpowers Hook 机制深度解析
hook·claude code·skills·superpowoer
阿昭L10 天前
I Pack You:实现基本的软件壳框架
逆向工程·pe文件·加壳
玫幽倩15 天前
2026盘古石取证初赛(APK取证)
python·电子取证·hook·wp·apk取证·盘古石·盘古石取证
booksyhay22 天前
串口调试助手注册机制研究(一)
逆向工程·串口调试助手·uartassist
带娃的IT创业者24 天前
当不可能成为可能:我将 Mac OS X 移植到了 Nintendo Wii
逆向工程·mac os x·极客·nintendo wii·操作系统移植·powerpc·硬件破解