适用场景:游戏开发、功能研究、学习逆向工程基础概念
难度:★★★☆☆(需要有一定基础可读)
工具:Cheat Engine 7.5(免费开源)
第零章:在开始之前你将学会什么第一步:找到 HP 的内存地址(数值扫描)第二步:找到稳定的基址+偏移(指针扫描)第三步:找到扣血的代码位置(断点追踪)第四步:把扣血代码 NOP 掉实现无敌工具下载安装时注意:取消勾选所有捆绑软件(一路 Next 的时候留意每一步)。 一个重要认知内存里全是数字。HP 是数字,坐标是数字,角色名字是数字(ASCII 编码)。
找内存地址的本质,就是在几百MB的数字海洋里,用你已知的值过滤出目标。
第一章:认识 Cheat Engine 界面启动 Cheat Engine,界面大概长这样: ┌─────────────────────────────────────────────┐│ [进程图标] ← 点这里附加到游戏进程 │├──────────────┬──────────────────────────────┤│ 搜索区域 │ ││ Value: [ ] │ 搜索结果列表 ││ [First Scan]│ (找到的候选地址显示在这) ││ [Next Scan] │ │├──────────────┴──────────────────────────────┤│ 已保存地址列表(下方) ││ Address │ Value │ Description │└─────────────────────────────────────────────┘三个核心按钮: - First Scan(第一次扫描):在全部内存里搜索
- Next Scan(继续扫描):在上次结果里继续过滤
- New Scan(重新开始):清空结果重新来过
第二章:第一步——找到 HP 的地址2.1 附加到游戏进程- 先把游戏打开,进入游戏角色
- 打开 Cheat Engine
- 点击左上角的 小电脑图标(或 File → Open Process)
- 在弹出的进程列表里找到游戏进程
- 双击它,窗口标题会变成游戏进程名
✅ 成功标志:Cheat Engine 标题栏显示游戏进程名 2.2 第一次扫描目标:搜索当前 HP 值 假设你角色现在 HP = 30000 - 在左侧 Value 输入框里输入 30000
- Value Type 选择 4 Bytes(大多数游戏 HP 用 4 字节整数存储)
- Scan Type 选择 Exact Value(精确匹配)
- 点击 First Scan
等待片刻,右侧会出现大量搜索结果(可能几万个),这是正常的。 搜索结果(举例): 0x01A3F0CC → 30000 0x02B4E120 → 30000 0x03C5D044 → 30000 ... (几万行)💡 太多结果不要慌,下一步会大量过滤。 2.3 让 HP 变化,然后继续扫描关键操作:去游戏里让角色受伤(被怪打一下),让 HP 发生变化。 假设受伤后 HP 变成 28500 回到 Cheat Engine: - Value 输入框改成 28500
- 点击 Next Scan(注意不是 First Scan)
结果会大幅减少! 上一次:几万个结果这一次:可能只剩 几百个2.4 重复过滤,直到剩下几个继续回游戏受伤,回 CE 输入新 HP 值点 Next Scan,重复 3~5 次: 第1次扫描:58000 个结果第2次扫描: 230 个结果第3次扫描: 8 个结果第4次扫描: 2 个结果 ← 差不多了🎯 目标:结果剩到 5 个以内时,可以开始验证了。 2.5 验证找到了正确地址对每一个候选地址,双击它的值,改成一个很大的数(比如 99999),然后看游戏里角色的 HP 是否变化: - 改了之后游戏 HP 变成 99999 → ✅ 这就是 HP 地址
- 没有变化 → ❌ 不是这个,改回去,试下一个
找到之后,右键 → Add to Address List,保存下来。 第三章:第二步——找稳定的基址(解决重启失效问题)3.1 为什么要找基址你刚才找到的地址(比如 0x1A3F0CC4)是堆内存里的,每次重启游戏位置都会变化。
重启后地址变成 0x2B50D0C4,上次找到的就没用了。 解决办法:找到一个固定地址,它存着一个指针,指针指向角色对象,角色对象里有固定偏移能找到 HP。 固定地址 0x00BDD6F0(每次都在这) ↓ 读出来的值(每次重启会变)0x1A3F0000(角色对象的位置) ↓ + 偏移 0xCC40x1A3F0CC4(HP 的位置)3.2 用"谁写入了这个地址"功能找到指针- 在地址列表里,右键 HP 地址 → Find out what writes to this address
- 弹出确认框,点 Yes
- 回游戏让角色受伤(让 HP 被写入)
- 回 CE,弹出的窗口会显示哪段代码修改了 HP
你会看到类似这样的汇编指令: mov [ebx+CC4], eax ; 把 eax(新HP值)写入 ebx+CC4 的位置💡 这里 +CC4 就是偏移量,ebx 就是角色对象的地址。 3.3 用指针扫描找基址- 在 HP 地址上右键 → Pointer scan for this address
- 弹出设置,默认即可,点 OK,等待扫描完成
- 扫描结果会显示很多指针链,格式类似:
"MapleStory.exe"+BDD6F0 → 偏移 CC4 → HP地址"MapleStory.exe"+BDD6F0 → 偏移 CC4 → HP地址(可能有很多重复的,找模块名+偏移的那种)- 带有 "MapleStory.exe"+BDD6F0 格式的,就是基址
✅ "MapleStory.exe"+BDD6F0 表示:程序基址 + 偏移 BDD6F0,这个是固定的! 3.4 验证基址是否稳定- 重启游戏,重新附加 CE
- 用找到的指针链算出 HP 地址:读 [程序基址 + BDD6F0] 得到角色对象,再加 CC4
- 如果还能读到 HP → ✅ 基址找对了
在 CE 的地址列表里,可以这样填写: Address: "MapleStory.exe"+BDD6F0Type: 4 Bytes☑ Pointer Offsets: CC4 第四章:第三步——找扣血代码这一步是实现无敌的关键:找到是哪段代码在扣血,把它屏蔽掉。 4.1 在 HP 地址上设置断点- 在地址列表里,右键 HP 地址 → Find out what writes to this address(和之前一样)
- 弹出的窗口等待有东西写入
- 回游戏受伤
- 窗口出现一条记录,双击它
4.2 看到扣血的汇编代码双击后会打开反汇编窗口,高亮的一行就是扣血指令,类似: 地址 指令---------- ----------------------------------------00A5B3C2: sub [ebx+CC4], eax ; HP -= 伤害值(这就是扣血)00A5B3C8: cmp [ebx+CC4], 0 ; 检查 HP 是否为 000A5B3CE: jle 00A5B420 ; 如果 HP<=0 跳转到死亡处理💡 sub 是减法指令,sub [ebx+CC4], eax 意思是:HP = HP - 伤害值 4.3 NOP 掉扣血指令(实现无敌)NOP = No Operation,什么都不做。把扣血指令替换成 NOP,就等于把扣血逻辑删除了。 - 点击那行 sub [ebx+CC4], eax 指令
- 右键 → Replace with code that does nothing(Fill with NOPs)
- 回游戏受伤测试
原来:sub [ebx+CC4], eax → HP 减少改后:90 90 90 90 90(NOP)→ 什么都不做,HP 不变✅ 如果受伤后 HP 不再减少,无敌功能完成! 4.4 记录下这个代码地址这个代码地址(比如 0x00A5B3C2)就是代码段的固定地址,可以写进代码里: // 扣血指令地址const int ADDR_HP_DECREASE = 0x00A5B3C2;// 无敌开启:NOP 掉扣血指令(5字节)byte[] nop = { 0x90, 0x90, 0x90, 0x90, 0x90 };// 无敌关闭:还原原始指令byte[] original = { 0x2B, 0x83, 0xC4, 0x0C, 0x00 }; // 原始字节 第五章:完整流程回顾┌─────────────────────────────────────────────────────┐│ 目标:找到 HP 地址并实现无敌 │└─────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ 第一步:数值扫描 ││ 当前HP=30000 → 扫描 → 受伤HP=28500 → Next Scan ││ 重复3~5次 → 剩下2~3个结果 → 改值验证 → 找到HP地址 │└─────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ 第二步:指针扫描 ││ HP地址 → Pointer Scan → 找到基址+偏移 ││ 格式:[程序名.exe + 基址偏移] + CC4 = HP ││ 重启验证稳定性 │└─────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ 第三步:断点追踪 ││ HP地址 → "写入断点" → 受伤触发 → 看到扣血指令 ││ 记录代码地址 + 原始字节序列 │└─────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ 第四步:NOP 替换 ││ 把扣血指令替换为 90 90 90...(NOP) ││ 验证受伤后 HP 不减少 → 完成 │└─────────────────────────────────────────────────────┘ 第六章:常见问题Q:扫描结果一直很多,减不下来?
A:确认 Value Type 选对了(HP 通常是 4 Bytes,不是 2 Bytes 或 Float) Q:找到的地址,改了之后游戏没反应?
A:可能是"显示用的 HP"而不是"实际 HP"。服务端权威的游戏,改客户端内存只改显示,服务端仍然扣血。 Q:NOP 掉扣血后角色还是死了?
A:游戏可能有多处扣血逻辑(比如毒伤、技能伤害走不同代码路径),每条路径都要找到并 NOP。 Q:重启游戏后代码地址会变吗?
A:代码段地址(.text 段)通常固定,不随重启变化。但如果游戏开启了 ASLR(地址随机化),每次都会变。MapleStory 老版本一般没有 ASLR。 Q:这些字节怎么还原回去?
A:在 NOP 之前,先把原始字节记录下来。可以在反汇编窗口看到每条指令对应的十六进制字节序列。 附录:常用汇编指令速查指令含义例子
mov赋值mov eax, 100 → eax = 100
sub减法sub [addr], eax → 内存值 -= eax
add加法add [addr], eax → 内存值 += eax
cmp比较(不改值)cmp eax, 0 → 比较 eax 和 0
jle小于等于则跳转jle 地址 → 如果上次比较 ≤ 则跳
jmp无条件跳转jmp 地址 → 直接跳
7E xxJLE 的机器码改成 EB 变 JMP(攻击不停原理)
90NOP 的机器码什么都不做
|