FPS 游戲實現D3D透視 (API Hook)


FPS游戲可以說一直都比較熱門,典型的代表有反恐精英,穿越火線,絕地求生等,基本上只要是FPS游戲都會有透視掛的存在,而透視掛還分為很多種類型,常見的有D3D透視,方框透視,還有一些比較高端的顯卡透視,本教程將學習D3D透視的實現原理,並通過DLL注入的方式實現透視。

反恐精英下載地址:鏈接:https://pan.baidu.com/s/1U4-E9-xNIoHOyLg5aP_l7w 提取碼:yupq
DX9 SDK精簡版:鏈接:https://pan.baidu.com/s/1SUufWoizbpZL1ki85J1zbA 提取碼:u1ak

Direct3D 透視是一種主流的透視方式,因為現如今大部分游戲都會使用Dx9圖形接口,那么我們該如何實現D3D透視?

在D3D中普遍會使用深度緩存區(Depth Buffer)來進行消隱處理,通過使用Z軸深度緩存即可實現將人物被遮擋的部分不被顯示出來,而我們的目的就是要讓它強制顯示出來,D3D的核心功能主要集成在COM組件中,只要Hook其中EndScence(), DrawPrimitive(),DrawIndexedPrimitive()函數就可以感知游戲的繪圖操作,然后通過調用SetRenderState()渲染函數,改變其中的渲染參數即可實現不同的透視效果。

為了確保能夠正常的編譯代碼,請自行配置好 Direct3D 9 SDK 和 VS 系列開發環境,過程中使用了 x64dbg,DBGview工具,我這里還是使用CS起源作為演示對象吧,電腦上沒別的游戲。

SetWindowHookEx 全局注入

SetWindowHookEx 函數可以將一個Dll強行插入到系統的每個進程里,因為是全局注入,所以該方法可注入到具有保護的游戲中,首先我們需要創建一個Dll工程 hook.cpp 然后將SetHook方法導出,在DllMain中進行了判斷,如果窗口句柄為valve001則彈出一個消息框,其他進程直接跳過,即可實現指定進程注入。

#include <windows.h>
HHOOK global_hook;

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
	global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}
extern "C" __declspec(dllexport) void UnHook()
{
	if (global_hook) UnhookWindowsHookEx(global_hook);
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
	HWND hwnd = FindWindowW(L"valve001",NULL);
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	if (GetCurrentProcessId() == pid)
	{
		MessageBox(hwnd, TEXT("inject"), 0, 0);
	}
	return true;
}

調用代碼如下,注意必須將上方編譯好的hook.dll與下方工程放到同一個目錄下,通過LoadLibrary函數獲取到模塊句柄,然后通過GetProcAddress獲取到導出函數地址,並通過函數指針調用。

#include <windows.h>

int main()
{
	HMODULE hMod = LoadLibrary(TEXT("hook.dll"));
	typedef void(*pSetHook)(void);
	pSetHook SetHook = (pSetHook)GetProcAddress(hMod, "SetHook");
	SetHook();
	while (1)
	{
		Sleep(1000);
	}
	return 0;
}

計算 DrawIndexedPrimitive 偏移

我們需要找到 DrawIndexedPrimitive 這個渲染函數並 Hook 這個函數,但 DrawIndexedPrimitive 函數與其他普通API函數不同,由於 DirectX 的功能都是以COM組件的形式提供的類函數,所以普通的Hook無法搞它,我這里的思路是,自己編寫一個D3D繪圖案例,在源碼中找到 DrawIndexedPrimitive 函數並設置好斷點,通過VS調試單步執行找到函數的所在模塊的地址,並與d3d9.dll的基址相減得到相對偏移地址。

#include <windows.h> 
#include<tchar.h> 
#include<d3d9.h>
#pragma comment( lib, "d3d9.lib") 

#define null NULL
#define RETURN return
 
LPDIRECT3D9             g_pD3D = NULL;  
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL;  
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;  
 
    struct CUSTOMVERTEX 
    {
         
        float x, y, z, rhw;  
        DWORD color;  
    };  
#define FVF ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE ) 
     
    HRESULT InitD3D(HWND hWnd) 
        { 
        g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);  
        D3DPRESENT_PARAMETERS   d3dpp;  
        ZeroMemory(&d3dpp, sizeof(d3dpp));  
        d3dpp.Windowed = TRUE;  
        d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;  
        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;  
        g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING,&d3dpp, &g_pd3dDevice);  
        return S_OK;  
        } 
         
        HRESULT InitVB() 
        { 
        CUSTOMVERTEX v[] =  
        { 
        100, 000, 0, 1, 0xffff0000,  
        300, 50, 0, 1, 0xff00ff00,  
        500, 400, 0, 1, 0xff0000ff 
        };  
         
        g_pd3dDevice->CreateVertexBuffer(3 * sizeof(v), 0, FVF, D3DPOOL_DEFAULT, &g_pVB, 0);  
         
        void* vb;  
        g_pVB->Lock(0, 0, (void**)&vb, 0);  
        memcpy(vb, v, sizeof(v));  
        g_pVB->Unlock();  
        return S_OK;  
        } 
        void Render() 
        { 
        g_pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 255, 0), 1, 0);  
         
        g_pd3dDevice->BeginScene();  
        g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));  
        g_pd3dDevice->SetFVF(FVF);  
        //g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 10);
        g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
        g_pd3dDevice->EndScene();  
         
        g_pd3dDevice->Present(0, 0, 0, 0);  
        } 
         
        LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
        { 
        message == WM_CLOSE ? PostQuitMessage(0) : 0;  
        return DefWindowProc(hWnd, message, wParam, lParam);  
        } 
        int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PTSTR, int) 
        { 
        wchar_t cn[] = L"ClassName";  
        WNDCLASS wc;  
        wc.style = CS_HREDRAW | CS_VREDRAW;  
        wc.lpfnWndProc = WndProc;  
        wc.cbClsExtra = 0;  
        wc.cbWndExtra = 0;  
        wc.hInstance = hInstance;  
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);  
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);  
        wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);  
        wc.lpszMenuName = NULL;  
        wc.lpszClassName = cn;
        RegisterClass(&wc);  
         
        HWND hWnd = CreateWindow(cn, 0, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL);  
        ShowWindow(hWnd, SW_SHOW);  
         
        InitD3D(hWnd);  
        InitVB();  
        MSG msg;  
        ZeroMemory(&msg, sizeof(msg));  
            while (msg.message != WM_QUIT) 
            { 
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
                { 
                TranslateMessage(&msg);  
                DispatchMessage(&msg);  
                } 
                else 
                { 
                Render();  
                } 
            } 
            return 0;  
        }

首先我們直接在VS中運行自己的工程(這樣的例子有很多),然后在源代碼中找到 DrawIndexedPrimitive並下一個【F9】斷點,然后直接運行程序,發現程序斷下后直接按下【Alt + 8】切到反匯編窗口。

函數調用:g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
00D01853 8B F4                mov         esi,esp  
00D01855 6A 04                push        4  
00D01857 6A 00                push        0  
00D01859 6A 04                push        4  
00D0185B 6A 00                push        0  
00D0185D 6A 00                push        0  
00D0185F 6A 04                push        4  
00D01861 A1 44 91 D0 00       mov         eax,dword ptr ds:[00D09144h]  
00D01866 8B 08                mov         ecx,dword ptr [eax]  
00D01868 8B 15 44 91 D0 00    mov         edx,dword ptr ds:[0D09144h]  
00D0186E 52                   push        edx  
00D0186F 8B 81 48 01 00 00    mov         eax,dword ptr [ecx+148h]  
00D01875 FF D0                call        eax  
00D01877 3B F4                cmp         esi,esp  
00D01879 E8 EF F8 FF FF       call        __RTC_CheckEsp (0D0116Dh) 

上方的代碼就是你在VS中看到的代碼片段,該代碼片段就是調用 DrawIndexedPrimitive 函數的初始化工作,可以明顯的看出壓棧了6條數據,最后調用了 call eax 我們直接在單步【F9】走到00D01875地址處並按下【F11】進入到CALL的內部,可看到以下代碼片段,我們需要記下片段中的 6185CD20 這個地址。

6185CD20 8B FF                mov         edi,edi  
6185CD22 55                   push        ebp  
6185CD23 8B EC                mov         ebp,esp  
6185CD25 6A FF                push        0FFFFFFFFh  
6185CD27 68 C8 49 87 61       push        618749C8h  
6185CD2C 64 A1 00 00 00 00    mov         eax,dword ptr fs:[00000000h]  
6185CD32 50                   push        eax  
6185CD33 83 EC 20             sub         esp,20h  
6185CD36 53                   push        ebx  
6185CD37 56                   push        esi  
6185CD38 57                   push        edi  
6185CD39 A1 70 62 95 61       mov         eax,dword ptr ds:[61956270h]  

上方的起始地址 6185CD20 經常會變化,所以我們需要找到當前 d3d9.dll 模塊的基址,通過X64DBG獲取到的基址是61800000通過當前地址減去模塊基址 6185CD20 - 61800000 得到相對偏移地址5CD20,此時我們就可以通過 d3d9.dll + 5CD20 來動態的計算出這個變化的地址,編程實現的代碼片段如下:

#include <windows.h>
HHOOK global_hook;

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
	global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}

ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
	HANDLE handle = GetModuleHandle(TEXT("d3d9.dll"));
	if (handle == INVALID_HANDLE_VALUE) return NULL;
	return(ULONG_PTR)handle + 0x5cd20;
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
	HWND hwnd = FindWindowW(L"Valve001", NULL);
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	if (GetCurrentProcessId() == pid)
	{
		ULONG_PTR temp = GetDrawIndexedPrimitiveAddr();
		MessageBox(hwnd, (LPCWSTR)temp,0,0);
	}
	return true;
}

將這個DLL注入到游戲,即可獲取到模塊基址,此處編碼問題顯示有問題,不過已經可以獲取到了。


劫持 DrawIndexedPrimitive 函數

劫持 DrawIndexedPrimitive 函數就可以感知繪圖操作,其實這里就是 API Hook 首先我們使用 VirtualProtect() 函數將我們需要填充的內存設置為可讀寫可執行權限,接着直接使用 jmp (遠跳轉) 指令替換掉系統領空中的 DrawIndexedPrimitive 函數的前5個字節,然后讓其跳轉到我們的 hook.dll 模塊中的 MyDrawIndexedPrimitive 執行我們自己的繪圖過程,執行完畢以后直接通過 Transfer_DrawIndexedPrimitive 中轉函數跳轉回程序領空中,即可完成 D3D的函數劫持。

我們需要Hook該函數,並跳轉到我們自己的函數中,為了保證調用堆棧的平衡,我們需要確保自己的函數參數應和系統函數參數相等,如下是DrawIndexedPrimitive函數的原型定義。

STDMETHOD(DrawIndexedPrimitive)(
	THIS_ D3DPRIMITIVETYPE,
	INT BaseVertexIndex,
	UINT MinVertexIndex,
	UINT NumVertices,
	UINT startIndex,
	UINT primCount
)

從上方的定義上,可以看出一共傳遞了6個參數,這里需要注意,由於該函數是類函數,在調用時需要傳遞自身指針(pdevice ->DrawIndexedPrimitive()),所以我們還需要加上一個自身指針,完整聲明應該如下:

pdevice =  LPDIRECT3DDEVICE9 pDevice

HRESULT DrawIndexedPrimitive(
	[in] LPDIRECT3DDEVICE9 pDevice,  // 設備指針
	[in]  D3DPRIMITIVETYPE Type,        // 圖元類型
	[in]  INT BaseVertexIndex,               // 起始頂點索引
	[in]  UINT MinIndex,                        // 最小頂點索引
	[in]  UINT NumVertices,                   // 頂點數量
	[in]  UINT StartIndex,                       // 起始索引
	[in]  UINT PrimitiveCount                 // 圖元數量
)

上方我們既然知道了聲明方式,那么我們就可以制作自己的中轉函數Transfer_DrawIndexedPrimitive以及自己的MyDrawIndexedPrimitive函數了,代碼片段如下,需要注意調用約定:

__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, 
	D3DPRIMITIVETYPE type, 
	INT BaseVertexIndex, 
	UINT MinVertexIndex, 
	UINT NumVertices, 
	UINT startIndex, 
	UINT primCount)
{
	__asm{
		mov edi, edi
		push ebp
		mov ebp, esp
		mov eax, jump
		jmp eax
	}
}

HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, 
	D3DPRIMITIVETYPE type, 
	INT BaseVertexIndex, 
	UINT MinVertexIndex, 
	UINT NumVertices, 
	UINT startIndex, 
	UINT primCount)
{
	OutputDebugStringA("執行我自己的函數,中轉函數\r\n");
	return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}

接着公布下Hook函數的代碼,下方我們通過內聯匯編進行了跳轉的鏈接,構成了一個完整的Hook鏈。

bool HookDrawIndexedPrimitive()
{
	ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
	DWORD oldProtect = 0;
	if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect))   // 設置內存保護方式為可讀寫
	{
		DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5;                 // 計算出需要跳轉字節
		jump = address + 5;                                                        // 計算下一個跳轉字節
		__asm
		{
			mov eax, address
			mov byte ptr[eax],0xe9                                                 // 填充為 jmp
			add eax,1                                                              // 指針遞增
			mov ebx,value                                                          // 中轉
			mov dword ptr[eax],ebx                                                 // 賦值跳轉地址(遠跳轉)
		}
		VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect);               // 恢復內存保護方式
	}
	return true;
}

最終完整代碼如下所示:

#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")

HHOOK global_hook;
DWORD jump = 0;

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
	global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}

__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, 
	D3DPRIMITIVETYPE type, 
	INT BaseVertexIndex, 
	UINT MinVertexIndex, 
	UINT NumVertices, 
	UINT startIndex, 
	UINT primCount)
{// 中轉函數,執行被我們填充后的指令片段,並跳轉到原始指令的后面繼續執行
	__asm{
		mov edi, edi
		push ebp
		mov ebp, esp
		mov eax, jump
		jmp eax
	}
}

HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, 
	D3DPRIMITIVETYPE type, 
	INT BaseVertexIndex, 
	UINT MinVertexIndex, 
	UINT NumVertices, 
	UINT startIndex, 
	UINT primCount)

{// 在此函數中DIY添加功能(例如:繪制菜單)
	OutputDebugStringA("執行我自己的函數,中轉函數\r\n");
	return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}

ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
	HANDLE handle = GetModuleHandle(TEXT("d3d9.dll"));      // 獲得d3d9.dll模塊基址
	if (handle == INVALID_HANDLE_VALUE) return NULL;
	return(ULONG_PTR)handle + 0x5cd20;                      // 相加偏移
}

bool HookDrawIndexedPrimitive()
{
	ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
	DWORD oldProtect = 0;
	if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect))   // 設置內存保護方式為可讀寫
	{
		DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5;                 // 計算出需要跳轉字節
		jump = address + 5;                                                        // 計算下一個跳轉字節
		__asm
		{
			mov eax, address
			mov byte ptr[eax],0xe9                                                 // 填充為 jmp
			add eax,1                                                              // 指針遞增
			mov ebx,value                                                          // 中轉
			mov dword ptr[eax],ebx                                                 // 賦值跳轉地址(遠跳轉)
		}
		VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect);               // 恢復內存保護方式
	}
	return true;
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
		HWND hwnd = FindWindowW(L"valve001", NULL);
		DWORD pid;
		GetWindowThreadProcessId(hwnd, &pid);
		if (GetCurrentProcessId() == pid)
		{
			HookDrawIndexedPrimitive();
		}
	return true;
}

將代碼編譯為 hook.dll 並使用前面提到過的SetWindowHook方法注入游戲,注入后發現已經成功劫持,並且游戲沒有崩潰說明我們的Hook中轉正常,如果出現錯誤多半是代碼沒有銜接完整。

我們通過X64DBG附加游戲進程,可以觀察到模塊已經注入成功了,我們將 d3d9.dll + 5cd20 = 5B50CD20

X64DBG直接跟一下這個地址,觀察我們寫入的情況,發現一個遠指針(遠跳轉)

在 jmp hook.5D391122 地址處繼續跟進,既可以看到我們自己的中轉函數了。


找人物模型ID號

簡單的模型過濾:

HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	HRESULT Result = S_OK;
	IDirect3DVertexBuffer9 *pStreamData = NULL;
	UINT iOffsetInBytes, iStride;
	if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release();  // 得到模型來源
	if (iStride == 200)            // 得到來源為200的時候,才會渲染
	{
		Result = Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
	}
	return Result;
}

添加虛擬鍵位: 創建並添加虛擬鍵位,按下上光標鍵模型序號加2,按下下光標鍵模型序號減2,進入游戲以后按下上光標鍵,觀察游戲的反應,如果人物消失了,就是我們要找的人物ID號。

WNDPROC Global_OldProc = NULL;
DWORD Fvalue = 0;

HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	HRESULT Result = S_FALSE;
	IDirect3DVertexBuffer9 *pStreamData = NULL;
	UINT iOffsetInBytes, iStride;
	if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release();  // 得到模型來源
	if (iStride != Fvalue)            // 當來源不等於Fvalue時,就渲染,否則直接去除
	{
		Result = Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
	}
	return Result;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg == WM_KEYDOWN)
	{
		if (wParam = VK_UP)     // 按下上光標將,我們讓模型編號加2
		{
			Fvalue += 2;
		}
		if (wParam == VK_DOWN)   // 按下下光標鍵,我們讓我們讓模型編號減2
		{
			Fvalue -= 2;
		}
	}
	return CallWindowProc(Global_OldProc, hwnd, uMsg, wParam, lParam);  // 全局熱鍵回調函數
}
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
		HWND hwnd = FindWindowW(L"valve001", NULL);
		DWORD pid;
		GetWindowThreadProcessId(hwnd, &pid);
		if (GetCurrentProcessId() == pid)
		{
			HookDrawIndexedPrimitive();
			Global_OldProc = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)WindowProc);  // 注冊全局熱鍵
		}
	return true;
}

完整版:

#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")

HHOOK global_hook;
DWORD jump = 0;


// 虛擬按鍵代碼片段
WNDPROC Global_OldProc = NULL;
DWORD Fvalue = 5000;

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(global_hook, nCode, wParam, lParam);
}

extern "C" __declspec(dllexport) void SetHook()
{
	global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}

// 中轉函數,執行被我們填充后的指令片段,並跳轉到原始指令的后面繼續執行
__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	__asm{
		mov edi, edi
			push ebp
			mov ebp, esp
			mov eax, jump
			jmp eax
	}
}

// 在此函數中DIY你的項目,這個就是我們的中轉函數,用於繪制透視方框等
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	HRESULT Result = S_FALSE;
	IDirect3DVertexBuffer9 *pStreamData = NULL;
	UINT iOffsetInBytes, iStride;
	if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release();  // 得到模型來源
	if (iStride != Fvalue)            // 當來源不等於Fvalue時,就渲染,否則直接去除
	{
		Result = Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
	}
	return Result;
}

// 通過硬編碼方式獲取到GetDrawIndexedPrimitive函數的基地址
ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
	HANDLE handle = GetModuleHandle(TEXT("d3d9.dll"));      // 獲得d3d9.dll模塊基址
	if (handle == INVALID_HANDLE_VALUE) return NULL;
	return(ULONG_PTR)handle + 0x5CD20;                      // 相加偏移
}

// 開始Hook
bool HookDrawIndexedPrimitive()
{
	ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
	DWORD oldProtect = 0;
	if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect))   // 設置內存保護方式為可讀寫
	{
		DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5;                 // 計算出需要跳轉字節
		jump = address + 5;                                                        // 計算下一個跳轉字節
		__asm
		{
			mov eax, address
			mov byte ptr[eax], 0xe9                                                 // 填充為 jmp
			add eax, 1                                                              // 指針遞增
			mov ebx, value                                                          // 中轉
			mov dword ptr[eax], ebx                                                 // 賦值跳轉地址(遠跳轉)
		}
		VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect);               // 恢復內存保護方式
	}
	return true;
}


LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg == WM_KEYDOWN)
	{
		if (lParam = VK_HOME)     // 按下上光標將,我們讓模型編號加2
		{
			OutputDebugStringA("+1 \n");
			Fvalue = Fvalue+1;
		}
		if (lParam == VK_END)   // 按下下光標鍵,我們讓我們讓模型編號減2
		{
			OutputDebugStringA("-1 \n");
			Fvalue = Fvalue-1;
		}
	}
	return CallWindowProc(Global_OldProc, hwnd, uMsg, wParam, lParam);  // 全局熱鍵回調函數
}


// 主函數判斷如果是游戲標題則進行注入
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
	HWND hwnd = FindWindowW(L"Valve001", NULL);
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	if (GetCurrentProcessId() == pid)
	{
		HookDrawIndexedPrimitive();

		Global_OldProc = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)WindowProc);  // 注冊全局熱鍵
	}
	return true;
}

關閉Z軸緩沖: 通過 GetStreamSource 函數獲取到模型的來源,通過判斷來源來禁用相應模型的Z軸緩沖,實現透視。

#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")

HHOOK global_hook;
DWORD jump = 0;

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
	global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("dx9.dll")), 0);
}

// 中轉函數,執行被我們填充后的指令片段,並跳轉到原始指令的后面繼續執行
__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	__asm{
		mov edi, edi
		push ebp
		mov ebp, esp
		mov eax, jump
		jmp eax
	}
}

// 在此函數中DIY你個項目
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	//  OutputDebugStringA("執行我自己的函數,中轉函數\r\n");


	IDirect3DVertexBuffer9 *pStreamData = NULL;
	UINT iOffsetInBytes, iStride;
	if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release();  // 得到模型來源
	//if (iStride == 4)            // 得到來源為4的時候,才會關閉Z軸(此處為敵人ID)
	//{
	//	m_pDevice->SetRenderState(D3DRS_ZENABLE, FALSE);     // 關閉Z軸緩沖
	//}
	m_pDevice->SetRenderState(D3DRS_ZENABLE, FALSE);     // 關閉Z軸緩沖
	return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}

ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
	HANDLE handle = GetModuleHandle(TEXT("d3d9.dll"));      // 獲得d3d9.dll模塊基址
	if (handle == INVALID_HANDLE_VALUE) return NULL;
	return(ULONG_PTR)handle + 0x5CD20;                      // 相加偏移
}

bool HookDrawIndexedPrimitive()
{
	ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
	DWORD oldProtect = 0;
	if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect))   // 設置內存保護方式為可讀寫
	{
		DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5;                 // 計算出需要跳轉字節
		jump = address + 5;                                                        // 計算下一個跳轉字節
		__asm
		{
			mov eax, address
				mov byte ptr[eax], 0xe9                                                 // 填充為 jmp
				add eax, 1                                                              // 指針遞增
				mov ebx, value                                                          // 中轉
				mov dword ptr[eax], ebx                                                 // 賦值跳轉地址(遠跳轉)
		}
		VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect);               // 恢復內存保護方式
	}
	return true;
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
	HWND hwnd = FindWindowW(L"Valve001", NULL);
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	if (GetCurrentProcessId() == pid)
	{
		HookDrawIndexedPrimitive();
	}
	return true;
}

如下截圖:我直接禁用了全部的模型Z軸,實現了地圖全透的效果

老實說,這款游戲我並沒有找到人物的ID(一般也不玩CS),利用上面的方法排查就能找到,找到后替換上方的敵人ID即可完成針對人物的透視,這里懶得試了。

寫教程不易,轉載請加出處,謝謝 !!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM