魔兽中的dx写屏(原版为英文)

来源:互联网 发布:桩基低应变检测数据 编辑:程序博客网 时间:2024/05/01 01:22
原文地址:
http://www.rohitab.com/discuss/topic/34411-run-time-directx-hooking-using-code-injection-and-vtable/
相关资料:
http://bbs.pediy.com/showthread.php?t=85368&highlight=hook+%E6%B8%B8%E6%88%8F+%E6%88%8F
工程代码:
http://download.csdn.net/detail/langyanduan/4126849
win7x64配置Detours:
http://hi.baidu.com/flicker317/item/97357c9ff5e64ccd7b7f0174

以前对这方面捣鼓了一段时间,后来看到这篇文章,索性就按着它的步骤再进行一次魔兽的写屏,同时加上了一些自己的理解,和原文稍有出入,重新记录下过程吧。


环境:    Win7 x64
                VS 2010
                DirectX 9.0 SDK
                Detour 2.1
工具:    OD
                Dbgview


我们想要在魔兽中绘制文字和各种图形对象,要获得一个类型为LPDIRECT3DDEVICE8的设备对象指针.所以我们通过hook掉Direct3DCreate8以获得类型为LPDIRECT3D8的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D8::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D8::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE8的设备对象指针,然后就可以随意绘制文字或者图形了.


下面就开始进入正题

一、

1. 首先,我们先新建一个空的Win32 App工程Loader,添加以下3个文件:

     inject.h

     inject.cpp

     main.cpp // 通过CreateProcess启动war3并注入我们的dll

////////////////////////////////////////////////////////////////////////////////////////////// inject.h#ifndef __INJECT_H__#define __INJECT_H__#include <windows.h>#include <iostream>HMODULE InjectDLL(DWORD ProcessID, char* dllName);#endif

////////////////////////////////////////////////////////////////////////////////////////////// inject.cpp#include "inject.h"// 这个函数大家应该见多了HMODULE InjectDLL(DWORD ProcessID, char* dllName){HANDLE Proc;HANDLE Thread;char buf[50]={0};LPVOID RemoteString, LoadLibAddy;HMODULE hModule = NULL;DWORD dwOut;if(!ProcessID)return false;Proc = OpenProcess(PROCESS_ALL_ACCESS, 0, ProcessID);if(!Proc){sprintf_s(buf, "OpenProcess() failed: %d", GetLastError());MessageBoxA(NULL, buf, "Loader", NULL);return false;}LoadLibAddy = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");if (!LoadLibAddy) {return false;}RemoteString = (LPVOID)VirtualAllocEx(Proc, NULL, strlen(dllName), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);if (!RemoteString) {return false;}if (!WriteProcessMemory(Proc, (LPVOID)RemoteString, dllName, strlen(dllName), NULL)) {return false;}Thread = CreateRemoteThread(Proc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddy, (LPVOID)RemoteString, NULL, NULL);   if (!Thread) {return false;} else {while(GetExitCodeThread(Thread, &dwOut)) {if(dwOut != STILL_ACTIVE) {hModule = (HMODULE)dwOut;break;}}}CloseHandle(Thread);CloseHandle(Proc);return hModule;}

////////////////////////////////////////////////////////////////////////////////////////////// main.cpp#include "inject.h"const char* EXE_NAME = "war3.exe -window";// 这里我们用窗口模式启动魔兽const char* DLL_NAME = "dll.dll";// 要注入的dll名int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){char path[MAX_PATH];char exename[MAX_PATH];char dllname[MAX_PATH];// 获取本程序的完整路径名称,注意这里我们的exe文件要放在魔兽目录下GetModuleFileNameA(0, path, MAX_PATH);// 找到路径中的最后一个\符号,并删除之后的字符// (如 D:\Game\魔兽\loader.exe 变成 D:\Game\魔兽\)int pos = 0;for (int k = 0; k < strlen(path); k++) {if (path[k] == '\\') {pos = k;}}path[pos+1] = 0; // null-terminate it for strcat// 创建war3路径strcpy_s(exename, path);strcat_s(exename, EXE_NAME);// 创建dll路径strcpy_s(dllname, path);strcat_s(dllname, DLL_NAME);// 启动war3:STARTUPINFOA siStartupInfo;PROCESS_INFORMATION piProcessInfo;memset(&siStartupInfo, 0, sizeof(siStartupInfo));memset(&piProcessInfo, 0, sizeof(piProcessInfo));siStartupInfo.cb = sizeof(siStartupInfo);if (!CreateProcessA(NULL,exename,0,0,false,CREATE_SUSPENDED,0,0,&siStartupInfo,&piProcessInfo)) {MessageBoxA(NULL, exename, "Error", MB_OK); }// 获得war3的pidDWORD pId = piProcessInfo.dwProcessId;// 注入dllif (!InjectDLL(pId, dllname)) {MessageBoxA(NULL, "Injection failed", "Error", MB_OK);}ResumeThread(piProcessInfo.hThread);return 0;}
这样我们的装载器就完成了。

