尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++游戏内存数据逆向分析实战:从Cheat Engine扫描到进程读写

C++游戏内存数据逆向分析实战:从Cheat Engine扫描到进程读写 这次我们来看一个针对特定游戏《王权与自由》的C外挂逆向分析教程。这类内容的核心不是教你制作外挂而是通过一个具体的游戏案例深入理解Windows平台下游戏内存数据的结构、定位方法以及C如何与游戏进程交互。对于学习游戏安全、逆向工程或想了解游戏底层数据流的开发者来说这是一个非常硬核的实战切入点。本文的重点在于技术原理的拆解与实践。我们将聚焦于两个核心目标一是分析并读取游戏中的“人物属性”如生命值、魔法值、等级等二是定位并获取游戏角色的“坐标”信息。整个过程会涉及常用的逆向分析工具如Cheat Engine、C编程实现内存读写以及如何稳定地注入代码。如果你对游戏内存机制、指针扫描、数据断点以及编写外部辅助工具有兴趣这篇文章会提供一套清晰的实践路径。我们将从环境准备开始一步步演示如何定位关键数据地址分析数据结构并最终用C编写一个能够稳定读取人物属性和坐标的控制台程序。本文假设你具备基础的C编程能力和对Windows API的初步了解。需要强调的是所有分析和代码仅用于合法的学习、研究与测试目的严禁用于破坏游戏平衡、侵犯他人权益或任何非法用途。1. 核心能力速览能力项说明分析目标《王权与自由》游戏的人物属性与坐标内存数据技术栈C、Windows API、逆向工程基础核心工具Cheat Engine (用于扫描和定位)、Visual Studio (用于开发)、调试器关键技能指针扫描、多级指针追踪、内存读写、数据断点输出成果可编译运行的C程序能读取游戏实时属性与坐标适合场景游戏安全研究、逆向工程学习、内存数据分析实践前置知识基础C语法、对指针概念的理解、使用过Cheat Engine进行简单搜索2. 适用场景与使用边界这个教程主要适用于以下几类开发者或学习者逆向工程初学者希望通过一个完整的游戏案例了解从数据定位到代码实现的全流程。游戏安全研究人员学习如何分析一款网络游戏的内存数据组织方式为安全防护或漏洞挖掘提供思路。对游戏机制感兴趣的开发者想了解角色属性、坐标等核心数据在内存中是如何存储和变化的。C学习者希望将C应用于实际的进程交互和内存操作场景超越课本示例。重要的使用边界与法律风险提醒合法学习与研究本文所有技术仅限用于个人技术学习、学术研究或在拥有合法授权的私有服务器上进行测试。严禁将所学技术用于破坏官方游戏的公平性、制作并传播外挂、盗取他人账号或财产。不涉及网络协议本文专注于客户端内存数据分析不涉及封包加密、解密或修改服务器通信这是另一个复杂且法律风险更高的领域。环境隔离建议所有实践在单机环境、虚拟机或明确的测试服中进行避免对任何官方运营的在线游戏造成影响。尊重知识产权分析过程涉及的游戏客户端是软件作品请确保你拥有其合法的使用权。3. 环境准备与前置条件在开始动手之前你需要准备好以下软件和环境。请注意游戏客户端版本更新会导致内存地址偏移变化本文的方法论是通用的但具体地址需要你根据当前版本重新分析。1. 目标游戏环境《王权与自由》游戏客户端。为安全起见强烈建议使用单机版、私服或虚拟机内安装的客户端进行测试。确保游戏可以正常运行。2. 分析与开发工具Cheat Engine (CE)这是最重要的内存扫描和调试工具。从官网下载最新版本。Visual Studio 2022用于编写和编译C程序。安装时确保勾选“使用C的桌面开发”工作负载。进程查看工具如Process Explorer或CE自带的进程列表功能用于确认游戏进程名和PID。3. 开发环境配置Windows SDKVS2022通常会自动安装。确保你的项目能正常调用Windows API。基础C项目在VS中创建一个新的“控制台应用”项目这将是我们编写内存读取代码的地方。4. 知识准备理解十六进制、十进制转换。理解C中指针、多级指针的概念。了解Windows进程的基本概念如进程IDPID、虚拟内存空间。4. 分析流程与工具使用Cheat Engine实战这是整个教程最核心的部分。我们将使用Cheat Engine以下简称CE来定位人物属性和坐标在内存中的地址。4.1 定位基础属性以生命值为例启动游戏与CE运行《王权与自由》游戏并进入可以观察到角色生命值HP变化的场景如被攻击或恢复。以管理员身份运行Cheat Engine。附加进程在CE中点击左上角的电脑图标在进程列表中找到游戏的进程例如GameClient.exe并打开。首次扫描在“数值”输入框旁选择扫描类型为“精确数值”。假设你当前生命值是100在数值框输入100数值类型选择“4字节”大多数游戏用4字节整数或浮点数存储生命值。点击“首次扫描”。CE会列出所有存储着数值100的内存地址结果可能成千上万。变化过滤回到游戏让生命值发生变化例如受到一点伤害生命值变为95。在CE的数值框输入变化后的值95。点击“再次扫描”。CE会在上次的结果中筛选出值变为95的地址。重复此过程改变生命值扫描新值直到将结果减少到少数几个理想情况是1-2个。验证地址在地址列表中双击找到的地址将其添加到下方的地址列表。右键该地址选择“更改记录 - 数值”手动修改其值。回到游戏查看生命值是否随之改变。如果改变说明找到了正确的生命值地址。查找指针找到静态地址还不够因为游戏重启后地址会变。我们需要找到指向这个地址的指针。在地址列表中找到正确的生命值地址右键它选择“找出是什么改写了这个地址”。弹出一个新窗口回到游戏让生命值自然变化如自动恢复CE会记录下修改该地址的汇编指令及其寄存器信息。通常指令会是类似mov [raxrcx], edx的形式其中rax或rcx等寄存器里存放的就是基地址加偏移。记下这个指令和寄存器。4.2 定位坐标数据X, Y, Z坐标通常是浮点数Float或双精度浮点数Double。扫描未知初始值扫描类型选择“未知初始值”数值类型选择“Float”先尝试Float。点击“首次扫描”。通过变化筛选让角色移动改变位置。回到CE扫描类型选择“变动的数值”点击“再次扫描”。让角色静止扫描类型选择“未变动的数值”点击“再次扫描”。通过“数值增加”、“数值减少”等类型结合角色的移动方向如向前移动X坐标可能增加反复筛选直到找到3个连续或规律排列的Float值它们很可能就是X, Y, Z坐标。验证与指针扫描将这三个地址添加到地址列表手动修改其值看游戏内角色是否“瞬移”到对应位置。同样对其中一个坐标地址如X坐标执行“找出是什么访问了这个地址”然后移动角色CE会记录读取该坐标的指令。通过分析这些指令可以找到坐标数据的基地址和偏移。4.3 进行指针扫描这是获得重启后仍有效地址的关键。在CE的地址列表中右键你已验证的基础属性或坐标地址选择“指针扫描”。设置一个合理的偏移范围如0-1000点击“确定”开始扫描。CE会找出所有可能指向该地址的指针路径。扫描结果可能很多。重启游戏地址会变重新用CE附加进程。在指针扫描结果窗口中点击“重新扫描指针映射”并输入目标地址当前的新值。CE会筛选出仍然有效的指针路径。通常选择层级较少、偏移量看起来规整的指针路径。最终你会得到一个类似“GameClient.exe”0x123456的基地址以及一个或多个偏移量如0x78、0x234。假设我们通过以上分析得到以下信息地址为示例需自行分析生命值指针路径“GameClient.exe”0xABCDEF - 0x10 - 0x20最终地址存储4字节整数。坐标基址“GameClient.exe”0xFEDCBAX坐标偏移0x30(Float)Y坐标偏移0x34(Float)Z坐标偏移0x38(Float)。5. C编程实现内存读取有了指针路径和偏移信息我们就可以用C编写一个外部程序来读取这些数据了。我们将使用Windows API来打开进程、读取内存。5.1 创建项目与包含头文件在Visual Studio中创建控制台项目在主源文件如main.cpp开头包含必要的头文件并链接库。#include iostream #include Windows.h #include TlHelp32.h // 用于进程快照 #include string #include vector // 假设我们分析出的偏移常量请替换为你自己分析出的值 #define GAME_BASE_OFFSET 0xFEDCBA // 示例基地址偏移 #define HP_PTR_OFFSET1 0x10 #define HP_PTR_OFFSET2 0x20 #define COORD_X_OFFSET 0x30 #define COORD_Y_OFFSET 0x34 #define COORD_Z_OFFSET 0x385.2 获取游戏进程ID和模块基址我们需要找到游戏进程的ID和主模块在内存中的加载基址。DWORD GetProcessIdByName(const std::wstring processName) { DWORD pid 0; HANDLE snapshot CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot ! INVALID_HANDLE_VALUE) { PROCESSENTRY32W pe32; pe32.dwSize sizeof(PROCESSENTRY32W); if (Process32FirstW(snapshot, pe32)) { do { if (processName pe32.szExeFile) { pid pe32.th32ProcessID; break; } } while (Process32NextW(snapshot, pe32)); } CloseHandle(snapshot); } return pid; } uintptr_t GetModuleBaseAddress(DWORD pid, const std::wstring moduleName) { uintptr_t baseAddr 0; HANDLE snapshot CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); if (snapshot ! INVALID_HANDLE_VALUE) { MODULEENTRY32W me32; me32.dwSize sizeof(MODULEENTRY32W); if (Module32FirstW(snapshot, me32)) { do { if (moduleName me32.szModule) { baseAddr (uintptr_t)me32.modBaseAddr; break; } } while (Module32NextW(snapshot, me32)); } CloseHandle(snapshot); } return baseAddr; }5.3 读取多级指针指向的数据这是核心函数用于根据基址和一系列偏移读取最终的数据。templatetypename T T ReadMemory(HANDLE hProcess, uintptr_t baseAddress, const std::vectoruintptr_t offsets) { uintptr_t addr baseAddress; for (size_t i 0; i offsets.size(); i) { // 如果不是最后一个偏移读取的是指针地址 if (i offsets.size() - 1) { ReadProcessMemory(hProcess, (LPCVOID)addr, addr, sizeof(addr), nullptr); if (addr 0) { std::cerr 读取指针失败地址为0。 std::endl; return T(); } addr offsets[i]; } else { // 最后一个偏移读取目标数据 addr offsets[i]; T value; if (ReadProcessMemory(hProcess, (LPCVOID)addr, value, sizeof(value), nullptr)) { return value; } else { std::cerr 读取数据失败错误码: GetLastError() std::endl; return T(); } } } return T(); // 不应该执行到这里 }5.4 主函数逻辑将以上功能整合实现循环读取。int main() { const std::wstring gameProcessName LGameClient.exe; // 替换为实际进程名 const std::wstring gameModuleName LGameClient.exe; std::cout 正在寻找游戏进程... std::endl; DWORD pid GetProcessIdByName(gameProcessName); if (pid 0) { std::cout 未找到游戏进程: gameProcessName.c_str() std::endl; system(pause); return 1; } std::cout 找到进程PID: pid std::endl; HANDLE hProcess OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); if (hProcess NULL) { std::cout 打开进程失败错误码: GetLastError() std::endl; system(pause); return 1; } uintptr_t gameBaseAddr GetModuleBaseAddress(pid, gameModuleName); if (gameBaseAddr 0) { std::cout 获取模块基址失败。 std::endl; CloseHandle(hProcess); system(pause); return 1; } std::cout 游戏模块基址: 0x std::hex gameBaseAddr std::dec std::endl; // 计算生命值和坐标的最终基址 uintptr_t hpBasePtr gameBaseAddr GAME_BASE_OFFSET; // 生命值指针链的起点 uintptr_t coordBase gameBaseAddr GAME_BASE_OFFSET; // 坐标基址可能与生命值相同或不同 // 定义偏移链 std::vectoruintptr_t hpOffsets { HP_PTR_OFFSET1, HP_PTR_OFFSET2, 0 }; // 最后0表示读取指针指向的值 // 坐标是直接基址偏移不需要多级指针 // std::vectoruintptr_t coordXOffsets { COORD_X_OFFSET }; std::cout \n开始读取数据 (按CtrlC退出)...\n std::endl; while (true) { // 读取生命值 (假设是4字节整数) int currentHP ReadMemoryint(hProcess, hpBasePtr, hpOffsets); // 读取坐标 (假设是Float) float coordX ReadMemoryfloat(hProcess, coordBase, { COORD_X_OFFSET }); float coordY ReadMemoryfloat(hProcess, coordBase, { COORD_Y_OFFSET }); float coordZ ReadMemoryfloat(hProcess, coordBase, { COORD_Z_OFFSET }); system(cls); // 清屏使输出更清晰 std::cout 《王权与自由》内存数据读取 std::endl; std::cout 生命值(HP): currentHP std::endl; std::cout 坐标(X, Y, Z): ( coordX , coordY , coordZ ) std::endl; std::cout std::endl; Sleep(500); // 每500毫秒读取一次 } CloseHandle(hProcess); // 实际上循环不会退出这里为了代码完整保留 return 0; }6. 编译、运行与效果验证编译程序在Visual Studio中选择Release模式Debug模式也可但Release更小更快生成解决方案快捷键CtrlShiftB。运行游戏启动《王权与自由》游戏并进入游戏世界。运行你的程序以管理员身份运行编译生成的.exe文件通常在项目目录的x64/Release或Win32/Release文件夹下。验证效果程序窗口会持续输出读取到的人物生命值和三维坐标。在游戏中让角色移动、受到攻击或恢复生命值。观察程序输出的数值是否与游戏内的实际状态同步、准确地变化。如果数值没有变化或显示为0说明指针路径或偏移量不正确需要回到CE重新分析。成功的标准程序输出的生命值、坐标数值能实时、准确地反映游戏内的状态。7. 常见问题与排查方法在分析和编码过程中你几乎一定会遇到以下问题。这里提供排查思路。问题现象可能原因排查方式解决方案CE扫描不到任何地址或地址过多数值类型选错如用4字节扫浮点数、游戏数据加密、数值不是精确存储尝试所有数值类型4字节、浮点、双浮点、8字节等尝试“未知初始值”扫描结合游戏行为增减使用“变动的数值”、“未变动的数值”等扫描类型过滤找到的地址重启游戏后失效找到的是动态地址没有找到静态指针对找到的动态地址使用“找出是什么访问/改写了该地址”功能分析汇编指令获取基地址寄存器使用CE的“指针扫描”功能找到指向该动态地址的静态指针链C程序读取到的值始终为0或乱码1. 进程ID或模块基址获取失败2. 指针偏移错误3. 权限不足1. 检查进程名是否正确确认游戏已运行。2. 在CE中手动验证指针链每一步的地址和值。3. 以管理员身份运行你的C程序。1. 使用Process Explorer确认进程名和PID。2. 在CE的地址列表中手动添加指针链每一步的地址验证其值。3. 确保代码中OpenProcess请求了PROCESS_VM_READ权限。程序运行后游戏崩溃读取了非法内存地址如空指针在ReadMemory函数中添加更多错误检查例如在每次ReadProcessMemory后检查返回值。在读取指针值后判断其是否为NULL或明显不合法的地址如小于0x10000。坐标读取不准确如角色不动坐标乱跳1. 坐标基址不对2. 坐标不是Float类型3. 读取到了摄像机坐标而非角色坐标1. 在CE中冻结你找到的坐标值看游戏内角色是否真的无法移动。2. 尝试用Double类型扫描和读取。3. 寻找“World”或“LocalPlayer”相关的指针。1. 重新进行坐标扫描注意区分世界坐标和局部坐标。2. 尝试分析角色结构体坐标可能在一个包含更多信息的对象中。8. 扩展思路与最佳实践当你成功读取基础属性后可以尝试以下扩展这能让你对游戏内存结构有更深的理解分析角色结构体生命值、魔法值、体力、等级、经验值等属性很可能存储在同一个内存块结构体中。通过CE的“浏览相关内存区域”功能可以查看地址附近的数据尝试找出其他属性的偏移。写入内存需极度谨慎使用WriteProcessMemoryAPI可以修改内存。仅限单机测试例如尝试修改生命值为一个固定值。注意很多游戏服务器会校验关键数据在线修改极易导致封号。注入DLL与内部Hook更高级的做法是编写一个DLL注入到游戏进程内部通过Hook游戏函数来获取或修改数据。这涉及到更复杂的逆向和编程技术。使用模式扫描Pattern Scan为了应对游戏更新导致的地址变化可以编写代码扫描内存中的特定字节模式特征码来动态定位关键函数或数据而不是硬编码地址偏移。代码与数据分离将游戏版本特定的偏移量定义在配置文件中而不是硬编码在源代码里。这样游戏更新后只需更新配置文件无需重新编译程序。安全与合规最佳实践测试环境隔离所有实验务必在单机、虚拟机或明确允许的测试服务器上进行。最小权限原则你的程序只请求必要的权限如PROCESS_VM_READ。不破坏平衡深刻理解所学技术用于破坏游戏公平性所带来的法律和道德风险。用于防御理解攻击原理是构建更好防御的基础。可以思考游戏公司如何检测此类内存读取行为。通过这个完整的从分析到编码的流程你不仅学会了如何针对一款游戏进行数据定位更重要的是掌握了一套通用的Windows平台游戏内存数据分析方法论。这套方法CE扫描 - 指针追踪 - C读写是理解许多客户端软件内部机制的钥匙其价值远超过针对某一款游戏本身。
返回列表