您的位置:首页 > 运维架构 > Shell

D3D游戏降帧的动态创建D3D设备以及ShellCode HOOK玩法

2014-08-05 11:21 579 查看
欢迎转载,转载请注明出处:http://blog.csdn.net/gnorth/article/details/9327971

说白了,也就是HOOK掉Present,这种代码,其实百度上某些地方有,但是很多人估计不知道怎样得到Present的地址。

所以就有些奇葩的例子:

先到游戏的登录器内把CreateProcess之类的HOOK掉,让游戏进程暂停启动,然后注入游戏 HOOK Direct3DCreate9 得到 IDirect3D9 对象之后,又得到 IDirect3DDevice9 对象,最终得到Present,反正挺蛋疼的,而且就是在游戏创建对象前要HOOK掉,用户体验非常的差。

其实自己创建个对象,从虚函数表就拿到真正的函数地址了。

本文的一大堆废话只是为了说明实现流程,由于写得比较仓促,所以嘛,估计要有一定基础的人才看得懂,最后面有完整的代码,你不想理解流程的话直接复制去用吧。

创建对象的代码如下:

BOOL InitializeD3D9(HWND hWnd, IDirect3D9 **ppD3D9, IDirect3DDevice9 **ppDev)

{

*ppD3D9 = Direct3DCreate9(D3D_SDK_VERSION);

if(!(*ppD3D9))

return FALSE;

D3DDISPLAYMODE d3ddm;

HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

if(FAILED(hr))

{

(*ppD3D9)->Release();

return FALSE;

}

D3DPRESENT_PARAMETERS d3dpp = {0};

d3dpp.Windowed= TRUE;

d3dpp.SwapEffect= D3DSWAPEFFECT_DISCARD;

d3dpp.BackBufferFormat= d3ddm.Format;

hr = (*ppD3D9)->CreateDevice(

D3DADAPTER_DEFAULT,

D3DDEVTYPE_HAL,

hWnd,

D3DCREATE_SOFTWARE_VERTEXPROCESSING,

&d3dpp,

ppDev);

if(FAILED(hr))

{

(*ppD3D9)->Release();

return FALSE;

}

return TRUE;

}

void ReleaseD3D9(IDirect3D9 *pD3D9, IDirect3DDevice9 *pDev)

{

if(pDev)

pDev->Release();

if(pD3D9)

pD3D9->Release();

}

LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset) //根据偏移从虚函数表获取函数地址

{

LPVOID pRet = NULL;

__asm{

mov eax, dword ptr [pDev]

mov ecx, dword ptr [eax]

mov ebx, dwOffset

mov eax, dword ptr [ecx + ebx]

mov pRet, eax

}

return pRet;

}

这个代码要有DXSDK才行,需要依赖这两个,d3d9.h d3d9.lib,由于这种比较恶心的代码,一般就是静态库里一丢就永远不管了,但是呢,丢静态库里,假如不用这种功能的代码调用静态库时,就要出警告提示你,你木有用到d3d9.dll中的代码云云,非常不爽。

所以针对这个问题,我就自己调试了下,写了一个动态调用创建D3D设备对象的代码出来:最后面有完整的代码,你不想理解流程的话直接复制去用吧。

大概流程如下:

首先 Direct3DCreate9 是个标准API导出函数,所以,直接加载d3d9.dll,获取它即可

HMODULE hD3d9 = LoadLibraryA("d3d9.dll");

LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");

__asm{

push 32 //D3D_SDK_VERSION 32

call _Direct3DCreate9

mov pD3D9, eax

}

if(!pD3D9)

return false;

这样就得到了接口对象,接下来是Release 以及 GetAdapterDisplayMode

77: (*ppD3D9)->Release();

00401243 8B 4D 0C mov ecx,dword ptr [ebp+0Ch]

00401246 8B 11 mov edx,dword ptr [ecx]

00401248 8B 45 0C mov eax,dword ptr [ebp+0Ch]

0040124B 8B 08 mov ecx,dword ptr [eax]

0040124D 8B 01 mov eax,dword ptr [ecx]

0040124F 8B F4 mov esi,esp

00401251 52 push edx

00401252 FF 50 08 call dword ptr [eax+8]

它的偏移是8

GetAdapterDisplayMode

72: HRESULT hr = (*ppD3D9)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

00401209 8B F4 mov esi,esp

0040120B 8D 45 EC lea eax,[ebp-14h]

0040120E 50 push eax

0040120F 6A 00 push 0

00401211 8B 4D 0C mov ecx,dword ptr [ebp+0Ch]

00401214 8B 11 mov edx,dword ptr [ecx]

00401216 8B 45 0C mov eax,dword ptr [ebp+0Ch]

