有同学问我什么是虚表hook,是不是很难? 其实相较于其他hook,虚表hook是最简单的.
我们先来复习下什么是虚函数.
虚函数是指父类中被声明, 子类中被重新定义的成员函数.
当类有虚函数的时候,会自动产生虚表.
比如这个人物对象下, 前8字节就是虚表地址(32位的话是4字节续表地址), 后面就是对象的属性.
游戏中的类继承关系极为复杂,重写的函数也特别多,所以基本上都是有虚表的.
虚函数被调用的时候是这样的流程
首先rcx是对象
通过mov rax,[rcx] 就得到了虚表地址
虚表地址+偏移里的值 可以得到其中某个虚函数的地址 这里是[rax+C08],对应的是走路,如下图,7FF8CD2AC46D就是走路函数地址
然后直接 call [rax+C08] 即可
如果把这个地址修改了他是不是就可以执行其他的函数了,甚至是我们自己写的函数.
下载xdbg等工具可以公众号 任鸟飞逆向.
那么了解了这个调用过程,和修改的基本思路,我们就可以对他进行hook了.
原理非常简单:
虚函数地址是通过虚表中获得的,
那么我们把虚表中的虚函数地址修改成我们自己的函数地址,
这样虚函数调用的时候就会调用到我们的函数了.
同时,为了不影响他原来的功能,
执行完我们的代码,我们再调用他原来的真正的虚函数即可.
那么可以上代码了:
void Ctextdialog::OnBnClickedButton14()
{
hookVirtualTable(*(uintptr_t*)Base_人物, 0xC08);
}
void Ctextdialog::OnBnClickedButton15()
{
uhookVirtualTable(*(uintptr_t*)Base_人物, 0xC08);
}
uintptr_t oldVirtualTable = 0;
void hookVirtualTFunc(uintptr_t RCX,uintptr_t RDX,uintptr_t R8,uintptr_t R9,uintptr_t arg5,uintptr_t arg6)
{
if (R8 == 1)
printf_rnf("人物跑: %.0f,%.0f,%.0f", *(FLOAT*)RDX, *(FLOAT*)(RDX+4), *(FLOAT*)(RDX+8));
else
printf_rnf("人物走: %.0f,%.0f,%.0f", *(FLOAT*)RDX, *(FLOAT*)(RDX + 4), *(FLOAT*)(RDX + 8));
Call(oldVirtualTable, RCX, RDX, R8, R9, arg5, arg6);//调用原来的虚函数 不影响他原来的代码
}
void hookVirtualTable(uintptr_t obj, uintptr_t offset)
{
uintptr_t virtualTable = *(uintptr_t*)obj;// 第一步先获得虚表
oldVirtualTable = *(uintptr_t*)(virtualTable + offset);//第二步保存原来的虚函数地址
DWORD old = 0;
VirtualProtect(PVOID(virtualTable + offset), 0x100, PAGE_EXECUTE_READWRITE, &old);
*(uintptr_t*)(virtualTable + offset) = (uintptr_t)hookVirtualTFunc;//第三步把虚函数地址写成我们自己的函数地址
VirtualProtect(PVOID(virtualTable + offset),0x100, old,&old);
}
void uhookVirtualTable(uintptr_t obj, uintptr_t offset)
{
uintptr_t virtualTable = *(uintptr_t*)obj;//第一步获得虚表
DWORD old = 0;
VirtualProtect(PVOID(virtualTable + offset), 0x100, PAGE_EXECUTE_READWRITE, &old);
*(uintptr_t*)(virtualTable + offset) = oldVirtualTable;//第二步 把原来虚函数地址 写回去
VirtualProtect(PVOID(virtualTable + offset), 0x100, old, &old);
}
这样就对走路功能进行了时时hook
拓展题目:
我们怎么快速获得 每个怪物的下一步动向?比如BOSS技能?
可以给老师留言你的答案.