计算机系统实践:Cheat Engine开发游戏修改器

📅 2022-01-28

🔄 2022-01-28

⌚ Reading time: 1 min


(待完善)

官网介绍:Cheat Engine是一个主要用于修改游戏和应用程序的开发环境。

也就是说CE是一个和游戏修改器差不多的东西,改物品栏数值、锁血啥的,但是普通游戏修改器是个现成的程序一键操作完成,CE是个开发环境,功能相当强大。

CE最终目的并不是去改游戏,更重要的,是对计算机组成原理、微机原理、汇编、程序与数据理论知识的应用。在游戏修改器开发过程中计算机知识的实践,这种快乐程度要比在8086虚拟环境上多得多。

既然是游戏修改,就涉及到一个游戏道德甚至是法律问题,对于单人游戏,个人认为怎么搞随便,至于多人游戏,有反作弊机制,更严重的“破坏计算机信息系统罪”适用于制作外挂并传播这一行为。

这个工作涉及到CPU工作原理、内存、x86指令集,理论知识,CE使用方法、工作流程,数据修改与指令修改。

GitHub: Cheat Engine. A development environment focused on modding

Cheat Engine wiki

游戏的运行与CE原理

CPU是逻辑电路,进行简单的逻辑运算,游戏执行最终落实到CPU上是一系列指令。因此最终CPU运行的就是一条一条指令。

CPU工作很呆板,就是执行一条条指令,通过指令操作数据对数据进行读和写。

比如游戏背包里的物品个数,这就是数据,物品数量变化,CPU进行加减就可以。

事实上,指令和数据是编码的形式存在在电脑内部的。

如果只有这么一串01,理论上讲是无法区分到底是指令还是数据,CPU是靠上下文来判断的。电脑开机后CPU读到的第一个字节必然是指令,每条指令里会指示后面的编码是指令还是数据。所以,改数据和改指令从编码层次上来看是一样的。

如果要修改编码,我们就得知道这些编码保存在哪里,肯定不是CPU,事实上存在内存里,指令和数据都是从内存里面取出来的,所以我们改程序实际上是在改内存里的编码。

CE的工作原理就是监控任意一个应用程序使用的全部内存,并且可以随意修改编码。这样一来,理论上就可以对游戏进行任何操作。

比如说,找到了血量的存储位置,并冻结数据,这就锁血了。或者找到“减”指令,换成空指令,或者直接变成加,这就挨打了还加血。

综上,CE修改的思路也就是很简单两步:

  • 1.找到感兴趣的编码在内存里的位置
  • 2.修改编码
    • 数据
    • 指令

数据和指令没有区别,都是编码

计算机编码是二进制的,但是为了表达方便,用字节为单位使用16进制表示。

数据修改

基本操作

最简单的用法,根据精确的数值行为定位内存地址。多次筛选后,就可以找到生命值所在的内存地址,可以直接修改数值。

如果不知道具体数值,比如说只有个血条,那么使用未知初值的选项进行第一次搜索,然后使用数值行为,比如说正在减少或者减少的具体值这些特征来筛选对应的内存地址。

数据类型也是个需要关注的问题。

内存数据结构

单次修改保存了变量列表,下次打开以后,发现这个表已经变了。如果要反复使用该如何操作呢?

内存从下往上看,

指令|数据(全局、堆、栈)

堆是随便分配加回收的,栈倒是连续的,但是栈的其实位置随机的。

因此指令的位置是固定的,堆和栈里的数据位置不固定,因此重新运行的游戏就不知道了。

但是游戏自己肯定是知道堆栈地址在哪里的。通过指针指向堆栈数据。

那么我们也可以和游戏一样,通过源头指针一路找到血量。

指针追溯

追溯到全局变量段就可以了。

手动追溯,对数据结构的深入理解。

查找到具体数值所在的内存后,可以直接查找这个内存地址,查看是什么指令在访问这个内存,就可以找到这个地址的指针。直接使用指针来操作数据,在每次游戏重启后,同样也是可以用的。

要注意的是,有时候数据定义在一个结构体内(或者是一个类),寻址方式是基地址+偏移地址,因此需要计算出偏移地址。可以在手动添加指针的时候设置偏移地址。

实际游戏这么一级指针还是有点简单了,更多情况下是好多级指针,在上层程序设计的时候有数据的继承,在汇编层次上就是多次指针跳转。每次跳转实际上是跳转到结构体的基地址了,因此要注意每一次跳转的偏移地址。追溯到绿色名称就可以停下了,这一段是指令段。

手动追溯指针需要非常了解数据结构以及编译后的数据存储方式。自动追溯会容易一点,首先找到血量所在的地址,使用指针扫描,然后瑞出游戏重新进入。再次找到血量的地址,找出相关指针,取个交集。大概就是这么个思路,因为偏移量总是固定的,交叉比对出我们需要的指针。

最后,如果数据的源头也不在全局变量里,那这修改数据就行不通了,然而指令肯定是在固定位置的。这时候就得使用改指令的方法了。

指令修改

x86指令集

指令集和机器码差不多(真指令),可以等价起来。机器码给给机器读,汇编给人看更容易一点。

CPU里的寄存器数量有限,速度快数量少。x86对应的寄存器只有16个,除了寄存器,还需要了解一下寻址方式和常用的指令。

MOV ECX, [RSI + 07E0]

机器码的格式,指令编码的长度是不固定的,简单知道编码格式

操作码 数据1 数据2

指令修改思路

这也是个去哪里干什么的事情。

首先找到感兴趣的数据,查看对这个数据的操作,看看是哪些指令修改了这些数据。

然后去看看反汇编代码,这时候就可以调整指令了。一个典型的,减血指令SUB位置替换为NOP,就实现锁血效果了。

还有个小问题,脚本如何知道替换哪个位置的指令呢?

把指令的物理地址记下来了,因此游戏一旦启动,指令的位置是固定的。

我们有了个更高的目标:在游戏版本更新后,这个插件还能使用。

游戏版本更新一般会有指令地址的偏移,但是这一段的顺序应该还是不变的,因此把这一段指令当作数据,查找这一段数据,使用内存扫描器。

这就是AOB技术,

一些经验

现代游戏基本上只能通过指令修改

实际修改的时候

寻找数据->找到关联指令->执行修改(自己写汇编)

寻找数据,可以通过具体数字直接找。

间接找数据,比如血量,