三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

C++游戏逆向入门:从整数变量定位到内存修改实战

C++游戏逆向入门:从整数变量定位到内存修改实战

1. 从“整数变量”切入,理解外挂逆向的底层逻辑

如果你对游戏外挂或软件逆向感兴趣,并且正在学习C++,那么“整数变量”这个概念,就是你绕不开的第一个,也是最核心的实战起点。很多人一上来就想找复杂的函数调用或内存结构,结果连最基础的数值都改不了,很快就卡住了。

这篇内容不讲那些花哨的、可能涉及违规的“成品”制作,我们只聚焦于一个纯粹的技术问题:在一个运行中的C++程序(比如一个游戏客户端)里,如何定位、读取并修改一个简单的整数变量,例如角色的生命值、金币数量或者弹药量。这就像学开车先学打方向盘和踩刹车,是最基本的操作,但也是后续所有复杂操作(如修改浮点数、分析类结构、调用函数)的基石。

为什么整数变量这么重要?因为在游戏或应用的内存中,大量关键状态都是用整数(int,DWORD,int32_t等)来存储的。它的内存布局简单、寻址直接,是理解内存数据读写、指针偏移、多级指针等逆向核心概念的最佳练手对象。搞定了整数,你就拿到了打开内存修改这扇大门的钥匙。

2. 动手前的准备:工具、目标与安全边界

在开始任何操作之前,必须明确两件事:用什么工具,以及在什么目标上练习。这是保证学习过程有效且安全的前提。

2.1 工具选择:调试器与内存查看器

对于C++程序的逆向,尤其是针对桌面应用或游戏,以下几类工具是标配:

  1. 调试器 (Debugger):用于动态分析程序执行流程,下断点,查看寄存器、栈和内存。这是最强大的工具。

    • x64dbg / x32dbg:开源、免费、功能强大,对Windows平台程序支持极好,是逆向初学者的首选。它集成了反汇编、内存查看、断点管理等功能。
    • Cheat Engine (CE):虽然常被称作“修改器”,但其本质是一个极其强大的内存扫描和调试工具。它的“内存扫描”功能是定位变量的神器,图形化界面也比纯调试器更友好入门。
    • Visual Studio Debugger:如果你有程序的调试符号(PDB文件)或源代码,这是最好的选择。但对于逆向无源码程序,通常不适用。
  2. 静态分析工具:用于在不运行程序的情况下分析二进制文件。

    • IDA Pro / Ghidra:行业标杆,功能极其强大,可以进行反编译和深度静态分析。但学习曲线陡峭,对于单纯的整数变量定位,初期可能用不上它的全部功能。

对于本教程的目标——定位并修改一个整数变量,我强烈建议从 Cheat Engine 开始。它的扫描流程直观,能让你快速建立“内存地址”与“游戏数值”之间的关联感。

2.2 练习目标:切勿触碰在线游戏

绝对不要用任何正在运营的在线多人游戏(如《英雄联盟》、《CS:GO》、《原神》等)作为练习目标。这些游戏都有强大的反作弊系统(如 BattlEye, EasyAntiCheat, VAC),任何内存修改行为都会被检测并导致账号封禁,甚至可能触发法律风险。

安全的练习环境应该是:

  • 单机游戏:例如一些老的、没有反作弊的单机游戏。
  • 专门设计的练习程序:自己用C++写一个小程序,包含几个你想找的整数变量(生命值、分数等)。这是最安全、最可控的方法。
  • 破解练习靶场:如一些CTF(Capture The Flag)逆向题目或专门的逆向练习平台,它们的设计目的就是让你去分析和修改。

记住:我们的目的是学习底层的内存和C++程序工作原理,而不是破坏游戏规则。一切操作应在合法授权的环境或自己构建的环境中进行。

2.3 基础概念预热:进程、内存地址与数值类型

在打开工具前,需要理解三个概念:

  • 进程:一个正在运行的程序实例。调试器或CE需要“附加”到目标进程,才能访问其内存空间。
  • 内存地址:进程内存中每个字节的唯一编号,通常用十六进制表示(如0x01234567)。变量就存储在某个地址上。
  • 数值类型:整数有很多种。在32位程序中,int通常是4字节;在64位程序中也可能如此,但指针长度会变。CE和调试器需要你指定扫描的类型,如4 Bytes(4字节整数)。

3. 实战流程:四步定位并修改一个整数变量

下面我们以 Cheat Engine 为例,结合一个自己编写的简单C++程序(假设它有个变量int playerHealth = 100;),演示完整的流程。

3.1 第一步:启动目标与扫描器

首先,运行你的目标程序(比如那个自己写的“游戏”)。然后打开Cheat Engine。

  1. 点击 CE 左上角的电脑图标(“选择进程”)。
  2. 在进程列表中找到你的目标程序,选中它,点击“打开”。
  3. 现在,CE 就附加到了目标进程上,可以读写其内存了。

3.2 第二步:首次扫描与数值变化追踪

假设游戏中玩家生命值显示为 100。

  1. 在 CE 的“数值”输入框里,输入当前已知的数值100
  2. 在“扫描类型”中,选择精确数值
  3. 在“数值类型”中,选择4 Bytes(因为C++中int通常是4字节)。如果不确定,也可以先试试All(所有类型),但结果会很多。
  4. 点击“首次扫描”。CE 会在进程的整个内存空间中寻找所有值为 100 的 4 字节数据。扫描结束后,左侧列表可能会显示几十万甚至上百万个地址。这很正常。

关键操作来了:让游戏中的数值发生变化。5. 在你的“游戏”里,做点事情让生命值改变。比如让自己掉一点血,假设生命值变成了 95。 6. 回到 CE,在数值输入框里输入变化后的值95。 7. 点击“再次扫描”。这次 CE 会在上一轮的结果中,筛选出那些值从 100 变成了 95 的地址。 8. 重复步骤5-7。再让生命值变化一次(比如吃药回血到 98),然后在 CE 中输入新值98并“再次扫描”。

通常,经过2-3次这样的变化扫描,左侧的地址列表会急剧减少到只剩下几个,甚至一个。这个(或这些)地址,就极有可能是存储playerHealth这个整数的内存地址。

3.3 第三步:验证与锁定地址

找到疑似地址后,需要验证。

  1. 在 CE 左侧的地址列表中,双击某个地址,它会出现在下方的地址列表中。
  2. 在下方的列表中,你可以直接修改该地址的“值”这一栏。比如把 98 改成 200。
  3. 切回你的“游戏”查看。如果游戏里显示的生命值也瞬间变成了 200,那么恭喜,你找对了!
  4. 为了下次方便,你可以点击地址列表最左边的复选框,或者右键地址选择“锁定”,并设置一个锁定值(如 999)。这样 CE 会不断将该地址的值写回 999,实现“锁血”效果。(注意:这只是演示原理,在真实游戏中使用“锁定”功能非常容易被检测。)

3.4 第四步:理解“指针”与重启动态

你可能会发现,关闭游戏再重新打开,刚才找到的那个地址失效了,修改后游戏没反应。这是因为很多程序(尤其是游戏)使用动态内存分配,变量每次启动时被分配到的内存地址都不同。

这时就需要找指针。指针是另一个内存地址,里面存放着目标变量(如生命值)的当前地址。虽然生命值的地址每次变,但指向它的那个指针的地址可能是固定的(相对于某个模块基址)。

  1. 在 CE 中,对你已经找到的生命值地址右键,选择“找出是什么改写了这个地址”或“找出是什么访问了这个地址”。
  2. 让游戏里的生命值发生变化(掉血或回血),CE 会记录下所有访问该地址的汇编指令。
  3. 查看这些指令,通常你会看到类似mov [eax+04], ecx这样的指令。其中的eaxebx等寄存器里存放的就是基地址,+04就是偏移。
  4. 通过分析这些指令,你可以找到一层或多层指针。在 CE 中可以使用“指针扫描”功能来帮助寻找稳定的指针路径。

这个过程比找直接地址复杂得多,是逆向从入门到进阶的关键一步。它涉及到对程序内存布局(如堆、栈、数据段)和反汇编指令的初步理解。

4. 从修改到理解:常见问题与深度思考

当你成功修改了一个整数后,真正的学习才刚刚开始。下面是一些必然会遇到的问题和需要深究的方向。

4.1 为什么扫描不到或地址太多?

  • 数值类型选错int可能是4 Bytes,但如果是short就是2 Bytesint64就是8 Bytes。如果游戏用float存储生命值(虽然不常见),你就需要选Float类型扫描。不确定时,用All进行首次扫描是可行的策略。
  • 数值不是精确值:有些游戏的显示值是经过处理的,内存中存储的可能是原始值的倍数(比如实际存储*10,显示时/10)。这时需要用“未知初始值”扫描类型,并通过“数值增加了”、“数值减少了”来过滤。
  • 加密或校验:简单的整数也可能被异或(XOR)一个值,或者与另一个变量进行运算。这需要更高级的逆向分析,找到解密算法。

4.2 修改后游戏崩溃或被踢出

  • 客户端校验:游戏客户端自身会检查关键数据的合理性。比如生命值瞬间从 100 变成 99999,可能触发断言或异常处理导致崩溃。
  • 服务器权威:在联网游戏中,生命值等核心状态由服务器计算并同步给客户端。客户端只是显示端(称为“客户端预测”或“显示镜像”)。你修改的只是本地显示,服务器不认可,下一秒就会被同步回正确值,或者因为数据异常被服务器断开连接。这是为什么修改在线游戏几乎必然失败的根本原因。

4.3 如何将找到的地址用于“外挂”程序?

这涉及到外部进程内存读写。在 Windows 上,通常使用ReadProcessMemoryWriteProcessMemoryAPI。找到稳定地址(或指针路径)后,你可以用另一个C++程序(你的“外挂”程序)去打开目标进程,计算最终地址,然后进行读写。

// 极度简化的示例,省略了所有错误处理 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId); int healthValue = 0; SIZE_T bytesRead; ReadProcessMemory(hProcess, (LPCVOID)targetAddress, &healthValue, sizeof(int), &bytesRead); // 修改 healthValue... WriteProcessMemory(hProcess, (LPVOID)targetAddress, &healthValue, sizeof(int), &bytesRead); CloseHandle(hProcess);

再次强调,这段代码仅用于学习进程间通信原理,切勿用于非法修改他人软件。

4.4 整数之外的下一步:数组、结构与类

一个int变量很少孤立存在。比如,一个角色可能有生命值、魔法值、经验值等多个整数,它们很可能在内存中连续排列,这就是一个结构体

struct Character { int health; int mana; int level; };

找到health的地址后,mana的地址很可能就是health地址 + 4(一个int的大小)。通过分析这种内存布局,可以定位整个对象。

如果游戏中有多个同类对象(比如多个怪物),它们可能被放在一个数组里。通过分析数组的基址、元素大小和索引,可以遍历并修改所有对象。

5. 总结:逆向思维与安全实践的平衡

通过“整数变量”这个小小的切入点,我们实际上走完了一个微型逆向工程的核心流程:观察 -> 假设 -> 测试 -> 验证 -> 分析 -> 稳定化。这个过程锻炼的是对程序运行时状态的洞察力和对底层内存操作的熟悉度。

对于学习者,我的建议是:

  1. 目标安全化:永远在自己的程序、开源程序或明确允许的练习环境上操作。
  2. 工具熟练化:把 Cheat Engine 和 x64dbg 的基本操作(扫描、断点、查看内存/寄存器、单步执行)练到形成肌肉记忆。
  3. 思维系统化:不要满足于“改成功了”。多问为什么:这个地址为什么在这里?它和哪个模块相关?是谁在读写它?指针链是怎么构成的?
  4. 知识扩展化:理解整数之后,主动去研究浮点数(Float/Double)在内存中的格式(IEEE 754),研究字符串的存储方式(ASCII/UTF-16),研究C++类对象的虚函数表(vTable)在内存中的布局。

最终,这项技能的正向用途远大于其边缘用途:它可以用于软件调试、漏洞分析、安全研究、游戏模组(Mod)开发(在支持的前提下)以及理解计算机系统的工作原理。把基础打牢,把原理吃透,你看到的将不再是一个个冰冷的数字,而是一个正在呼吸的、由逻辑和数据构成的程序生命体。

← 返回列表