2. 接着,我们添加一个空的Win32 dll工程,工程名就要dll吧,包括以下文件:

     main.cpp


先添加dll的入口函数

#include <windows.h>#include <d3dx8.h>#include <detours.h>#pragma comment(lib, "detours.lib")BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH: {DisableThreadLibraryCalls(hModule);// 我们将要在这里进行hook}}return TRUE;}

我们知道,魔兽使用 LoadLibraryA 访问 DirectX 的。这意味着,我们不能直接 hook 住Direct3DCreate8因为
a)d3d8.dll 尚未加载
b)我们不知道它的地址


所以我们hook 住 LoadLibraryA 来获取  Direct3DCreate8 的地址,首先定义LoadLibraryA的形式,并声明一个变量用来保存LoadLibrayA 的原始地址

typedef HMODULE (WINAPI *LoadLibrary_t)(LPCSTR);LoadLibrary_t orig_LoadLibrary = LoadLibraryA;// 保存 LoadLibraryA 的原始地址


然后在当 d3d8.dll 真正被加载时,我们在重写的 LoadLibraryA 方法中得到 Direct3DCreate8 的地址并 detour 它。
在测试过程中,可以发现游戏对 DLL 加载了几次。所以如果你在别的游戏中,并不能采用这里的加载次数。


// 我们重写的 LoadLibraryA 方法HMODULE WINAPI LoadLibrary_Hook ( LPCSTR lpFileName ) {static int hooked = 0;HMODULE hM = orig_LoadLibrary( lpFileName );if ( strcmp( lpFileName, "d3d8.dll" ) == 0) {hooked++;if (hooked == 3) {// 得到 Direct3DCreate8 的地址,然后就可以做我们想做的事情了。。。。pDirect3DCreate8 = (PBYTE)GetProcAddress(hM, "Direct3DCreate8");// 我们对 Direct3DCreate8 进行 hook HookAPI();}}return hM;}


// 向前声明IDirect3D8* __stdcall hook_Direct3DCreate8(UINT sdkVers);void HookAPI();typedef IDirect3D8* (__stdcall *Direct3DCreate8_t)(UINT SDKVersion);Direct3DCreate8_t orig_Direct3DCreate8;// 用来保存原始地址// Direct3DCreate8 的地址,在我们重写的 LoadLibraryA 方法中赋值,有了他我们就能对 Direct3DCreate8 进行 hook了PBYTE pDirect3DCreate8;

我们就用微软的 Detour 库来 hook Direct3DCreate8

void HookAPI(){// simple detourorig_Direct3DCreate8 = (Direct3DCreate8_t)pDirect3DCreate8;DetourRestoreAfterWith();DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());DetourAttach(&(PVOID&)orig_Direct3DCreate8, hook_Direct3DCreate8);DetourTransactionCommit();}


钩住了 Direct3DCreate8 后我们可以通过虚函数表获得 CreateDevice 的地址了,有关虚函数表不知道的可以去问google。

// 重写 Direct3DCreate8 方法IDirect3D8* __stdcall hook_Direct3DCreate8(UINT sdkVers){IDirect3D8* pD3d8 = orig_Direct3DCreate8(sdkVers); // real one// 通过虚函数表得到 CreateDevice 的地址,CreateDevice 是虚表中的第15个函数DWORD* pVtable = GetVtableAddress(pD3d8);HookFunction(pVtable, (void*)&hook_CreateDevice, (void*)&orig_CreateDevice, 15);return pD3d8;}

向前声明

// CreateDevicetypedef HRESULT (APIENTRY *CreateDevice_t)(IDirect3D8*,UINT,D3DDEVTYPE,HWND,DWORD,D3DPRESENT_PARAMETERS*,IDirect3DDevice8**);CreateDevice_t orig_CreateDevice;HRESULT APIENTRY hook_CreateDevice(IDirect3D8* pInterface,UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface);

这里我们可以获得 LPDIRECT3DDEVICE8 的设备对象指针,并把他们输出,等下我们要用到。

HRESULT APIENTRY hook_CreateDevice(IDirect3D8* pInterface,UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface){HRESULT ret = orig_CreateDevice(pInterface, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);// Registers MUST be preserved when doing your own stuff!!__asm pushad// get a pointer to the created device IDirect3DDevice8* d3ddev = *ppReturnedDeviceInterface;// lets log it (format in hex mode to make it easier to work with)char buf[50] = {0};wsprintfA(buf, "pD3ddev: %X", d3ddev);OutputDebugStringA(buf);__asm popadreturn ret;}


上面用到的2个函数

DWORD* GetVtableAddress(void* pObject){// The first 4 bytes of the object is a pointer to the vtable:return (DWORD*)*((DWORD*)pObject);}// 通过替换虚函数表中的函数地址实现 hook CreateDevicevoid HookFunction(DWORD* pVtable, void* pHookProc, void* pOldProc, int iIndex){// Enable writing to the vtable at address we aquiredDWORD lpflOldProtect;VirtualProtect((void*)&pVtable[iIndex], sizeof(DWORD), PAGE_READWRITE, &lpflOldProtect);// Store old addressif (pOldProc) {*(DWORD*)pOldProc = pVtable[iIndex];}// Overwrite original addresspVtable[iIndex] = (DWORD)pHookProc;// Restore protectionVirtualProtect(pVtable, sizeof(DWORD), lpflOldProtect, &lpflOldProtect);}


二、

这样我们就得到了一个 IDirect3DDevice8 指针,然后就可以进行魔兽写屏了。 不过这样的话我们每次必须在游戏初始化D3d之前完成hook,不是很方便。

我们希望能够有一种方法可以不用 hook,就能随时获得设备指针。那我们就继续完成这个任务吧。


我们打开DebugView,运行我们的Loader,得到设备指针。(比如我的:[3296] pD3ddev: 3317FC0)

然后用 od 附加魔兽。

1)我们先 alt + e,查看 d3d8.dll 的模块基址,我这是 54D20000

2)在Command 栏中输入 hr ????????  (下硬件断点,????????是你刚刚获得的设备指针,比如我输入的就是 hr 3317FC0)。

od 就断下了,看看是否是在 d3d8 模块内被断下的,不是的话F9运行,直到是在 d3d8 模块内被断下。

3)断下后记住地址(程序断下后的偏移地址可能会不大一样,以为有很多地方都访问了这个指针,随便找个地址就可以了,也可以想原文中那样用CE扫描)

就用原文中的地址吧,F9多跑几次断在了原文中的地址。

54D96E29  |> \C746 18 01000>mov dword ptr ds:[esi+0x18],0x154D96E30  |.  8B76 0C       mov esi,dword ptr ds:[esi+0xC]54D96E33      8B06          mov eax,dword ptr ds:[esi]54D96E35      8B48 08       mov ecx,dword ptr ds:[eax+0x8];断在了这里,说明是上一句访问了设备指针,那么esi中的值就是设备指针54D96E38  |.  56            push esi


然后我们Ctrl + End 来到模块末尾,找一段空的区域,我们将在这获取并保存设备指针。

54E13FC3      00            db 0054E13FC4      00            db 0054E13FC5      00            db 0054E13FC6      00            db 0054E13FC7      00            db 0054E13FC8      00            db 0054E13FC9      00            db 0054E13FCA      00            db 0054E13FCB      00            db 0054E13FCC      00            db 0054E13FCD      00            db 0054E13FCE      00            db 0054E13FCF      00            db 0054E13FD0      00            db 0054E13FD1      00            db 0054E13FD2      00            db 0054E13FD3      00            db 0054E13FD4      00            db 0054E13FD5      00            db 0054E13FD6      00            db 00

我们就跳到 54E13FC7 这个位置来获取设备指针,然后把值存到 54E13FC3 中。

4)alt + g 回到 54D96E33 处,双击,输入 jmp 54E13FC7

54D96E29  |> \C746 18 01000>mov dword ptr ds:[esi+0x18],0x154D96E30  |.  8B76 0C       mov esi,dword ptr ds:[esi+0xC]54D96E33      E9 8FD10700   jmp d3d8.54E13FC754D96E38  |.  56            push esi

alt + g 转到 54E13FC7,写入原指令,在将 esi 存入 54D96E33,然后在跳到 54D96E38。

54E13FC3      00            db 0054E13FC4      00            db 0054E13FC5      00            db 0054E13FC6      00            db 0054E13FC7      8B06          mov eax,dword ptr ds:[esi]54E13FC9      8B48 08       mov ecx,dword ptr ds:[eax+0x8]54E13FCC      8935 C33FE154 mov dword ptr ds:[0x54E13FC3],esi54E13FD2    ^ E9 612EF8FF   jmp d3d8.54D96E38

现在我们运行的话程序就会退出,以为 54E13FC3 的属性是不可写的。


这里的54D96E33、54E13FC3、54E13FC7会随每次载入时 d3d8基址的变化而变化,所有我们要进行重定位。

RVA:

76E33 = 54D96E33 - 54D20000

F3FC3 = 54E13FC3 - 54D20000

F3FC7 = 54E13FC7 - 54D20000

之后只要获得 d3d8 的基址,加上偏移量就行了。


注:上面这种方法是让魔兽跳转到模块末尾执行,我们也可以让魔兽跳到我们的模块函数内执行,保存好设备指针后在跳回去,函数可以这样写:

DWORD g_d3dPoint = 0;__declspec(naked) void GetValue(){__asm{mov eax, dword ptr ds:[esi];mov ecx, dword ptr ds:[eax + 0x8];mov g_d3dPoint, esi;jmp addr_back;}}

4)新建一个空的Win32 Dll 工程,包含以下文件:
     main.cpp

dll 入口函数

#include <Windows.h>#include <d3d8.h>#include <d3dx8.h>// 向前声明DWORD*pVtable;// EndScene (offset : 35)BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE8 pDxdevice,TCHAR* strText ,int nbuf);typedef HRESULT (APIENTRY *EndScene_t)(IDirect3DDevice8* );HRESULT APIENTRY hook_EndScene(IDirect3DDevice8* pInterface);EndScene_t orig_EndScene;DWORD WINAPI Patch_StealD3d8Device(LPVOID param);DWORD WINAPI HookAPI(LPVOID param);DWORD* GetVtableAddress(void* pObject);void HookFunction(DWORD* pVtable, void* pHookProc, void* pOldProc, int iIndex);// hooks.hBOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH:{DisableThreadLibraryCalls(hModule);HANDLE hpThread;hpThread = ::CreateThread(NULL, 0, Patch_StealD3d8Device, NULL, 0, NULL);// 这个线程用来让d3d8.dll执行我们代码,获得设备指针CloseHandle(hpThread);}break;case DLL_PROCESS_DETACH:{DWORD hook_addr;HookFunction(pVtable, (void *)orig_EndScene, (void*)&hook_addr, 35);// 退出时还原虚函数表}break;}return TRUE;}


patch d3d8.dll,让我们的代码执行

DWORD WINAPI Patch_StealD3d8Device(LPVOID param){int base_d3d8 = (int)GetModuleHandle(TEXT("d3d8"));// 重定位const int addr_jmp =   base_d3d8 + 0x00076E33;const int addr_cave =  base_d3d8 + 0x000F3FD0;const int addr_value = base_d3d8 + 0x000F3FC7;// 我们保存的2进制码byte jmp[] =  "\xE9\x98\xD1\x07"; // 字符串是以 0x00 终止的byte cave[] = "\x8B\x06\x8B\x48\x08\x89\x35\xC5\x3F\x0B\x6D\xE9\x58\x2E\xF8\xFF"; // 0终止,之后的是数据 0,所以是可行的// 更改内存的读写属性,让我们能够写入DWORD lpflOldProtect;// 写入跳转指令,让程序跳到模块末尾执行我们的代码VirtualProtect((void*)addr_jmp, sizeof(jmp), PAGE_EXECUTE_READWRITE, &lpflOldProtect);memcpy((void*)addr_jmp, (void*)jmp, sizeof(jmp));VirtualProtect((void*)addr_jmp, sizeof(jmp), lpflOldProtect, &lpflOldProtect);// 在模块末尾写入我们的代码VirtualProtect((void*)addr_cave, sizeof(cave), PAGE_EXECUTE_READWRITE, &lpflOldProtect);memcpy((void*)addr_cave, (void*)cave, sizeof(cave));// 这里我们要对保存设备指针的地址进行重定位*(int*)(addr_cave + 7) = addr_value;VirtualProtect((void*)addr_cave, sizeof(cave), lpflOldProtect, &lpflOldProtect);// 让保存设备指针的地址可写VirtualProtect((void*)addr_value, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &lpflOldProtect);// 等待设备指针被保存到指定的地址HANDLE hThread = CreateThread(0, 0, HookAPI, 0, 0, 0);WaitForSingleObject(hThread, INFINITE);CloseHandle(hThread);// 还原byte orig[] = {0x8B, 0x06, 0x8B, 0x48, 0x08};VirtualProtect((void*)addr_jmp, sizeof(orig), PAGE_EXECUTE_READWRITE, &lpflOldProtect);memcpy((void*)addr_jmp, (void*)orig, sizeof(orig));VirtualProtect((void*)addr_jmp, sizeof(orig), lpflOldProtect, &lpflOldProtect);return 0;}

堵塞线程直到设备指针被保存到指定位置。

DWORD WINAPI HookAPI(LPVOID param){// Aquire base address of d3d8.dllint base_d3d8 = (int)GetModuleHandle(TEXT("d3d8"));const int addr_value = base_d3d8 + 0x000F3FC7;Sleep(100); // wait for address to get written// protect value addr for reading / writingDWORD lpflOldProtect;VirtualProtect((void*)addr_value, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &lpflOldProtect);// poll the value until it gets written by our caveDWORD result = 0;while (!result) {result = *(DWORD*)addr_value;Sleep(10);}// find the vtablepVtable = GetVtableAddress((void*)result);// 这里我们替换虚函数表中的 EndScene 函数指针,执行我们重写的 EndScene 进行写屏HookFunction(pVtable, (void*)&hook_EndScene, (void*)&orig_EndScene, 35);//HookFunction(pVtable, (void*)&hook_DrawIndexedPrimitive, (void*)&orig_DrawIndexedPrimitive, 71);//HookFunction(pVtable, (void*)&hook_Present, (void*)&orig_Present, 15);//HookFunction(pVtable, (void*)&hook_SetStreamSource, (void*)&orig_SetStreamSource, 83);return 0;}

下面的没就什么了,直接上代码吧

DWORD* GetVtableAddress(void* pObject){// The first 4 bytes of the object is a pointer to the vtable:return (DWORD*)*((DWORD*)pObject);}void HookFunction(DWORD* pVtable, void* pHookProc, void* pOldProc, int iIndex){// Enable writing to the vtable at address we aquiredDWORD lpflOldProtect;VirtualProtect((void*)&pVtable[iIndex], sizeof(DWORD), PAGE_READWRITE, &lpflOldProtect);// Store old addressif (pOldProc) {*(DWORD*)pOldProc = pVtable[iIndex];}// Overwrite original addresspVtable[iIndex] = (DWORD)pHookProc;// Restore protectionVirtualProtect(pVtable, sizeof(DWORD), lpflOldProtect, &lpflOldProtect);}//EndsceneHRESULT APIENTRY hook_EndScene(IDirect3DDevice8* pInterface){ __asm pushadDrawText((LPDIRECT3DDEVICE8)pInterface, TEXT("by: 狼烟断  2012-03-09"), 20); __asm popadreturn orig_EndScene(pInterface);}BOOL _stdcall DrawText(LPDIRECT3DDEVICE8 pDxdevice,TCHAR* strText ,int nbuf){if (pDxdevice) {RECT myrect;myrect.top= 10;myrect.left= 50;myrect.right= 1000 + 50; myrect.bottom = 100 + 10;HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);LOGFONT lf = {0};GetObject(hFont, sizeof(lf), &lf);DeleteObject(hFont);hFont = NULL;ID3DXFont* g_font = NULL;if(D3D_OK != D3DXCreateFontIndirect(pDxdevice, &lf, &g_font))return FALSE;g_font->DrawTextW(strText,nbuf, &myrect, DT_TOP | DT_LEFT,0xFFFFFF00); g_font->Release();}return true;}


效果图: