D3D HOOK实现透视讲解
实现目的:
目前大部分游戏通过Direct3D实现3D效果,通过挂钩相应函数,可以实现3D透视,屏幕挂字效果。而透视,屏蔽特定效果,设置透明在很多游戏(特别是FPS)中发挥着巨大的作用!
实现思路:
[D3D]
DirectX的功能都是以COM组件的形式提供的。在Direct3D中,主要通过采取以下操作来实现编程:
调用适当的函数获取接口指针;
调用接口的方法(成员函数)来完成所需功能;
用完接口后,调用Release方法进行“释放”,注意释放顺序应该和获取它们的顺序相反。
D3D的实现流程:
大体可以分为:设计,渲染和显示三个部分。通过设计物体的顶点,贴图,材质等信息,并将坐标转换为屏幕坐标后,调用渲染方式,根据坐标变化,材质文理等计算亮度,进行背面消除,裁剪,投影和视口计算,最后在后备缓冲中绘制好图形交换到当前缓冲区。
设计阶段:
这里介绍部分概念方便大家理解
图元
在d3d编程中,所有的图形都是由图元组成的,例如:
这些图元分为点列,线列,线带,三角形列,三角形带,三角扇形
顶点缓存
顶点缓存通常除顶点坐标之外还包括法线,颜色,纹理等数据。
索引缓存
索引缓存就是将顶点的具体数据和代表图元格式的顶点顺序分开存储:顶点数据仍然放到顶点缓存区中,索引缓存区则按照图元格式,顺序存放顶点的索引。
如
A,B,C,D对应的顶点缓存索引为0 1 2 3,按照三角形的列的组成顺序,把顶点索引值存入索引缓存区,4个三角形分别为△ACB、△ADC、△ABD、△BCD(注意顶点排列顺序和可视面的关系),则索引序列为0 2 1 0 3 2 0 1 3 1 2 3。这样原本要用12个顶点数据构建一个三棱锥,使用索引缓存后,只需要4个。
Z缓存
在Direct3D中,使用深度缓存区(Depth Buffer)来进行消隐处理(隐藏面消除),以确保实体被遮挡的部分不被显示。Z缓存是最常用的一种深度缓存,它因为用Z坐标作为判断深度(远近)的依据而得名,其工作原理如图14所示,图中的渲染表面相当于Direct3D窗口,Z缓存用来保存窗口中各个像素的深度。在消隐时,Direct3D先用背景色(或纹理)填充渲染表面,Z缓存则统一设置成最大深度,即投影变换中后裁剪平面的距离,然后逐像素处理渲染表面:对于任意一个像素,Direct3D逐一测试所有与该像素重叠的三角形,如果三角形中像素对应点的Z坐标小于Z缓存中的数值,也就是说,此三角形离观察者较近,则像素取该点的颜色,同时像素在Z缓存中的深度也设为该点的Z坐标,然后继续测试下一个三角形... ...
坐标变换
渲染
在Direct3D中,一个设备对象至少包含两个显示缓存区:当前缓存区(Front Buffer)和后备缓存区(Back Buffer),前者可以看成Direct3D窗口的映射。当我们渲染图形时,实际上并不是直接在窗口上输出,而是在后备缓存区上绘图。渲染完毕后,交换两个缓存区,使原来的后备缓存区变成当前缓存区,从而实现窗口刷新。快速重复此过程,就会在屏幕上形成连续的动画
有了上述的一些基本概念之后,我们就可以编写出自己的d3d Demo:
详情见demo
[D3D HACK]
通过上述了解,我们发现渲染中有个函数SetRenderState()可以设置渲染状态,通过设置不同的参数既可以实现我们想要的功能:
透视:
SetRenderState(D3DRS_ZENABLE, FALSE)
去除烟雾:
SetRenderState(D3DRS_FOGENABLE, FALSE)
设置多边形填充模式:
SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) //线填充模式,D3D在多边形的每个边绘制一条线
......
[D3D HOOK]实现
分析发现在绘制图形之前,通过顶点缓存,然后调用DrawIndexedPrimitive或者DrawPrimitive来绘制图形。所以通过hook此函数就能感知绘图操作,进而实现我们想要的功能。
可以知道Direct3D在内存中的布局为:
为了得到偏移,我们调试一下:
单步到call edx
此时打开pchunter,查看d3d9.dll所在地址
通过计算可以得到偏移为:+2B6B1(此偏移只针对win7,不保证其他平台下的偏移是否正确)
我们构造寻址函数
HANDLE handle = GetModuleHandle(L"d3d9.dll");
if (handle == INVALID_HANDLE_VALUE)
{
OutputDebugString(L"get d3d9.dll failed\n");
return NULL;
}
return (ULONG_PTR)handle + 0x2B6B1;
构造跳转型型hook
if(VirtualProtect((LPVOID)g_uladdr,5,PAGE_EXECUTE_READWRITE,&dwoldprotect))
{
DWORD dwjmp = (DWORD)New_DrawIndexedPrimitive - g_uladdr - 5;
_asm
{
mov eax, g_uladdr
mov byte ptr[eax], 0xe9
add eax, 1
mov ebx, dwjmp
mov dword ptr[eax], ebx
}
}
VirtualProtect((LPVOID)g_uladdr,5,dwoldprotect,&dwoldprotect);
构造hook函数
查找DrawIndexedPrimitive定义:
HRESULT DrawIndexedPrimitive(
[in] D3DPRIMITIVETYPE Type,
[in] INT BaseVertexIndex,
[in] UINT MinIndex,
[in] UINT NumVertices,
[in] UINT StartIndex,
[in] UINT PrimitiveCount
);
官方给了6个参数,通过调试我们发现,需要将this传进去
故构造函数如下
HRESULT WINAPI New_DrawIndexedPrimitive(
LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE Type,
INT BaseVertexIndex,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
)
{
//做你想要的功能
return Old_DrawIndexedPrimitive(m_pDevice, Type,BaseVertexIndex, MinIndex, NumVertices, StartIndex, PrimitiveCount);
}
构建跳转
分析函数头5个字节
构建跳转
__declspec(naked) HRESULT WINAPI Old_DrawIndexedPrimitive(
LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE Type,
INT BaseVertexIndex,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
)
{
_asm
{
mov edi,edi
push ebp
mov ebp, esp
mov eax, g_dwjmpto
jmp eax
}
}
注入游戏:
实现透视
实现线框透明:
Direct3D的核心功能集中在IDirect3DDevice9的接口中,只要能hook其中的EndScence(), DrawPrimitive()或DrawIndexedPrimitive()就能感知绘图操作,进而实现我们想要的功能!
jpg改rar