C++游戏指针地址转偏移异常:仅返回最后偏移值问题排查
问题:获取多级指针地址时仅返回最后一个偏移值
我编写了一段C++代码用于获取多级指针的最终地址,通过基址加偏移逐层解析的方式实现,但运行后输出始终仅为指针偏移列表中的最后一个值(0x160)。参考网上同类实现仍无法定位问题,相关代码及输出情况如下:
原代码
#include <Windows.h> #include <TlHelp32.h> #include <iostream> #include <tchar.h> #include <vector> #include <stdlib.h> using namespace std; DWORD GetModuleBaseAddress(TCHAR* lpszModuleName, DWORD pID) { DWORD dwModuleBaseAddress = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pID); // 快照进程内所有模块 MODULEENTRY32 ModuleEntry32 = { 0 }; ModuleEntry32.dwSize = sizeof(MODULEENTRY32); if (Module32First(hSnapshot, &ModuleEntry32)) // 读取第一个模块信息 { do { if (_tcscmp(ModuleEntry32.szModule, lpszModuleName) == 0) // 匹配目标模块 { dwModuleBaseAddress = (DWORD64)ModuleEntry32.modBaseAddr; break; } } while (Module32Next(hSnapshot, &ModuleEntry32)); // 遍历所有模块 } CloseHandle(hSnapshot); return dwModuleBaseAddress; } DWORD GetPointerAddress(HWND hwnd, DWORD gameBaseAddr, DWORD address, vector<DWORD> offsets) { DWORD pID = NULL; // 游戏进程ID GetWindowThreadProcessId(hwnd, &pID); HANDLE phandle = NULL; phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); std::cout<< GetLastError()<<std::endl; if (phandle == INVALID_HANDLE_VALUE || phandle == NULL); std::cout<< GetLastError()<<std::endl; DWORD offset_null = NULL; ReadProcessMemory(phandle, (LPVOID*)(gameBaseAddr + address), &offset_null, sizeof(offset_null), 0); DWORD pointeraddress = offset_null; // 初始指针地址 for (int i = 0; i < offsets.size() - 1; i++) // 遍历除最后一个外的所有偏移 { ReadProcessMemory(phandle, (LPVOID*)(pointeraddress + offsets.at(i)), &pointeraddress, sizeof(pointeraddress), 0); std::cout<< GetLastError()<<std::endl; } return pointeraddress += offsets.at(offsets.size() - 1); // 添加最后一个偏移 } int main() { HWND hwnd_AC = FindWindowA(NULL, "BloonsTD6"); if (hwnd_AC != FALSE); DWORD pID = NULL; GetWindowThreadProcessId(hwnd_AC, &pID); HANDLE phandle = NULL; phandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); if (phandle == INVALID_HANDLE_VALUE || phandle == NULL); DWORD gamebaseaddress1 = GetModuleBaseAddress(_T("GameAssembly.dll"), pID); DWORD moneyAddr = 0x035F9E18; vector<DWORD> moneyOffsets{ 0x198,0xF8,0x138,0x160,0x60,0x1A8,0xC8 }; DWORD64 moneyPtrAddr = GetPointerAddress(hwnd_AC, gamebaseaddress1, moneyAddr, moneyOffsets); std::cout << "debugginfo: Value at offset = " << std::hex << moneyPtrAddr << std::endl; std::cout<< GetLastError()<<std::endl; }
问题分析与修复方案
核心问题点
- 地址类型截断:64位程序下
DWORD是32位类型,无法存储完整的64位地址,会导致基址或指针地址被截断,后续读取完全错误。 - 无效的条件判断:多处
if语句后带有多余的分号(;),导致错误处理逻辑完全失效,比如if (phandle == INVALID_HANDLE_VALUE || phandle == NULL);会直接跳过错误处理,继续执行后续代码。 - 错误的错误码输出时机:
GetLastError()仅在API调用失败时才有意义,成功调用后输出的是之前的错误值,无法反映当前操作的状态。 - 未检查API返回值:
ReadProcessMemory、OpenProcess等关键API调用后未检查返回值,无法确认操作是否成功,一旦读取失败会导致后续指针地址错误。
修正后的代码
#include <Windows.h> #include <TlHelp32.h> #include <iostream> #include <tchar.h> #include <vector> #include <cstdint> using namespace std; // 使用uintptr_t适配32/64位程序 uintptr_t GetModuleBaseAddress(TCHAR* lpszModuleName, DWORD pID) { uintptr_t dwModuleBaseAddress = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pID); // 兼容32位模块快照 if (hSnapshot == INVALID_HANDLE_VALUE) { cerr << "CreateToolhelp32Snapshot failed, error: " << GetLastError() << endl; return 0; } MODULEENTRY32 ModuleEntry32 = { 0 }; ModuleEntry32.dwSize = sizeof(MODULEENTRY32); if (Module32First(hSnapshot, &ModuleEntry32)) { do { if (_tcscmp(ModuleEntry32.szModule, lpszModuleName) == 0) { dwModuleBaseAddress = reinterpret_cast<uintptr_t>(ModuleEntry32.modBaseAddr); break; } } while (Module32Next(hSnapshot, &ModuleEntry32)); } else { cerr << "Module32First failed, error: " << GetLastError() << endl; } CloseHandle(hSnapshot); return dwModuleBaseAddress; } uintptr_t GetPointerAddress(HWND hwnd, uintptr_t gameBaseAddr, uintptr_t address, const vector<uintptr_t>& offsets) { DWORD pID = 0; if (!GetWindowThreadProcessId(hwnd, &pID)) { cerr << "GetWindowThreadProcessId failed, error: " << GetLastError() << endl; return 0; } // 请求最小必要权限,避免PROCESS_ALL_ACCESS被拒绝 HANDLE phandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pID); if (phandle == NULL || phandle == INVALID_HANDLE_VALUE) { cerr << "OpenProcess failed, error: " << GetLastError() << endl; return 0; } uintptr_t pointerAddress = gameBaseAddr + address; // 读取第一层指针 if (!ReadProcessMemory(phandle, reinterpret_cast<LPCVOID>(pointerAddress), &pointerAddress, sizeof(pointerAddress), nullptr)) { cerr << "ReadProcessMemory (first layer) failed, error: " << GetLastError() << endl; CloseHandle(phandle); return 0; } // 遍历中间偏移,每次解引用 for (size_t i = 0; i < offsets.size() - 1; ++i) { if (!ReadProcessMemory(phandle, reinterpret_cast<LPCVOID>(pointerAddress + offsets[i]), &pointerAddress, sizeof(pointerAddress), nullptr)) { cerr << "ReadProcessMemory (offset " << i << ") failed, error: " << GetLastError() << endl; CloseHandle(phandle); return 0; } } // 添加最后一个偏移 pointerAddress += offsets.back(); CloseHandle(phandle); return pointerAddress; } int main() { HWND hwnd_AC = FindWindowA(NULL, "BloonsTD6"); if (hwnd_AC == NULL) { cerr << "FindWindowA failed, error: " << GetLastError() << endl; return 1; } DWORD pID = 0; GetWindowThreadProcessId(hwnd_AC, &pID); uintptr_t gamebaseaddress1 = GetModuleBaseAddress(_T("GameAssembly.dll"), pID); if (gamebaseaddress1 == 0) { cerr << "Failed to get module base address" << endl; return 1; } uintptr_t moneyAddr = 0x035F9E18; vector<uintptr_t> moneyOffsets{ 0x198,0xF8,0x138,0x160,0x60,0x1A8,0xC8 }; uintptr_t moneyPtrAddr = GetPointerAddress(hwnd_AC, gamebaseaddress1, moneyAddr, moneyOffsets); if (moneyPtrAddr != 0) { cout << "debugginfo: Final pointer address = " << hex << moneyPtrAddr << endl; } else { cerr << "Failed to get final pointer address" << endl; } return 0; }
关键修改说明
- 统一地址类型:使用
uintptr_t(C++标准类型)存储地址,自动适配32/64位程序,避免地址截断。 - 修复条件判断:移除
if语句后的多余分号,添加有效的错误处理逻辑。 - API返回值检查:对所有关键API调用添加返回值检查,一旦失败立即输出错误并终止流程。
- 权限最小化:将
OpenProcess的权限从PROCESS_ALL_ACCESS改为PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,避免因权限不足导致打开进程失败。 - 正确的错误码输出:仅在API调用失败时输出
GetLastError(),确保错误信息有效。
内容的提问来源于stack exchange,提问作者Chraydragon
相关产品推荐
相关产品推荐

