虚表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检测时使用。

相关推荐
John_ToDebug2 天前
Windows客户端热修复技术:从原理到工程实践
c++·经验分享·hook
Amo Xiang4 天前
申万宏源证券新闻中心 —— AES/ECB 响应解密(摩斯电码派生密钥)
js逆向·python爬虫·逆向工程·aes加密·响应解密
DogDaoDao5 天前
C++核心技术深度剖析:从底层原理到工程实践
开发语言·c++·面试·程序员·指针·虚函数
忧云7 天前
x64dbg 反汇编逆向入门到实操:从安装到动手调试零基础完整教程
逆向工程·反汇编·x64dbg·windows 调试·软件逆向入门
阿昭L10 天前
通过KiSystemServiceUser获取SSDT基址
逆向工程·windows内核·ssdt
△曉風殘月〆12 天前
C#如何Hook托管函数
c#·hook
巷尚UP3D-三维扫描检测逆向建模13 天前
工业部件逆向工程与检测,Artec Leo三维扫描兼顾效率与精度【巷尚UP3D】
逆向工程·工业检测·高精度三维扫描
阿昭L16 天前
Lab 3-1
windows·安全·逆向工程·恶意代码分析
少司府17 天前
C++进阶:多态
c语言·开发语言·c++·多态·抽象类·虚函数·虚表指针
阿昭L17 天前
Lab 1-2
windows·恶意代码·逆向工程