CE 修改器是一款本地内存修改工具。它的核心逻辑非常纯粹,即通过扫描和定位程序运行在计算机本地物理内存中的数据,然后对其进行强制修改。虽然它一直以来在游戏逆向和游戏漏洞挖掘(也就是俗称的“开挂”或“修改内存”)中声名鹊起,但它的应用场景远不止于此。我们完全可以使用这款工具去尝试挖掘各类 APP,甚至是 Web 端业务逻辑的漏洞。
关于软件的获取,大家可以直接前往 Cheat Engine 的官方网站下载最新版本并进行无脑安装,安装过程十分简单,本文不再赘述。为了让大家更直观地理解它的强大功能,本教程将随便打开一款游戏作为测试载体,借着这款游戏,手把手带你拆解 CE 的各项核心功能,以及如何利用它去搜索、修改内存数据,并最终转化为漏洞挖掘的实战思路。
一,环境准备与进程附加机制
在使用 CE 修改器进行任何操作之前,最核心的前置条件是“附加进程”。因为内存数据是依附于正在运行的程序进程而存在的。
1.1 寻找目标进程
打开 CE 修改器后,在界面的左上角,你会看到一个带有放大镜图标的小电脑按钮。点击这个图标,会弹出一个“进程列表(Process List)”窗口。这个列表展示了当前计算机上正在运行的所有活跃进程。
1.2 模拟器进程的特殊性
如果我们要测试的是手机游戏或移动端 APP,通常会在电脑上使用安卓模拟器。例如可以使用雷电模拟器。在进程列表中,你需要仔细寻找一个名为 headless 的进程(完整的进程名通常带有特定的前缀,如雷电模拟器可能是 leidian 或 LdBox 等前缀加上 headless)。
不同的模拟器,其进程的前缀可能千差万别,但如果你深入观察,它们的后端渲染或核心运行进程后面,大概率都会带有 headless 这个标识。找到这个目标进程后,直接选中它,然后点击右下角的 Open(打开)按钮,即可成功将 CE 注入并附加到该模拟器的内存空间中。
二,数据类型与搜索模式
附加进程后,CE 的主界面虽然看起来参数众多,但其实入门游戏漏洞挖掘(即修改内存)的逻辑非常简单:在搜索框里输入我们想要修改的数据,然后把它找出来。
2.1 深入理解数据类型
在 CE 搜索界面的右侧,有一个下拉菜单用于选择数据类型。内存世界的数据并不是单一的,它们根据存储需求被划分为不同的类型:
- 四字节 (4 Bytes):这是 CE 的默认选项。在绝大多数游戏和常规应用中,代表数量、状态、金币等常见数值的变量,一般都是使用四字节的整型(Integer)来存储的。大概率我们在测试游戏时,默认的四字节就足够应对 90% 的场景,几乎用不到其他类型。
- 其他类型科普:当然,为了应对复杂的业务逻辑,CE 也支持其他类型。比如八字节 (8 Bytes) 用于存储极其庞大的数值;单浮点 (Float) 和 双浮点 (Double) 常用于存储带有小数点的精确数据(例如游戏人物的坐标 X/Y/Z 轴、复杂的属性加成比例);此外还有专门用于搜索文本的字符串 (String) 类型以及各种各样的数组类型。它们有可能都存在于内存中,但初学者请牢牢锁定“四字节”。
2.2 精确搜索与范围搜索策略
在确认了数据类型后,我们需要决定采用何种扫描类型。CE 提供了极其丰富的条件过滤手段:
- 精确数值:第一个选项,也是最常用的选项。当我们在游戏中明确知道某个数值具体是多少时(例如我现在有 10 个金币),我们就精确搜索这个具体的数字。
- 比...大:当数值被遮挡或模糊化,但你知道它肯定大于某个临界值时使用。例如,你知道当前数值比 10 大。
- 比...小:同理,用于筛选比某个值(如比 10 小)的内存地址。结合上面两项,你可以手动逼近一个范围。
- 介于两者之间:这可以让你直接圈定一个范围区间。例如,你推测某个隐藏属性的数值介于 10 到 100 之间,即可使用此选项进行初步海选。
三,已知数值的精确搜索与“1≠1”
当我们知道目标数值时,搜索看似简单,但实操中会遇到大量干扰。我们将使用下面这个例子来演示这个过程。
3.1 首次扫描的“信息大爆炸”
假设我们现在要搜索一个数值 10。在输入框输入 10 后,点击首次扫描 (First Scan)。
此时,左侧的地址列表会瞬间爆满,可能会扫出几十万个对应的数值来。
- 什么是内存地址? 内存地址就是计算机本地内存条上,用来专门储存这些数据的一个个“房间号”或“坐标”。左边显示的是十六进制的内存地址,右边显示的是该地址当前对应的数值。
这几十万个地址中,只有一个或几个是我们真正需要的,其余的全是模拟器系统中其他应用或系统底层的无关缓存。
3.2 游戏内存中的“1 不一定等于 1”
假设我们在游戏里看到某个道具或状态显示为 1。如果它的数据类型是四字节,并且程序底层逻辑中确实让“显示的 1 等于内存的 1”,那么我们就直接在输入框输入 1,点击首次扫描。
结果非常惊人:左侧可能会出现 500多万个 值全为 1 的内存地址!
- 这第一次搜索的意思是,CE 在整个雷电模拟器(包括里面运行的其他所有后台应用、安卓系统本身以及当前测试的这款游戏)的广袤内存海中,把所有值为 1 的内存地址全部揪了出来。更重要的是,在游戏开发中,
1被极其频繁地用来代表“状态”。比如1代表“开启/激活”,0代表“关闭/取消”。这也是为什么单独搜个 1 能搜出几百万条记录的原因。
3.3 大浪淘沙
面对 500 万个地址,我们不知所措。解决办法是:回到游戏,想办法让这个数值产生变化。
- 数值变成 2:在游戏中操作,让原本的 1 往上增加,变成了
2。 - 再次扫描:切回 CE,在输入框输入
2,点击再次扫描。注意!这不是重新搜索,而是二次过滤。 它的逻辑是:“请帮我把刚才那 500 多万个旧值为 1 的地址中,现在值刚好变成 2 的那些地址筛选出来”。 - 瞬间,地址数量可能会锐减到 2000多个。
- 继续循环:我们将数值继续改成
3(这在测试游戏道具数量、属性加点方面经常用到)。在 CE 中输入 3 继续“再次扫描”,意思是把刚才变成 2 的那两千个地址里,现在变成 3 的筛选出来。此时,地址可能只剩下 17 个。 - 残酷的现实(红字警示):当我们尝试把数值改成
6,并在 CE 中搜索 6 时,左侧列表突然全空了!之前遗留的地址变成了红色(红色代表该内存地址的值在此刻发生了自动改变),但没有任何一个值是 6。
- “在游戏中,1 不一定等于 1。它有可能等于其他任何奇葩的数值。”
四,未知初始值与动态过滤
当“1≠1”的情况出现,或者我们面对的是血条、蓝条、模型大小这种根本没有数字显示的属性时,我们就不知道要搜的值究竟是多少。这时,必须启用未知的初始值扫描模式。
4.1 九亿地址的汪洋大海
在扫描类型中选择“未知的初始值”,这意味着我们要求 CE 把模拟器里所有的内存地址全部找出来。点击首次扫描后,由于没有数值过滤,CE 会抓取海量数据。他们可能是:“个、十、百、千、万、十万、百万、千万、亿……足足 九亿个 内存地址!”
面对这九亿个地址,我们如何定位那个控制特定属性的唯一内存?答案是:寻找变化规律。
4.2 追踪增减规律
假设我们测试的是一个模型大小滑块。
- 我们将滑块从 0 的位置往右拉大(比如拉到了刻度 1 的位置)。虽然不知道底层数字变成了几,但我们确信它的数值变大了。
- 在 CE 中,将扫描类型切换为 “数值增加了”。这也是区别于精确搜索、大于/小于区间的另一种动态逻辑过滤。
- 点击再次扫描,它的逻辑是:“把这九亿个内存地址中,当前值比上一次记录的值变大了的地址全部找到”。
- 瞬间,九亿个地址锐减到了 1000 万个(个十百千万十万百万千万)。
- 继续在游戏中把模型变大,再次使用“数值增加了”进行扫描,不断缩小范围。
4.3 无变化的值的妙用
在不断放大的过程中,假设我们将滑块拉到了最大值 10,无法再超过了。
为了继续筛选,我们可能会把它改回 0,然后再重新拉回 10。
此时,一个致命的逻辑陷阱出现了:
上一次我们扫描的终点状态,滑块是 10。现在我们折腾一圈又拉回了 10。如果此时我们再去搜索“数值变大了”,你会发现找不到目标了!因为当前状态的 10,对比上一次 CE 记录的状态 10,它既没有变大,也没有变小,它是没有改变的!
这时候,就必须祭出 CE 过滤中最核心的一招:无变化的值。
为什么会有这个选项?因为当我们无法确定数值到底变大还是变小时(或者像上述情况兜转回原点时),只要我们知道它相对于上一次扫描没有变化,就可以用它来大清洗。
- 清洗底层噪音:计算机系统中,有无数的内存地址在每一毫秒都在疯狂跳动(系统时间、渲染帧率、网络心跳包等)。比如,我们可以随便搜索一下无变化的值,原本 39000 个地址瞬间就变成了 3900 个!因为那些始终在后台跳动的垃圾数据,直接被“无变化”这个严苛的条件给踢出局了。
- 重复过滤:我们可以不改变游戏内的状态,连续多次点击“再次扫描(无变化的值)”。你会看到它一直在持续剔除那些偷偷发生改变的无关内存地址,直到地址数量彻底稳定下来。
经过反复的“变大(寻找 Increased value)”、“变小(de 开头,寻找 Decreased value)”以及“静止不动(寻找 Unchanged value)”的交叉过滤,最终地址减少的速度会越来越慢,可能卡在 2000 多个地址,再也不像之前那样大幅缩减了。
五,二分法与内存锁定机制
为什么经过这么严密的过滤,还会剩下 2000 多个地址同时在变动呢?
因为某些功能点牵扯到了无数个底层程序。 你拉动一个滑块,不仅改变了模型参数,可能同时触发了 UI 渲染、阴影重算、物理碰撞体积更新等一系列连带反应。这 2000 个地址都在忠实地根据你的拖动而变化。
但控制核心逻辑的源头地址只有一个!我们怎么从这 2000 个长得一模一样的同步变化地址中把它揪出来?
在下面将介绍一种被称为“最笨但也最无解”的方法——二分法内存锁定。
5.1 待修改区域与锁定功能
首,在左侧地址栏按下 Ctrl + A 全选这 2000 多个内存地址。然后点击列表右下方的一个红色对角线小箭头,将这些地址全部转移到底部的**“待修改区域”**。
在这个区域里:
- 双击某个地址后面的数值,可以将其强行改为任意值。
- 更关键的是最前方的方块(锁定框)。一旦勾选锁定,意味着 CE 会以极高的频率冻结这个内存地址。无论你在游戏中怎么拖动、怎么改变状态,这个被锁定的内存地址的数据都将被死死钉住,绝对不会被改变!
5.2 锁定排查实战演练
我们就要利用这个锁定特性来做排除法:
- 最大化状态:首先在游戏中把滑块拉到最大,观察模型大和小的明显视觉变化。
- 锁定一半:在底部的待修改区域,选中最上面的一批内存地址(大概一半),按住
Shift键往下拉选中一段,然后轻轻敲击键盘上的 空格键。瞬间,这批被选中的地址前面都打上了锁定勾。 - 游戏内验证:回到游戏,尝试把滑块往小拉。
- 理论逻辑:如果真正的控制内存被我们锁定了,那么无论滑块怎么动,游戏里的模型应该始终保持“最大”的状态(因为前端数据被冻结了)。
- 实际表现:拉小滑块后,发现模型居然跟着变小了!并没有被影响。这说明什么?说明刚才锁定的那一半地址里,全都是废物,没有我们要找的源头值。
- 无情剔除:既然不是,右键点击这一批地址,选择
Delete(或者直接按键盘快捷键Del),将其直接删除掉。 - 循环逼近:再选中剩下地址中的一半,按空格锁定,回游戏观察。没影响?继续右键 Delete。就这样劈一半、删一半,再劈一半、删一半。
- 曙光乍现:当删到某一批时,再回游戏拖动滑块,发现**“不太对劲了,模型越来越小了,甚至都凹进去了!”** 这说明什么?说明这一次锁定的地址堆里,终于包含了那个真正的核心控制地址。
- 逆向反推:先把凹进去的模型恢复原状取消锁定。既然目标在上面这一批里,那下面剩下的那些地址就全都不用了,直接删掉。
- 终极锁定:在包含目标的这一小堆地址里,继续切分。锁定一半,没反应?删掉。锁定另一半,有反应了!范围缩小到只剩两个地址。两个两个来试,排除掉不是的那俩,删掉。
- 真相大白:最后审视剩下的最后两个内存地址。修改第一个地址,发现游戏画面立刻出现不规则的剧变(“越来越那啥了”)。至此,我们成功揪出了唯一的控制内存!
六,从内存修改到后端业务逻辑漏洞
前面花费了大量篇幅讲解内存搜索与修改,很多人(尤其是纯做 Web 渗透的人)会产生一个巨大的疑惑:“我在本地把内存改了,只有我自己电脑的屏幕上能看到变化(俗称自娱自乐),这到底有什么用呢?”
6.1 案例一:道具购买时的负数绕过漏洞
假设游戏中有一个道具商城,道具有其对应的属性和数量。
正常情况下,我们在商城界面点击购买“一个”道具时,客户端(前端)会在底层组装一个请求,给服务器(后端)传送两个关键数据:
- 道具 ID(比如
1001) - 购买数量(比如
1)
这两个参数,在发送给服务器之前,是实实在在地躺在我们本地内存里的!
如果此时,我们利用上述学过的 CE 搜索技巧,精确找到了储存“购买数量”的那个内存地址,并在它发送请求前,强制把原本的 3 改成了 -3(负三)。
核心爆破点来了:
我们在本地改掉后,再次点击购买按钮。客户端此时抓取的内存数据已经是 -3,它会将这个被篡改的负数原封不动地传给服务器。
如果后端的开发人员在编写业务逻辑时,只校验了“金币是否足够”,却忘记对购买数量进行负数校验,那么服务器就会处理这个 -3。结果往往是:玩家非但没有扣钱,反而因为“扣除 -3 个道具的造价”,导致自己的金币余额瞬间暴涨!这就是利用本地内存修改直接影响前后端数据交互,从而引发的严重业务逻辑漏洞。同理,前面提到的修改人物建模参数,如果后端接收并保存,你就能在全服玩家面前展示出一个突破常规建模的畸形/与众不同的角色。
6.2 案例二:利用隐藏职业 ID 实现越权创建
除了外部数据篡改,内存修改还能窥探和提前利用未开放的功能。
职业切换是一个极其典型的重灾区。假设游戏后端已经实装了下一款大版本更新的职业——“枪炮手”。后端的代码逻辑、数据表都写好了,仅仅是当前的客户端(前端)尚未放出,UI 界面看不到这个按钮。
这时候客户端看不到怎么办?我们依然可以掏出 CE!
- 模糊搜索定位 ID:我们在创建角色界面,反复切换现有的职业。比如先选中弓箭手,使用模糊搜索(未知初始值);然后切换为战士,搜索“变化的值”。通过不断的反复切换、有变化、无变化的筛选,最终在内存中揪出代表“职业 ID”的那个内存地址。
- 寻找工整的规律:这类核心 ID 在游戏里通常设计得非常工整。无论是技能 ID、人物 ID 还是副本 ID,它们绝不是杂乱无章的。比如:战士的职业 ID 是
10101,法师是20101,道士是30101。这是一种高度规范的格式。 - 盲猜与越权发送:掌握了规律后,我们不难推测出那个被隐藏的枪炮手 ID 可能是
40101或某个极其相近的值。我们在找到的内存地址中,将原本战士的 ID 强制篡改为这个隐藏 ID。 - 绝杀:点击“创建角色”按钮!如果后端仅仅依靠前端 UI 来限制玩家,而忘记在接口处做二次校验(鉴权),那么我们向服务器发起的“创建枪炮师”请求就会被绿灯放行。作为一个普通玩家,我们就这样硬生生地越权创建了一个隐藏职业!这在游戏安全测试中,是一个极其经典的漏洞点。
6.3 为什么不用抓包?
讲到这里,很多懂得安全测试的读者一定会抛出一个灵魂拷问:“既然是为了篡改发给服务端的数据,我们用 Burp Suite 或者 Fiddler 直接抓包改包,不是更直接、更简单一些吗?没毛病吧?”
抓包确实更简单,但问题出在网络协议与加密机制上!
- 非 HTTP 协议的阻碍:现在的网游、手游,基本上为了保证通信的低延迟和实时性,极少会走传统的 HTTP/HTTPS 协议,绝大多数都是直接走 TCP 或 UDP 底层协议。传统的 Web 抓包工具对此往往束手无策。
- 加密数据的乱码困境:不仅如此,游戏数据在发送前几乎全被高强度加密了。我们就算用 Wireshark 抓到了包,抓到的也全是一堆毫无规律的乱码。
- 解密门槛过高:如果要走抓包路线,遇到加密,我们要么得自己具备极强的逆向分析能力去写一套解密工具,要么得花钱买解密脚本。大多数人都不会写解密工具,硬着头皮解密极为繁琐和困难。
为什么 CE 能无视这些难点?
因为无论数据在网络传输中被加密成了什么鬼样子,在它刚刚产生于客户端内部、尚未被加密函数处理、还躺在本地物理内存里的那一刻,它一定是明文的! 我们在内存中修改它,相当于在数据加密打包“上车”之前,就把货物给调包了。随后,客户端自己的加密代码会自动帮我们把这份“假数据”完美加密,并走 TCP/UDP 稳稳当当地发给服务器。这是极其优雅的降维打击!
6.4 案例三:突破 APP 加密的直播间越权开播漏洞
这种降维打击不仅适用于游戏,在 APP 和 Web 业务线同样大放异彩。
在测试某些直播平台时,你可能会发现直播间的许多核心功能点没走 HTTP 协议,而是走了 TCP/UDP,并且传输数据全被加密,抓出来的包全是乱码,根本解密不了。没法抓包改包,测试陷入死胡同。
这时候内存测试法就成了破局的唯一希望:越权开播漏洞。
- 我们打开 CE,附加到直播 APP 的进程中。
- 通过数值变化规律,在本地内存里找到了存储“我的直播间房间号”的地址。
- 在点击开播之前,我们将这个内存地址里的数值,强行改成了其他主播的直播间 ID。
- 点击“开播”按钮。APP 前端带着这个被篡改的他人 ID 进行了本地加密,顺利通过网络传给了后端服务器。
- 由于此时后端的开发人员过度信任了前端传来的(且经过严密加密的)数据流,忘记在后端做极其关键的鉴权操作(校验发请求的用户 UID 是否有该直播间 ID 的开播权限),最终导致漏洞触发——我们现在直接在别人的直播间里强行开播了!
这也完美印证了,在遇到加密、非标准协议的死胡同时,内存修改其实在特定业务场景下,是非常好用的神兵利器。
七,无他,唯手熟尔
什么是内存修改器?怎么使用它?说到底,核心只有两步:
- 搜索我要找的值
- 修改我要找的值
Cheat Engine 只是你手中的一把剑。招式看似简单,无非“搜索”与“修改”;但这一剑能刺多深、斩多准,终究取决于持剑之人的功力。
这条路没有捷径。你必须亲自动手,去拆解几十款游戏,去啃上百个 App 的本地内存交互,在海量数据里反复筛查、不断试错,直至从混沌中逼近那个唯一正确的答案。过程往往枯燥,却也正因如此,当真相浮现时,才更令人着迷。