00401219 8B 08 mov ecx,dword ptr [eax]

0040121B 8B 01 mov eax,dword ptr [ecx]

0040121D 52 push edx

0040121E FF 50 20 call dword ptr [eax+20h]

偏移为0x20

LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);

LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);

//先调用_GetAdapterDisplayMode

//这里要传入一个 D3DDISPLAYMODE结构指针,但是外面并不需要对这个结构进行操作,所以这里只需要知道结构大小,然后分配一个一样大的内存给它就好。

//sizeof(D3DDISPLAYMODE) 大小为 0x10

BYTE d3ddm[0x10]

HRESULT hr = NULL;

__asm{

lea eax, d3ddm

push eax

push 0

push pD3D9

call _GetAdapterDisplayMode

mov hr, eax

}

if(FAILED(hr)) // hr != S_OK;就释放对象,说明到这里就创建失败了,具体为啥失败我不懂

{

__asm{

push pD3D9

call d3d9_Release

}

return false;

}

接下来要用到 D3DPRESENT_PARAMETERS 结构,这个结构要在外面填参数了,所以,就要去看他的原型,看哪些是我们需要填的偏移。

它的大小是0x38, 注意我后面注释的值是偏移。

typedef struct _D3DPRESENT_PARAMETERS_

{

UINT BackBufferWidth; //0

UINT BackBufferHeight; //4

D3DFORMAT BackBufferFormat; //8

UINT BackBufferCount; //c

D3DMULTISAMPLE_TYPE MultiSampleType; //10 enum

DWORD MultiSampleQuality; //14

D3DSWAPEFFECT SwapEffect; //18 enum

HWND hDeviceWindow; //1C

BOOL Windowed; //20

BOOL EnableAutoDepthStencil;//24

D3DFORMAT AutoDepthStencilFormat;//28 enum

DWORD Flags; //2c

/* FullScreen_RefreshRateInHz must be zero for Windowed mode */

UINT FullScreen_RefreshRateInHz;//30

UINT PresentationInterval;
//34

} D3DPRESENT_PARAMETERS;

我们可以从结构上看到,需要操作的偏移如下

Windowed; //20

SwapEffect; //18

BackBufferFormat; //8

然后代码中可以看到,BackBufferFormat是从d3ddm里过来的

结构是这样, Format的偏移为0x0c

typedef struct _D3DDISPLAYMODE

{

UINT Width;

UINT Height;

UINT RefreshRate;

D3DFORMAT Format; //0x0c

} D3DDISPLAYMODE;

所以代码如下:

BYTE d3dpp[0x38];

memset(d3dpp, 0, 0x38);//如果不把结构填充为0,创建设备就会失败。

__asm{

lea eax, d3dpp

mov dword ptr [eax + 0x20], 1

mov dword ptr [eax + 0x18], 1

lea ebx, d3ddm

mov ecx, dword ptr [ebx + 0x0C]

mov dword ptr [eax + 8], ecx

}

最后就是创建设备的代码

86: hr = (*ppD3D9)->CreateDevice(

87: D3DADAPTER_DEFAULT,

88: D3DDEVTYPE_HAL,

89: hWnd,

90: D3DCREATE_SOFTWARE_VERTEXPROCESSING,

91: &d3dpp,

92: ppDev);

00401287 8B F4 mov esi,esp

00401289 8B 55 10 mov edx,dword ptr [ebp+10h]

0040128C 52 push edx

0040128D 8D 45 B4 lea eax,[ebp-4Ch]

00401290 50 push eax

00401291 6A 20 push 20h

00401293 8B 4D 08 mov ecx,dword ptr [ebp+8]

00401296 51 push ecx

00401297 6A 01 push 1

00401299 6A 00 push 0

0040129B 8B 55 0C mov edx,dword ptr [ebp+0Ch]

0040129E 8B 02 mov eax,dword ptr [edx]

004012A0 8B 4D 0C mov ecx,dword ptr [ebp+0Ch]

004012A3 8B 11 mov edx,dword ptr [ecx]

004012A5 8B 0A mov ecx,dword ptr [edx]

004012A7 50 push eax

004012A8 FF 51 40 call dword ptr [ecx+40h] <-----------偏移0x40

LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);

//0x40

__asm{

lea eax, pD3D9Dev

push eax

lea eax, d3dpp

push eax

push 0x20

push hWnd

push 1

push 0

push pD3D9

call _CreateDevice

mov hr, eax

}

if(FAILED(hr))

{

__asm{

push pD3D9

call d3d9_Release

}

return false;

}

最终得到设备对象后,就用同样的方法,从虚函数表获取真正的函数地址, Present的偏移为0x44

inline钩子函数,大概如下:

typedef HRESULT (WINAPI *DEFPRESENT)(LPDIRECT3DDEVICE9,CONST RECT *,CONST RECT *,HWND,CONST RGNDATA *);

DEFPRESENT PresentData = NULL;

HRESULT WINAPI _Present(LPDIRECT3DDEVICE9 p1, CONST RECT *p2, CONST RECT *p3, HWND p4, CONST RGNDATA *p5)

{

Sleep(64);//这里也就是降帧了

return PresentData(p1, p2, p3, p4, p5);

}

但是,这种代码,一般得写在DLL里才行,要是DLL卸载出来了,代码自然就没了,你不卸载HOOK就是一个字,“崩”!

而有些非常时期,却又不得不卸载DLL。所以嘛,就自然有了ShellCode的代码。

ShellCode不必完全模仿完善的inline hook 钩子函数,因为要模仿起来太累了, 因为存在PresentData中间代码实际上也是ShellCode。

所以大概原型如下:

__asm{

pushad

pushfd

//这里call Sleep

以及执行它的前5个字节

mov edi, edi

push ebp

mov ebp, esp 好像是这个样子

popfd

popad

jmp 原始Present + 5

}

这个代码实现起来不难,要注意的是,Sleep是不能直接调用的,因为Sleep实际上会被编译器进行包装,分配一个指针来包装它,然后call dword ptr [包装的指针]

而这个指针,在你的dll卸载时,也会无效掉。所以我们应该自己包装Sleep

PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);

*(PDWORD)psc = (DWORD)Sleep;

//使用VirtualAllocEx进行分配内存,使用MEM_COMMIT | MEM_RESERVE模式,这样分配出来的内存不会跟随你的DLL一起被释放掉。

//其他的几种方式 new 会跟随dll释放

//C函数库的 malloc也会释放

//GlobalAlloc 不能改变为可执行可读写保护模式

//还好最后剩VirtualAllocEx能用

//其实我早就感觉,dll卸载时会把dll new 出来的各种内存都释放掉了,也就是通过这个例子,我终于证实了这个结论。

ShellCode 如下

BYTE pCode[22] = {

0x60, //pushad

0x9C, //pushfd

0x6A, bTime, //Sleep参数

0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, //call dword ptr [psc] 暂时留空,在下面会对这里写入

0x9D, //popfd

0x61, //popad 我断了下Sleep看过,其实被改变的通用寄存器好像只有eax ecx edx这几个,但为了代码更简单点,所以直接pushad pushfd popad popfd

0x8B, 0xFF, //mov edi, edi 实际上这个代码是可以不写的

0x55, // push ebp

0x8B, 0xEC, //mov ebp, esp

0xE9, 0x00, 0x00, 0x00, 0x00, //jmp 回 Present + 5 留到下面去写入跳转相对地址

};

*((PDWORD)(pCode + 6)) = (DWORD)psc; //把Sleep的包装地址写入进去,由于call dword ptr 不是用的相对地址,所以直接写入地址进去即可

pCode是在局部分配来写ShellCode的,

所以需要分配一个能够保留的地址,拷贝一份代码过去。

PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);

memcpy(pShellCode, pCode, 22);

PDWORD pAddr = (PDWORD)(pShellCode + 18);//得到最后4字节的指针

*pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);//计算相对地址并写入

//这里普及下常识, 0xE8 call 0xE9 jmp 这种远跳 调用的代码

//它的值为相对地址,这个地址的计算公式为: 目标地址 - (指令地址 + 指令长度)

// (DWORD)pPresent - ((DWORD)pAddr - 1); 根据公式的话,这里看起来很诡异吧

//首先,我们要在这里跳回Present + 5 那么目标就是 (Present + 5 ) 然后,我们跳转的指令地址是在这里 0xE9, 0x00, 0x00, 0x00, 0x00, 从0xE9开始

//但是(PDWORD)(pShellCode + 18);//这里取得最后的4字节的地址时,已经到0xE9后面的一个字节去了, 所以:跳转地址实际上为(pShellCode - 1 + 5)

根据公式: 就应该是这样 (Present + 5 ) - (pShellCode - 1 + 5) 即: (DWORD)pPresent - ((DWORD)pAddr - 1);

一切准备完毕之后,用同样的方法,让原始的Present头部跳到pShellCode头部,这里需要VirtualProtect 改变一下要操作的那几个字节为可读写可执行,操作完后改回去

DWORD dwProtect = 0;

VirtualProtect(pPresent, 5, 0x40, &dwProtect);

*(PBYTE)pPresent = 0xE9;

