教程面向有C\C++基础的人,最好还要懂一些Windows编程知识
代码一律用Visual Studio 2013编译,如果你还在用VC6请趁早丢掉它...
写这个教程只是为了让玩家更好地体验所爱的单机游戏,顺便学到些逆向知识,我不会用网络游戏做示范,请自重
往其他进程注入代码大概分两种,一种是像CE注入代码那样在目标进程申请内存,然后把机器码写进去,另一种是用高级语言写一个DLL,然后注入目标进程(显然是用高级语言实现更方便!)
注入的代码就是目标进程的一部分了,可以直接用指针读写目标进程内存,还可以hook目标进程的函数
本章介绍几种常用的注入DLL的方法
远线程注入的原理是在目标进程调用LoadLibrary载入我们的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);
// 卸载时记得unhook
unhook(GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "TerminateProcess"), terminateProcessOldCode);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
主要用到的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;
}
// 创建远线程调用LoadLibrary
HANDLE 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)
{
// 创建远线程调用FreeLibrary
HANDLE 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
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;
}
#endif
int _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"));
// 注入DLL
HMODULE remoteModule = InjectDll(process, dllPath);
if (remoteModule == NULL)
{
CloseHandle(process);
return 2;
}
#ifdef _WIN64
remoteModule = GetRemoteModuleHandle(pid, _T("RemoteThreadDll.dll"));
printf("模块句柄:0x%08X%08X\n", *((DWORD*)&remoteModule + 1), (DWORD)remoteModule);
#else
printf("模块句柄:0x%08X\n", (DWORD)remoteModule);
#endif
// 暂停
printf("按回车卸载DLL\n");
getchar();
// 卸载DLL
if (!FreeRemoteDll(process, remoteModule))
{
CloseHandle(process);
return 3;
}
// 关闭进程
CloseHandle(process);
return 0;
}
又可以结束任务了
这个跟上面差不多,只是在目标进程的主线程运行前就可以运行我们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;
}
// 创建远线程调用LoadLibrary
HANDLE 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必须实现并导出钩子过程(就算用不到)
// 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钩子(当然,没有消息循环的进程还是注入不了)
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)
{
// 载入DLL
HMODULE 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;
}
// 取线程ID
DWORD 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();
// 卸载DLL
UnhookWindowsHookEx(hook);
return 0;
}
这个是我最喜欢的注入方式,因为只需要DLL,连EXE都不用了,而且随着程序启动就自动注入,有些游戏补丁就用这个实现的
原理是系统加载DLL时会优先搜索程序当前目录下有没有这个DLL,没有就再去System32等目录搜索(除非在注册表的KnownDLLs里)
所以只要自己编个DLL,实现程序要用的函数(直接调用原DLL对应的函数就可以实现),再把它放到要注入的程序同目录下,程序启动时就会自动注入(当然,32位的程序会无视掉64位的DLL)
d3d9.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);
// 手动卸载原DLL
FreeLibrary(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);
}