游戏修改器制作教程七:注入DLL的各种姿势
教程面向有C\C++基礎的人,最好還要懂一些Windows編程知識
 代碼一律用Visual Studio 2013編譯,如果你還在用VC6請趁早丟掉它...
 寫這個教程只是為了讓玩家更好地體驗所愛的單機游戲,順便學到些逆向知識,我不會用網絡游戲做示范,請自重
往其他進程注入代碼大概分兩種,一種是像CE注入代碼那樣在目標進程申請內存,然后把機器碼寫進去,另一種是用高級語言寫一個DLL,然后注入目標進程(顯然是用高級語言實現更方便!)
 注入的代碼就是目標進程的一部分了,可以直接用指針讀寫目標進程內存,還可以hook目標進程的函數
本章介紹幾種常用的注入DLL的方法
遠線程注入
遠線程注入的原理是在目標進程調用LoadLibrary載入我們的DLL
首先寫個DLL用來測試,實現禁止結束進程
// dllmain.cpp : 定義 DLL 應用程序的入口點。 #include "stdafx.h"// hook代碼略,參考上一章BYTE terminateProcessOldCode[sizeof(JmpCode)];BOOL WINAPI MyTerminateProcess(HANDLE hProcess, UINT uExitCode) {return FALSE; // 禁止結束進程 }// DLL被加載、卸載時調用 BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {TCHAR processPath[256];TCHAR msg[270];switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:GetModuleFileName(GetModuleHandle(NULL), processPath, sizeof(processPath) / sizeof(processPath[0]));_stprintf_s(msg, _T("注入了進程 %s"), processPath);MessageBox(NULL, msg, _T(""), MB_OK);hook(GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "TerminateProcess"), MyTerminateProcess, terminateProcessOldCode);break;case DLL_PROCESS_DETACH:MessageBox(NULL, _T("DLL卸載中"), _T(""), MB_OK);// 卸載時記得unhookunhook(GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "TerminateProcess"), terminateProcessOldCode);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:break;}return TRUE; }然后寫個EXE用來注入DLL
主要用到的API:
// 載入DLL HMODULE WINAPI LoadLibrary(_In_ LPCTSTR lpFileName );// 在目標進程創建遠線程,相當于調用目標進程的函數 HANDLE WINAPI CreateRemoteThread(_In_ HANDLE hProcess,_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ SIZE_T dwStackSize,_In_ LPTHREAD_START_ROUTINE lpStartAddress,_In_ LPVOID lpParameter,_In_ DWORD dwCreationFlags,_Out_ LPDWORD lpThreadId );// 在目標進程申請內存 LPVOID WINAPI VirtualAllocEx(_In_ HANDLE hProcess,_In_opt_ LPVOID lpAddress,_In_ SIZE_T dwSize,_In_ DWORD flAllocationType,_In_ DWORD flProtect );遠線程注入DLL的實現有兩個前提
 1. kernel32比較特殊,這個模塊的基址在每個進程是一樣的(當然,32位和64位是不一樣的),所以本進程中的LoadLibrary地址可以用在其他進程(只要本進程和其他進程位數一樣)
 2. LoadLibrary需要一個參數,而且CreateRemoteThread剛好能傳入一個參數
EXE代碼(建議下載GitHub上的整個項目看看):
// 注入DLL,返回模塊句柄(64位程序只能返回低32位) HMODULE InjectDll(HANDLE process, LPCTSTR dllPath) {DWORD dllPathSize = ((DWORD)_tcslen(dllPath) + 1) * sizeof(TCHAR);// 申請內存用來存放DLL路徑void* remoteMemory = VirtualAllocEx(process, NULL, dllPathSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (remoteMemory == NULL){printf("申請內存失敗,錯誤代碼:%u\n", GetLastError());return 0;}// 寫入DLL路徑if (!WriteProcessMemory(process, remoteMemory, dllPath, dllPathSize, NULL)){printf("寫入內存失敗,錯誤代碼:%u\n", GetLastError());return 0;}// 創建遠線程調用LoadLibraryHANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, remoteMemory, 0, NULL);if (remoteThread == NULL){printf("創建遠線程失敗,錯誤代碼:%u\n", GetLastError());return NULL;}// 等待遠線程結束WaitForSingleObject(remoteThread, INFINITE);// 取DLL在目標進程的句柄DWORD remoteModule;GetExitCodeThread(remoteThread, &remoteModule);// 釋放CloseHandle(remoteThread);VirtualFreeEx(process, remoteMemory, dllPathSize, MEM_DECOMMIT);return (HMODULE)remoteModule; }// 卸載DLL BOOL FreeRemoteDll(HANDLE process, HMODULE remoteModule) {// 創建遠線程調用FreeLibraryHANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)remoteModule, 0, NULL);if (remoteThread == NULL){printf("創建遠線程失敗,錯誤代碼:%u\n", GetLastError());return FALSE;}// 等待遠線程結束WaitForSingleObject(remoteThread, INFINITE);// 取返回值DWORD result;GetExitCodeThread(remoteThread, &result);// 釋放CloseHandle(remoteThread);return result != 0; }#ifdef _WIN64 #include <tlhelp32.h> HMODULE GetRemoteModuleHandle(DWORD pid, LPCTSTR moduleName) {HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);MODULEENTRY32 moduleentry;moduleentry.dwSize = sizeof(moduleentry);BOOL hasNext = Module32First(snapshot, &moduleentry);HMODULE handle = NULL;do{if (_tcsicmp(moduleentry.szModule, moduleName) == 0){handle = moduleentry.hModule;break;}hasNext = Module32Next(snapshot, &moduleentry);} while (hasNext);CloseHandle(snapshot);return handle; } #endifint _tmain(int argc, _TCHAR* argv[]) {// 提升權限,不提升貌似也可以,以管理員身份運行就行EnablePrivilege(TRUE);// 打開進程HWND hwnd = FindWindow(NULL, _T("任務管理器"));DWORD pid;GetWindowThreadProcessId(hwnd, &pid);HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);if (process == NULL){printf("打開進程失敗,錯誤代碼:%u\n", GetLastError());return 1;}// 要將RemoteThreadDll.dll放在本程序當前目錄下TCHAR dllPath[MAX_PATH]; // 要用絕對路徑GetCurrentDirectory(_countof(dllPath), dllPath);_tcscat_s(dllPath, _T("\\RemoteThreadDll.dll"));// 注入DLLHMODULE remoteModule = InjectDll(process, dllPath);if (remoteModule == NULL){CloseHandle(process);return 2;} #ifdef _WIN64remoteModule = GetRemoteModuleHandle(pid, _T("RemoteThreadDll.dll"));printf("模塊句柄:0x%08X%08X\n", *((DWORD*)&remoteModule + 1), (DWORD)remoteModule); #elseprintf("模塊句柄:0x%08X\n", (DWORD)remoteModule); #endif// 暫停printf("按回車卸載DLL\n");getchar();// 卸載DLLif (!FreeRemoteDll(process, remoteModule)){CloseHandle(process);return 3;}// 關閉進程CloseHandle(process);return 0; }結果
64位的win10:
 
然后任務管理器無法結束任務,按回車后
 
又可以結束任務了
32位的win7:
 
(同上)
 
在主線程運行前遠線程注入
這個跟上面差不多,只是在目標進程的主線程運行前就可以運行我們DLL的代碼,可以提前做些手腳
方法是用CreateProcess創建進程時指定CREATE_SUSPENDED標志,這樣主線程就被掛起了,然后用遠線程注入DLL,再恢復主線程
// 程序運行時注入DLL,返回模塊句柄(64位程序只能返回低32位) HMODULE InjectDll(LPTSTR commandLine, LPCTSTR dllPath, DWORD* pid, HANDLE* process) {TCHAR* commandLineCopy = new TCHAR[32768]; // CreateProcess可能修改這個_tcscpy_s(commandLineCopy, 32768, commandLine);int cdSize = _tcsrchr(commandLine, _T('\\')) - commandLine + 1;TCHAR* cd = new TCHAR[cdSize];_tcsnccpy_s(cd, cdSize, commandLine, cdSize - 1);// 創建進程并暫停STARTUPINFO startInfo = {};PROCESS_INFORMATION processInfo = {};if (!CreateProcess(NULL, commandLineCopy, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, cd, &startInfo, &processInfo)){delete commandLineCopy;delete cd;return 0;}delete commandLineCopy;delete cd;*pid = processInfo.dwProcessId;*process = processInfo.hProcess;DWORD dllPathSize = ((DWORD)_tcslen(dllPath) + 1) * sizeof(TCHAR);// 申請內存用來存放DLL路徑void* remoteMemory = VirtualAllocEx(processInfo.hProcess, NULL, dllPathSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (remoteMemory == NULL){printf("申請內存失敗,錯誤代碼:%u\n", GetLastError());return 0;}// 寫入DLL路徑if (!WriteProcessMemory(processInfo.hProcess, remoteMemory, dllPath, dllPathSize, NULL)){printf("寫入內存失敗,錯誤代碼:%u\n", GetLastError());return 0;}// 創建遠線程調用LoadLibraryHANDLE remoteThread = CreateRemoteThread(processInfo.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, remoteMemory, 0, NULL);if (remoteThread == NULL){printf("創建遠線程失敗,錯誤代碼:%u\n", GetLastError());return NULL;}// 等待遠線程結束WaitForSingleObject(remoteThread, INFINITE);// 取DLL在目標進程的句柄DWORD remoteModule;GetExitCodeThread(remoteThread, &remoteModule);// 恢復線程ResumeThread(processInfo.hThread);// 釋放CloseHandle(remoteThread);VirtualFreeEx(processInfo.hProcess, remoteMemory, dllPathSize, MEM_DECOMMIT);return (HMODULE)remoteModule; }消息鉤子注入
還記得第二章的SetWindowsHookEx嗎,只要把dwThreadId設置為其他進程的線程ID或0就可以注入指定進程或所有進程了!(當然,32位DLL只能注入32位程序,64位DLL只能注入64位程序)
當鉤子過程被調用時,系統會檢測鉤子過程所在DLL是否已載入,如果沒有就會載入
 所以用SetWindowsHookEx注入DLL的前提是鉤子過程會被調用(比如安裝了鍵盤鉤子,但是沒有在目標進程按一下鍵盤,DLL就不會被注入)
 而且用這種方法DLL必須實現并導出鉤子過程(就算用不到)
測試用的DLL(dllmain.cpp不變,在另外一個文件導出鉤子過程):
// MsgHookDll.cpp : 定義 DLL 應用程序的導出函數。 //#include "stdafx.h"extern "C" __declspec(dllexport) // 導出這個函數 LRESULT CALLBACK CallWndProc(int code, WPARAM wParam, LPARAM lParam) {return CallNextHookEx(NULL, code, wParam, lParam); }為了讓鉤子過程被調用我選擇了調用頻率最高的WH_GETMESSAGE鉤子(當然,沒有消息循環的進程還是注入不了)
EXE代碼(建議下載GitHub上的整個項目看看):
DWORD GetProcessThreadID(DWORD pid) {HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid);THREADENTRY32 threadentry;threadentry.dwSize = sizeof(threadentry);BOOL hasNext = Thread32First(snapshot, &threadentry);DWORD threadID = 0;do{if (threadentry.th32OwnerProcessID == pid){threadID = threadentry.th32ThreadID;break;}hasNext = Thread32Next(snapshot, &threadentry);} while (hasNext);CloseHandle(snapshot);return threadID; }// 注入DLL,返回鉤子句柄,DLL必須導出CallWndProc鉤子過程,pid = 0則安裝全局鉤子 HHOOK InjectDll(DWORD pid, LPCTSTR dllPath) {// 載入DLLHMODULE module = LoadLibrary(dllPath);if (module == NULL){printf("載入DLL失敗,錯誤代碼:%u\n", GetLastError());return NULL;}// 取鉤子過程地址HOOKPROC proc = (HOOKPROC)GetProcAddress(module, "CallWndProc");if (proc == NULL){printf("取鉤子過程地址失敗,錯誤代碼:%u\n", GetLastError());return NULL;}// 取線程IDDWORD threadID = 0;if (pid != 0){threadID = GetProcessThreadID(pid);if (threadID == 0){printf("取線程ID失敗\n");return NULL;}}// 安裝鉤子HHOOK hook = SetWindowsHookEx(WH_GETMESSAGE, proc, module, threadID);// 釋放FreeLibrary(module);return hook; }int _tmain(int argc, _TCHAR* argv[]) {// 提升權限,不提升貌似也可以,以管理員身份運行就行EnablePrivilege(TRUE);// 取PID//DWORD pid = 0; // 全局鉤子,少玩全局鉤子不然會出問題...HWND hwnd = FindWindow(NULL, _T("任務管理器"));if (hwnd == NULL){printf("尋找窗口失敗,錯誤代碼:%u\n", GetLastError());return 1;}DWORD pid;GetWindowThreadProcessId(hwnd, &pid);// 注入DLL// 要將MsgHookDll.dll放在本程序當前目錄下HHOOK hook = InjectDll(pid, _T("MsgHookDll.dll"));if (hook == NULL)return 2;// 暫停printf("按回車卸載DLL\n");getchar();// 卸載DLLUnhookWindowsHookEx(hook);return 0; }安裝全局鉤子的結果
DLL劫持
這個是我最喜歡的注入方式,因為只需要DLL,連EXE都不用了,而且隨著程序啟動就自動注入,有些游戲補丁就用這個實現的
原理是系統加載DLL時會優先搜索程序當前目錄下有沒有這個DLL,沒有就再去System32等目錄搜索(除非在注冊表的KnownDLLs里)
 所以只要自己編個DLL,實現程序要用的函數(直接調用原DLL對應的函數就可以實現),再把它放到要注入的程序同目錄下,程序啟動時就會自動注入(當然,32位的程序會無視掉64位的DLL)
 