*((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;

VirtualProtect(pPresent, 5, dwProtect, NULL);

完整的代码如下:

[cpp] view
plaincopy

LPVOID GetDirectDeviceMemberProc(LPVOID pDev, DWORD dwOffset)

{

LPVOID pRet = NULL;

__asm{

mov eax, dword ptr [pDev]

mov ecx, dword ptr [eax]

mov ebx, dwOffset

mov eax, dword ptr [ecx + ebx]

mov pRet, eax

}

return pRet;

}

struct _d3dhookdev_type{

LPVOID pD3D9;

LPVOID pD3DDev;

};

bool InitD3D9(HWND hWnd, _d3dhookdev_type &dev)

{

HMODULE hD3d9 = LoadLibraryA("d3d9.dll");

LPVOID pD3D9 = NULL, pD3D9Dev = NULL;

BYTE d3ddm[0x10];

if(hD3d9)

{

LPVOID _Direct3DCreate9 = (LPVOID)GetProcAddress(hD3d9, "Direct3DCreate9");

__asm{

push 32

call _Direct3DCreate9

mov pD3D9, eax

}

if(!pD3D9)

return false;

LPVOID d3d9_Release = GetDirectDeviceMemberProc(pD3D9, 0x08);

LPVOID _GetAdapterDisplayMode = GetDirectDeviceMemberProc(pD3D9, 0x20);

HRESULT hr = NULL;

__asm{

lea eax, d3ddm

push eax

push 0

push pD3D9

call _GetAdapterDisplayMode

mov hr, eax

}

if(FAILED(hr))

{

__asm{

push pD3D9

call d3d9_Release

}

return false;

}

BYTE d3dpp[0x38];

memset(d3dpp, 0, 0x38);

__asm{

lea eax, d3dpp

mov dword ptr [eax + 0x20], 1

mov dword ptr [eax + 0x18], 1

lea ebx, d3ddm

mov ecx, dword ptr [ebx + 0x0C]

mov dword ptr [eax + 8], ecx

}

LPVOID _CreateDevice = GetDirectDeviceMemberProc(pD3D9, 0x40);

//0x40

__asm{

lea eax, pD3D9Dev

push eax

lea eax, d3dpp

push eax

push 0x20

push hWnd

push 1

push 0

push pD3D9

call _CreateDevice

mov hr, eax

}

printf("%X\n", hr);

if(FAILED(hr))

{

__asm{

push pD3D9

call d3d9_Release

}

return false;

}

dev.pD3D9 = pD3D9;

dev.pD3DDev = pD3D9Dev;

return true;

}

return false;

}

void ReleaseD3D9(_d3dhookdev_type &dev)

{

if(dev.pD3D9)

{

LPVOID d3d_Release = GetDirectDeviceMemberProc(dev.pD3D9, 0x08);

__asm{

push dev.pD3D9

call d3d_Release

}

}

if(dev.pD3DDev)

{

LPVOID d3ddev_Release = GetDirectDeviceMemberProc(dev.pD3DDev, 0x08);

__asm{

push dev.pD3DDev

call d3ddev_Release

}

}

}

void D3DPresentShellCodeHook(HWND hWnd, BYTE bTime)

{

_d3dhookdev_type dev = {NULL, NULL};

LPVOID pPresent = NULL;

if(InitD3D9(hWnd, dev))

{

pPresent = GetDirectDeviceMemberProc(dev.pD3DDev, 0x44);

ReleaseD3D9(dev);

if(!pPresent)

return;

if(0xE9 == *(PBYTE)pPresent)

return;

}

PBYTE psc = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 4, MEM_COMMIT | MEM_RESERVE, 0x40);

*(PDWORD)psc = (DWORD)Sleep;

BYTE pCode[22] = {

0x60,

0x9C,

0x6A, bTime,

0xFF, 0x15, 0x00, 0x00, 0x00, 0x00,

0x9D,

0x61,

0x8B, 0xFF,

0x55,

0x8B, 0xEC,

0xE9, 0x00, 0x00, 0x00, 0x00,

};

*((PDWORD)(pCode + 6)) = (DWORD)psc;

PBYTE pShellCode = (PBYTE)VirtualAllocEx(GetCurrentProcess(), NULL, 22, MEM_COMMIT | MEM_RESERVE, 0x40);

memcpy(pShellCode, pCode, 22);

PDWORD pAddr = (PDWORD)(pShellCode + 18);

*pAddr = (DWORD)pPresent - ((DWORD)pAddr - 1);

DWORD dwProtect = 0;

VirtualProtect(pPresent, 5, 0x40, &dwProtect);

*(PBYTE)pPresent = 0xE9;

*((PDWORD)((PBYTE)pPresent + 1)) = (DWORD)pShellCode - (DWORD)pPresent - 5;

VirtualProtect(pPresent, 5, dwProtect, NULL);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: