从MOV指令到稳定基址:Cheat Engine逆向寻址实战解析
2026/7/22 11:32:34 网站建设 项目流程

1. 项目概述:从一条指令到稳定基址的完整旅程

在逆向分析,特别是游戏修改或软件调试的领域里,找到一个稳定的“基址”是后续所有操作(如制作修改器、分析数据流)的基石。很多新手朋友拿到一个地址,发现重启程序就变了,这就是典型的动态地址,其背后往往隐藏着一个由多层指针构成的寻址链。今天,我就以一个实战案例,带大家走一遍从游戏内存里一条最基础的MOV指令开始,如何一步步抽丝剥茧,最终定位到那个“重启不变”的稳定基址的全过程。这个过程会涉及到对指令流的分析、对寄存器值的追踪、偏移量的计算,以及最考验耐心的多层指针定位。我们使用的核心工具是Cheat Engine (CE),它不仅是内存扫描利器,更是动态分析的神器。无论你是想了解游戏内存结构,还是想深入学习逆向中的地址寻址逻辑,这篇手把手的解析都能给你提供一条清晰的路径。

2. 核心思路与工具准备

2.1 为什么是“MOV指令”?

在x86/x64汇编中,MOV指令(数据传送)是最常见、最核心的指令之一。当我们锁定一个感兴趣的数据(比如角色的生命值、金钱数量)时,在内存中访问或修改这个数据的代码,几乎必然包含MOV指令。例如,可能是MOV [RCX+28], EAX将EAX寄存器中的值(可能是计算出的新生命值)写入到以RCX值为基址、偏移0x28的内存地址中。我们的目标就是找到访问我们目标数据的这条关键MOV指令,然后逆向推导出这个内存地址(RCX+28)是怎么来的。RCX里的值很可能又是一个从更底层地址“读”出来的指针。这样一层层回溯,直到找到一个模块(如Game.exe)的基址加上一个固定偏移的地址,这个地址就是稳定的。

2.2 工具链与前期准备

工欲善其事,必先利其器。除了主角Cheat Engine (CE 7.5或更高版本),我们还需要一些辅助:

  • 目标程序:一个简单的、带有明确数值显示的单机游戏或演示程序(例如“植物大战僵尸”的向日葵阳光值,或一个自定义的测试程序)。选择单机是因为网络游戏通常有更复杂的反作弊机制,不适合初学者。
  • CE的基本功:你需要已经会用CE进行精确值/未知初始值扫描,找到动态地址。这是我们的起点。
  • “什么访问了这个地址”功能:这是CE最强大的动态分析功能之一,也是本次实战的核心。
  • 计算器:系统自带的计算器(程序员模式)即可,用于十六进制(Hex)和十进制(Dec)的转换与计算。
  • 耐心与笔记:寻址链可能很长,每一步的地址、偏移、寄存器值都需要记录下来,逻辑清晰是关键。

注意:所有分析请仅在合法授权的、自己拥有版权的程序或明确用于学习研究的演示程序上进行。尊重知识产权和法律法规是技术人的底线。

3. 实战第一步:定位动态地址与关键指令

假设我们的目标是修改一个单机游戏中的“金币”数量。我们首先通过CE的常规扫描,找到了存储当前金币数量的动态地址,例如0x12345678。这个地址每次启动游戏都会变化。

  1. 锁定动态地址:在CE的地址列表中,找到这个金币地址,并将其添加到下方列表。
  2. 启用访问监视:右键点击地址列表中的这个地址,选择“找出是什么访问了这个地址”。CE会弹出一个空窗口。
  3. 触发访问:切回游戏,进行一个会读取或更新金币数量的操作,比如捡起一枚金币、打开商店界面等。此时,CE的监视窗口会立即捕获到一条或多条汇编指令。
  4. 识别关键MOV指令:在捕获的指令中,寻找MOV指令。通常,我们关注的是将内存数据读入寄存器的指令,因为我们要找数据的来源。例如:
    • MOV EAX, [ESI+000005F8]— 这条指令将地址ESI+5F8处的值读入EAX寄存器。如果[ESI+5F8]正好是我们的金币地址,那么这条指令就是关键。
    • 也可能看到MOV [EDI+10], ECX这样的写入指令,这表示该指令在向某个地址写入数据。我们需要根据上下文判断哪条指令是直接操作我们目标数据的。

假设我们找到了这条关键指令:MOV EAX, [RCX+28],并且确认[RCX+28]就是我们的金币动态地址0x12345678。那么,RCX+28 = 0x12345678。由此可知,RCX寄存器中存储的值是0x12345678 - 0x28 = 0x12345650。这个0x12345650是一个新的、上一层的动态地址。我们的任务变成了:RCX里的这个值0x12345650又是从哪里来的?

4. 指令流分析与寄存器追踪

现在,我们需要知道在执行到MOV EAX, [RCX+28]这条指令时,RCX寄存器里的值0x12345650是如何被赋予的。这需要分析该指令之前的代码。

  1. 查看指令上下文:在CE的“什么访问了…”窗口中,通常可以双击那条MOV指令,或者CE提供了“显示反汇编程序”的选项,这会打开CE内置的反汇编窗口,并定位到该指令处。
  2. 向上回溯分析:在反汇编窗口中,向上滚动,查看前面的指令。我们寻找给RCX赋值的指令。常见的有:
    • MOV RCX, [RBP-30]— 从栈(RBP-30)中取值到RCX。
    • LEA RCX, [RDX+RAX*4]— 计算一个有效地址并加载到RCX。
    • MOV RCX, [0xDEADBEEF]— 从一个绝对地址0xDEADBEEF取值到RCX。如果是这样,0xDEADBEEF可能就是我们要找的上一层指针地址。
    • CALL或函数返回后,RCX可能被用作第一个参数(在x64调用约定中),其值由调用者传入。

假设我们在上方不远处看到了:MOV RCX, [R15+10]。这意味着,当前RCX的值,是从地址[R15+10]中读取出来的。那么,[R15+10] = 0x12345650

  1. 追踪R15:问题又转移了:R15的值是多少?我们需要知道R15的值,才能算出R15+10这个地址。此时,我们可以利用CE的另一个强大功能:“找出指令访问的地址”。但更直接的方法是:在反汇编器中断点处查看寄存器上下文
    • MOV RCX, [R15+10]这条指令上设置一个断点(在CE反汇编器中通常可以点击地址左侧或按F5)。
    • 让游戏继续运行并再次触发金币访问(比如再捡一次金币)。
    • 游戏会在断点处暂停。此时,CE的寄存器窗口会显示当前所有寄存器的值。我们记下R15的值,假设是0x37000000
    • 那么,[R15+10] = [0x37000000 + 0x10] = [0x37000010]。我们之前知道[0x37000010]里存的值是0x12345650
    • 所以,地址0x37000010是一个指针,它指向了0x12345650。而0x12345650+28才是最终的金币地址。

现在,我们有了一个两层的指针链:Level 1: 0x37000010-> (存放的值是)0x12345650Level 2: 0x12345650 + 0x28-> (存放的值是)金币数值

但是,0x37000010很可能还是一个动态地址。我们需要继续追问:0x37000010这个地址本身是怎么来的?

5. 偏移计算与多层指针定位

继续向上回溯分析MOV RCX, [R15+10]之前的指令,看R15是如何被赋值的。假设我们发现:MOV R15, [游戏模块.exe+0x123456]。这是一条非常关键的指令!

  • 游戏模块.exe是主程序模块,比如Game.exe。它的基址在每次程序加载时由操作系统分配,所以是变化的。
  • +0x123456是一个固定的偏移量(Offset)。
  • [游戏模块.exe+0x123456]这个表达式计算出一个绝对地址,从这个地址中读取出的值,存入了R15。

我们可以在CE中验证:

  1. 在CE主界面,打开“内存查看窗口”(Memory Viewer)。
  2. 在地址栏输入“游戏模块.exe”+123456(CE支持这种表达式)。这会计算出当前的绝对地址,例如0x50001234
  3. 查看0x50001234地址处存储的值,应该就是我们之前记录的R15的值0x37000000

那么,完整的寻址链就清晰了:

  1. 从固定的模块基址加上固定的偏移A(0x123456),得到第一层指针的地址P1 = [[Game.exe+0x123456]]
  2. P1指向的地址,加上偏移B(0x10),得到第二层指针的地址P2 = [[P1+0x10]]
  3. P2指向的地址,加上偏移C(0x28),得到最终的数据地址FinalAddr = [[P2+0x28]],这里存储着金币数值。

写成CE能理解的指针扫描格式就是:[[[“Game.exe”+0x123456]+0x10]+0x28]

5.1 使用指针扫描器验证

CE的“指针扫描器”功能可以自动化地为我们寻找多层指针链。

  1. 回到最初的金币动态地址0x12345678(这次启动游戏后的地址)。
  2. 右键该地址,选择“指针扫描器” -> “生成指针映射图”(这需要一点时间)。
  3. 生成后,打开指针扫描器,点击“指针扫描”,输入当前金币地址。
  4. 在结果中,我们可以设置过滤条件,比如“最终偏移”为0x28(因为我们知道最后一层偏移是0x28),并寻找基址是Game.exe的指针链。
  5. 指针扫描器可能会列出多条可能的链。我们需要结合之前手动分析的信息来判断:寻找一条链,其偏移序列与我们分析的一致(例如+123456 -> +10 -> +28),并且“静态地址”(基址+偏移)在游戏重启后保持不变。

找到正确的链后,我们可以将其添加到地址列表。即使游戏重启,只要这个指针链是正确的,CE就能自动解析出新的动态地址,并定位到金币数据。这就是“稳定基址”的意义——它不依赖于某次运行时的内存布局。

6. 常见问题与排查技巧实录

在实际操作中,绝不会总是一帆风顺。下面是我踩过的一些坑和总结的技巧:

6.1 指令流复杂,难以追踪

  • 问题:反汇编出来的代码跳转(JMP、CALL)很多,逻辑复杂,寄存器被频繁覆盖,难以追踪值的来源。
  • 技巧
    • 关注函数开头:给疑似函数起始的地址下断点。函数开头通常有PUSH RBP, MOV RBP, RSP等序言,参数(如RCX, RDX, R8, R9)在此时是有效的。
    • 使用“找出是什么改写了这个地址”:如果你追踪的中间指针地址(如0x37000010)本身也是被写入的,可以用这个功能找到是谁在修改它,从而逆向找到它的来源。
    • 结合数据断点:对中间指针地址设置“内存写入断点”,当该地址被写入时中断,直接看到写入的指令和来源。

6.2 指针扫描结果太多,无法判断

  • 问题:指针扫描器返回成千上万条结果,不知道哪条是正确的。
  • 技巧
    • 重启游戏:这是最有效的验证方法。记录下当前找到的疑似稳定指针链(基址和偏移序列)。关闭游戏,重新启动,再次扫描金币的新地址。然后,在指针扫描器中,使用“重新扫描指针映射”功能,并指定新的目标地址。如果之前那条指针链是正确的,它应该能再次匹配上,并且其“静态地址”部分(Game.exe+Offset)不会改变。
    • 利用手动分析信息:将手动分析得到的偏移(如+10,+28)作为过滤条件输入指针扫描器,能极大缩小范围。
    • 观察“指针计数”和“模块”:通常,正确的指针链的“指针”数量(链的长度)不会特别离谱(比如超过5层就值得怀疑),并且基址模块通常是主程序或核心DLL。

6.3 偏移量计算错误

  • 问题:在手动计算寄存器+偏移时,搞错了十六进制加减法,或者看错了指令是MOV EAX, [ECX+EDX]这种带索引寄存器的复杂情况。
  • 技巧
    • 善用CE的计算器:在内存查看窗口的地址栏,可以直接输入RCX+28这样的表达式,CE会帮你计算并跳转。这是最可靠的验证方法。
    • 理解LEA指令LEA(加载有效地址)指令是计算地址,而不是读内存。LEA RAX, [RBX+RCX*4+10]计算出的地址是RBX+RCX*4+10,这个结果被存入RAX,但并没有从该地址读取数据。它常用于数组索引计算。
    • 逐字节查看内存:在内存查看窗口中,对照汇编指令,一个字节一个字节地核对地址和数据。对于[BASE+INDEX*SCALE+DISPLACEMENT]这种复杂寻址,耐心分步计算。

6.4 基址本身是ASLR的

  • 问题:即使找到了Game.exe+0x123456,但Game.exe的基址每次启动也可能因为操作系统的地址空间布局随机化(ASLR)而不同。不过,CE的“模块基址”概念已经处理了这个问题。“Game.exe”在CE中代表的是该模块加载的当前基址,它是一个符号。只要模块不变,+0x123456这个偏移相对于模块基址就是固定的。ASLR影响的是绝对地址,但模块内的相对偏移是稳定的。

6.5 实战心得:保持清晰的记录

这是我个人觉得最重要的一点。准备一个文本文件或笔记,按以下格式记录每一步:

[时间] 游戏启动 目标数据:金币 首次找到动态地址:0x12345678 关键指令:MOV EAX, [RCX+28] @ 0xGameFunction+ABC => RCX = 0x12345678 - 0x28 = 0x12345650 上一条指令:MOV RCX, [R15+10] @ 0xGameFunction+AA0 断点处 R15 = 0x37000000 => [0x37000010] = 0x12345650 寻找R15来源... 发现:MOV R15, [Game.exe+0x123456] 验证:[[Game.exe+0x123456]] = 0x37000000 (正确) 最终指针链:[[[Game.exe+0x123456]+0x10]+0x28]

这样的记录在排查问题、重启验证时无比有用。

寻址链的挖掘就像侦探破案,从结果(数据被改变)反推原因(谁、从哪里、以何种方式改变了它)。这个过程需要对汇编指令有基本的理解,更需要耐心和细致的观察。CE工具提供了无与伦比的动态分析能力,将这个过程从纯粹的静态反汇编中解放出来,让我们能够实时地观察程序的运行状态。当你第一次通过自己手动分析,找到一条长达三层的指针链,并成功在CE中用[[[...]+offset]+offset]的形式稳定锁定目标数据时,那种成就感是巨大的。这不仅仅是学会了一个技巧,更是对程序内存模型和运行时数据结构的一次深刻理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询