概述
前言
虚函数表(VMT)Hook,又叫指针重定向,是一种常见的Hook技术,在游戏外挂程序中最常见。例如,使用VMTHook在Direct3D / OpenGL引擎游戏里实现内置叠加层。
虚函数表(VMT)
本文中VMT就代指虚函数表。
虚函数表是C++实现多态的一种方式。
每一个有虚函数的类(或有虚函数类的派生类)都有一个VMT,VMT本质上就是一个函数指针数组,通常位于对象内存布局的开头或结尾。每当C++类声明虚(virtual
)函数时,编译器都会增加一个条目到VMT中。
例如,在x86系统上使用VS2019编译以下代码:
class Base
{
public:
Base() { std::cout << "- Base::Basen"; }
virtual ~Base() { std::cout << "- Base::~Basen"; }
void A() { std::cout << "- Base::An"; }
virtual void B() { std::cout << "- Base::Bn"; }
virtual void C() { std::cout << "- Base::Cn"; }
};
class Derived final : public Base
{
public:
Derived() { std::cout << "- Derived::Derivedn"; }
~Derived() { std::cout << "- Derived::~Derivedn"; }
void B() override { std::cout << "- Derived::Bn"; }
void C() override { std::cout << "- Derived::Cn"; }
};
Base
类有三个虚函数:~Base
、B
和C
.
Derived
类派生自Base
,并重写了两个虚函数B
h和C
.
这里我们创建三个实例
Base base;
Derived derived;
Base* pBase = new Derived();
我们把代码跑起来,用调试器观察,发现Base
实例的VMT包含了~Base
、B
和C
而两个Derived
实例的VMT包含了~Derived
、B
和C
。但VMT里的函数地址与Base
实例中的不一样(见下图)
那么应该如何使用这些函数呢?
以一个函数为例,该函数获取一个指向Base
的指针并调用函数A
,B
和C
:
void Invoke(Base* const pBase)
{
pBase->A();
pBase->B();
pBase->C();
}
以以下方式调用:
Invoke(&base);
Invoke(&derived);
Invoke(pBase);
将Invoke
函数反汇编,看看在汇编层面,VMT内的函数是如何被调用的:
可以将RTC关闭(项目属性->C/C+±>代码生成->基本运行时检查->默认值),省去__RTC_CheckEsp等代码,让反汇编代码更简洁
对于B
的调用,编译器将pBase
也就是对象的地址移入EAX
寄存器,然后间接获取VTM的基地址,并将其存储在EDX
寄存器中。通过EDX
作为索引+4将函数地址存储在EAX
寄存器中,然后调用EAX
对C
的调用如出一辙,只是VMT中函数地址的偏移量为8。
由此可见,VMT的底层实现就是一个函数指针数组。
明白了VMT调用的原理,我们就可以很轻松的写一个函数来打印VMT:
void PrintVTable(Base* const pBase)
{
auto pVTableBase = *reinterpret_cast<void***>(pBase);
printf("First: %pn"
"Second: %pn"
"Third: %pn",
pVTableBase[0] , pVTableBase[1], pVTableBase[2]);
}
VMT Hook的实现
明白了VMT调用的原理,当然也就可以轻松的实现Hook了。
我们只要覆盖掉需要Hook的函数在VMT中的地址即可,这也解释了为什么VMT Hook也叫指针重定向。
void HookVMT(Base* const pBase)
{
auto pVTableBase = *reinterpret_cast<void***>(pBase);
SIZE_T ulOldProtect = 0;
VirtualProtect(&pVTableBase[1], sizeof(void*), PAGE_EXECUTE_READWRITE, &ulOldProtect);
pVTableBase[1] = VMTHookFnc;
VirtualProtect(&pVTableBase[1], sizeof(void*), ulOldProtect, &ulOldProtect);
}
VMTHook
定义:
void __fastcall VMTHookFnc(void* pEcx, void* pEdx)
{
Base* pThisPtr = (Base*)pEcx;
std::cout << "In VMTHookFncn";
}
这里利用
__fastcall
调用约定用来获取this
指针
成功Hook虚函数B
利用调试器,进入Hook函数中,可以看到this
指针的B
已经被替换成了VMTHookFuc
封装
剩下的就是封装了
这里的命名规则遵循STL标准库的小写规则
这里的实现是整个VMT替换,这样也可以方便的实现获取原函数。
vmt_hook.h
#include<memory>
class vmt_hook
{
public:
vmt_hook(void* obj, size_t num_funcs);
void hook(size_t index, void* func);
void unhook(size_t index);
template <typename T>
T get_original(size_t index);
void enable();
void disable();
private:
void*** m_object;
size_t m_num_funcs;
void** m_original_table;
std::unique_ptr<void*[]> m_new_table;
};
template<typename T>
inline T vmt_hook::get_original(std::size_t index)
{
return static_cast<T>(m_original_table[index]);
}
vmt_hook.cpp
#include "vmt_hook.h"
vmt_hook::vmt_hook(void* obj, size_t num_funcs) :
m_object(static_cast<void***>(obj)),
m_num_funcs(num_funcs + 1),
m_original_table(*m_object),
m_new_table(std::make_unique<void*[]>(m_num_funcs))
{
std::copy_n(m_original_table - 1, m_num_funcs, m_new_table.get());
}
void vmt_hook::hook(size_t index, void* func)
{
m_new_table[index + 1] = func;
}
void vmt_hook::unhook(size_t index)
{
m_new_table[index + 1] = m_original_table[index];
}
void vmt_hook::enable()
{
*m_object = m_new_table.get() + 1;
}
void vmt_hook::disable()
{
*m_object = m_original_table;
}
总结
- 执行速度:10
- 编写难度:3-5
- 检测率:3
VMT Hook是最好的Hook方法之一,因为没有API或者检测这类Hook的通用方法。
但大多数反作弊引擎都会在D3D渲染引擎上检测VMT Hook。当然,只要你有经验,你的Hook就不会被检测
最后
以上就是淡然万宝路为你收集整理的C++——Hook教程[1]:虚函数表(VMT)Hook前言虚函数表(VMT)VMT Hook的实现封装总结的全部内容,希望文章能够帮你解决C++——Hook教程[1]:虚函数表(VMT)Hook前言虚函数表(VMT)VMT Hook的实现封装总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复