D3D Hook
之前做FPS的透视自瞄,都是通过三维坐标进行三角函数计算实现的方框自瞄,但是这种确实是有一定的占用内存的嫌疑,所以使用D3DHook是相对而言比较轻松的一种写法,而且也是比较固定的一个框架。
简单的D3D知识
1 | // 从索引缓存区绘制图元,参数 1 为图元格式,参数 4 为顶点数,参数 6 为三角形数 |
这句代码就是用来绘制我们的模型的,是位于:
1 | m_pDevice->BeginScene(); |
之间的一句代码,如果将其注释掉,那么将不会再绘制模型了,这也是我们实现Hook的一个关键点,因为我们在绘制完毕模型之后,我们就可以调用类中的一个方法:m_pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE );
这句代码如果不使用,默认为FALSE,但是开发人员为了将我们的模型隐藏在建筑物的后面,他会将我们的这个代码设置为TRUE,以此进行任务的遮挡,其实也就是我们的一个前面的物体遮挡后面物体的一个效果。
所以我们的基本思路也就定下来了,也就是我们Hook了DrawIndexedPrimitive
之后,然后将指定的模型SetRenderState
为FALSE就可以了。
实现
因为我们要Hook的DrawIndexedPrimitive
方法是一个成员方法,所以我们需要使用基址加偏移的方法进行一个Hook,其实C++的一个类成员方法的调用,第一个参数传递的就是成员本身,所以说,也就是调用了一个全局方法,然后将对象自身传递过去就实现了所谓的类方法调用,所以我们是要找到这个类方法的一个地址,但是这个地址比较特殊,我们是要使用基址加偏移的方法进行定位。
首先我们自己写一个这个方法,然后找到对应的地址:
1 | 00AA19A6 8B 46 78 mov eax,dword ptr [esi+78h] |
可以看到他call的函数并不是一个固定的地址,而是一个寄存器,寄存器的地址,其实也可以一步步推出来,比较关键的就是那个ESI,他的值是:0x012ffd38,但是我没校验是不是基地址,一般可以我感觉,但是这样有点麻烦,比较简单的方法就是直接进入edx那个地址,然后计算相对于DLL的一个偏移量,计算的时候用模块基地址加上偏移量就可以了。
然后我们跟进去看一下:
1 | 547348C0 8B FF mov edi,edi |
模块是:d3d9.DLL,一计算发现RVA为:0x548c0,前五个字节是正好的,所以我们也就没啥麻烦的了,直接Hook就好了。
这里其他的一些代码我就不列出来了,因为不关键,我就写关键的:
首先我们需要一个计算地址的函数:
1 | ULONG_PTR GetDrawIndexedPrimitiveAddress() |
这样子就能找到函数的地址了,因为我们的DLL地址会变,所以每次都要找的。
然后当我们的DLL被注入的时候我们就去HOOKDrawIndexedPrimitive
这个函数:
1 | bool HookDrawIndexedPrimitive() |
获取到函数地址之后,我们就要写HOOK,MyDrawIndexedPrimitive
这个使我们要跳到的地址,我们这里使用的HOOK方法我是看别人这么写的,大同小异,也就是第一个e9为jmp,然后再将我们的地址放进去就好了,简单计算一下得到value就可以了。
1 | //这个WINAPI加不加代表返回的时候是retn还是retn 1c |
这里可以看到,原函数其实是6个参数,但是我们要七个参数,其实也就是我们要先接受自身,然后我们在最后的时候要跳回去:
1 | DWORD jmpto = 0; |
一个空函数,jmpto是在我们之前定义的,这里需要注意的是我们需要使用WINAPI表示我们这个函数是stdcall的调用约定,这里我踩坑了,否则我们在函数执行结束之后是不能进行自动清理堆栈的。
这里我们就是一个基本的D3D操作,意思就是说iStride深度缓存的值未16的时候我们就让他透视,其实这个16是我们需要调试寻找的,可以通过很多手段,例如共享内存操作之类的,也可以使用设置新的lang来实现,反正很多都是可以找到的,但是我们找到16之后并不是万事大吉,因为进入游戏发现地图也没有了,所以我们要判断NumVertices的值,这个是模型的顶点数,也是需要寻找的,但是却不是最好的方法,最好的方法是我们通过附加游戏找到调用DrawIndexedPrimitive
这个函数的外层call找到有没有一个标识来代表是否可以表示人,我们再通过hook之类的判断来告诉我们是不是需要清除深度缓存。