還是拿東方輝針城開刀,先看看它導入了什么函數
 
d3d9.dll只導入了一個函數,我們只用實現一個函數,就劫持它好了
 
DLL代碼(完整源碼):
// dllmain.cpp : 定義 DLL 應用程序的入口點。 #include "stdafx.h" #include "DllHijact.h"HMODULE g_d3d9Module = NULL;// DLL被加載、卸載時調用 BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {TCHAR processPath[MAX_PATH];TCHAR msg[MAX_PATH + 20];switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:GetModuleFileName(GetModuleHandle(NULL), processPath, MAX_PATH);_tcscpy_s(msg, _T("注入了進程 "));_tcscat_s(msg, processPath);MessageBox(NULL, msg, _T(""), MB_OK);// 加載原DLL,獲取真正的Direct3DCreate9地址g_d3d9Module = LoadLibrary(_T("C:\\Windows\\System32\\d3d9.dll"));RealDirect3DCreate9 = (Direct3DCreate9Type)GetProcAddress(g_d3d9Module, "Direct3DCreate9");if (RealDirect3DCreate9 == NULL){MessageBox(NULL, _T("獲取Direct3DCreate9地址失敗"), _T(""), MB_OK);return FALSE;}break;case DLL_PROCESS_DETACH:MessageBox(NULL, _T("DLL卸載中"), _T(""), MB_OK);// 手動卸載原DLLFreeLibrary(g_d3d9Module);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:break;}return TRUE; } // DllHijack.cpp : 定義 DLL 應用程序的導出函數。 //#include "stdafx.h" #include "DllHijact.h"Direct3DCreate9Type RealDirect3DCreate9 = NULL;// 把MyDirect3DCreate9導出為Direct3DCreate9,用__declspec(dllexport)的話實際上導出的是_MyDirect3DCreate9@4 #ifndef _WIN64 #pragma comment(linker, "/EXPORT:Direct3DCreate9=_MyDirect3DCreate9@4") #else #pragma comment(linker, "/EXPORT:Direct3DCreate9=MyDirect3DCreate9") #endif extern "C" void* WINAPI MyDirect3DCreate9(UINT SDKVersion) {MessageBox(NULL, _T("調用了Direct3DCreate9"), _T(""), MB_OK);return RealDirect3DCreate9(SDKVersion); }編譯后把DLL命名為d3d9.dll,放到目標程序同目錄下,運行目標程序時就自動注入
 
 
 
這個也能用于其他用了D3D9的程序,比如東方神靈廟
 
總結
以上是生活随笔為你收集整理的游戏修改器制作教程七:注入DLL的各种姿势的全部內容,希望文章能夠幫你解決所遇到的問題。