所谓 External Trainer, 就是我们的工具和游戏不在一个进程,需要进行跨进程修改。
Windows为我们提供了一系列的 External 类 API,我们可以在MSDN中搜索带有Ex的API:
我们可以看到很多External 跨进程的函数。这些我们都可以进行利用,进行跨进程创建,访问,或者读写。
还有几个比较经典的 跨进程 External 访问内存的函数是 CreateRemoteThread, ReadMemoryProcess, WriteMemoryProcess .....
下面以 寻找 太吾绘卷 的基址 作为例子
我已经找到天数的偏移为:
std::vector<DWORD> daysOffset = { 0x004A2308, 0x210, 0x170, 0x60, 0x38, 0x8, 0x10, 0x8EC};
模块为: mono-2.0-bdwgc.dll
首先我们需要找到模块地址
ULONGLONG getModuleBaseAddr(TCHAR* name, DWORD pid) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); //扫描游戏进程的所有模块
if (hSnap == INVALID_HANDLE_VALUE) //扫描失败,退出
{
DWORD error = GetLastError();
return NULL;
}
MODULEENTRY32 me;
me.dwSize = sizeof(MODULEENTRY32);
if (Module32First(hSnap, &me))
{
do
{
if (_tcscmp(me.szModule, name) == 0) // 如果找到了我们需要的模块
{
CloseHandle(hSnap);
return (ULONGLONG)(me.modBaseAddr); // 返回此模块地址
}
} while (Module32Next(hSnap, &me));
}
}
接着我们需要通过找到的模块地址和偏移找到天数的地址:
ULONGLONG getFinalAddddy(std::vector<DWORD> offsetVector, ULONGLONG BaseAddr, HANDLE hProcess)
{
DWORD offsetNum = offsetVector.size(); // 获取偏移的数量
ULONGLONG myAddy = BaseAddr;
for (int i = 0; i < offsetNum - 1; i++)
{
ReadProcessMemory(hProcess, LPCVOID(myAddy + offsetVector[i]), &myAddy, sizeof(ULONGLONG), NULL); // 遍历偏移地址
}
myAddy += offsetVector[offsetNum - 1];
return myAddy;
}
寻找天数最终的地址:
std::vector<DWORD> daysOffset = { 0x004A2308, 0x210, 0x170, 0x60, 0x38, 0x8, 0x10, 0x8EC}; // 天数偏移
ULONGLONG bdwgcModuleBaseAddr = getModuleBaseAddr((TCHAR*)(TEXT("mono-2.0-bdwgc.dll")), processId); // 模块地址
ULONGLONG dayAddress = getFinalAddddy(daysOffset, bdwgcModuleBaseAddr, hProcess); // 天数最终地址
寻找到最终天数地址我们就可以使用WriteMemoryProcess(),ReadMemoryProcess()进行读写
External Trainer 修改游戏进程的流程如下:
工具雏形:
源码地址:
https://github.com/absolutelycold/TaiWuWarrior
proc.h 解释
DWORD getPIDByName(TCHAR* name); // 通过进程名获取Process ID
HANDLE getProcessHandle(DWORD pid); // 通过 pid 获取进程句柄
ULONGLONG getModuleBaseAddr(TCHAR* name, DWORD pid); // 获取模块地址
ULONGLONG getFinalAddddy(std::vector<DWORD> offsetVector, ULONGLONG BaseAddr, HANDLE hProcess); // 通过偏移寻找地址
BOOL writeToMemoryInt(HANDLE hProcess, ULONGLONG addr, DWORD value); // 写入目标进程指定地址 4 字节大小的数据
DWORD readMemoryInt(HANDLE hProcess, ULONGLONG addr); // 读取目标进程指定地址 4 字节大小地址