魔兽中的dx写屏(原版为英文)
来源:互联网 发布:桩基低应变检测数据 编辑:程序博客网 时间:2024/05/01 01:22
相关资料:
工程代码:
以前对这方面捣鼓了一段时间,后来看到这篇文章,索性就按着它的步骤再进行一次魔兽的写屏,同时加上了一些自己的理解,和原文稍有出入,重新记录下过程吧。
环境: 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;}
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;}
效果图:
- 魔兽中的dx写屏(原版为英文)
- 英雄联盟 原汁原味 (国服改成原版英文字幕+英文语音)
- 把窗口化的DX魔兽变为全屏再画图
- 英文版XP下玩魔兽
- 魔兽中各族单位名称英文缩写
- java 判断字符串全为英文 提取字符串中的英文
- 为女儿认识英文写的一款Flash游戏(简易打字)
- iOS改变系统中的英文为汉语
- 二个英文物理题目[USA原版]
- DX中的颜色表示
- dx中的BeginScene/EndScene
- DX 中的坐标变换
- 魔兽宽屏修改
- 魔兽奥运会(超强)
- 魔兽
- 自己写了个魔兽显血改键工具
- C语言写魔兽显血条工具
- 写魔兽改键时遇到的问题
- Win32多线程同步方式
- Android编译脚本之telechips方案
- 个人笔记
- ubuntu10.10安装google拼音输入法
- 【shell】case 语句
- 魔兽中的dx写屏(原版为英文)
- 周末总结_杂谈
- java和oracle日期互转
- php开发规范
- PhoneGap和Sencha Touch2
- java初学者都必须理解的六大问题
- MongoDB的安装使用以及安装php扩展
- 用eclipse的fat-jar插件生成jar包
- 记一次版本并发开发过程