You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题分析与修复方案

核心问题点

  1. 地址类型截断:64位程序下DWORD是32位类型,无法存储完整的64位地址,会导致基址或指针地址被截断,后续读取完全错误。
  2. 无效的条件判断:多处if语句后带有多余的分号(;),导致错误处理逻辑完全失效,比如if (phandle == INVALID_HANDLE_VALUE || phandle == NULL);会直接跳过错误处理,继续执行后续代码。
  3. 错误的错误码输出时机:GetLastError()仅在API调用失败时才有意义,成功调用后输出的是之前的错误值,无法反映当前操作的状态。
  4. 未检查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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 13:57:05