冒险岛079

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 1|回复: 0

Day 9 -- 敌我识别:当你和怪物共享同一段代码

[复制链接]

701

主题

13

回帖

2235

积分

管理员

积分
2235
发表于 3 小时前 | 显示全部楼层 |阅读模式
Day 9 -- 敌我识别:当你和怪物共享同一段代码
Cheat Engine 从入门到住院 · Day 9

Day 5 我们学 NOP 的时候埋过一个伏笔:NOP 掉扣血代码后,不仅你不掉血,敌人也不掉血了。

这不是 bug,这是程序设计的常见模式——玩家和敌人用的是同一段扣血代码。程序不会为你和每只怪物分别写一个
  1. 扣血函数()
复制代码
,那太浪费了。它只写一个通用的函数,通过参数区分对谁操作。

所以问题来了:怎么修改代码,让它只对你免疫,对敌人照常生效?

━━━━━━━━━━━━━━━━━━━━

本文你将学到

  • 共享代码的产生原因
  • 如何区分"谁在被扣血"
  • 通过结构体比较进行敌我识别
  • 条件注入脚本的编写
    阅读时间:15 分钟 | 实操时间:35 分钟 | 难度:进阶

    ━━━━━━━━━━━━━━━━━━━━

    CE Tutorial Step 9:区分玩家和敌人

    进入 Step 9。这一步有你的角色和多个敌人,每个都有血量。

    任务:让你的角色不掉血(或者加血),但敌人正常掉血。

    如果你直接 NOP 掉扣血代码,所有人都不掉血,过不了关。

    ━━━━━━━━━━━━━━━━━━━━

    为什么代码是共享的

    在面向对象编程中,玩家和敌人通常是同一个类的不同实例:
    1. class Entity:
    2.     def __init__(self, name, health):
    3.         self.name = name
    4.         self.health = health
    5.     def take_damage(self, amount):
    6.         self.health -= amount    # 只有这一行代码
    7. player = Entity("Player", 100)
    8. enemy1 = Entity("Goblin", 50)
    9. enemy2 = Entity("Dragon", 200)
    复制代码
    1. take_damage
    复制代码
    只有一份代码,但通过
    1. self
    复制代码
    参数知道操作的是哪个实例。

    在汇编层面,
    1. self
    复制代码
    就是一个指向结构体的指针(通常存在某个寄存器里)。不同的实例有不同的结构体地址。

    ━━━━━━━━━━━━━━━━━━━━

    核心思路:比较结构体地址

    每次扣血代码执行时,某个寄存器里存着"当前被操作对象"的结构体地址。

  • 当这个地址指向你的角色 → 不扣血(或加血)
  • 当这个地址指向敌人 → 正常扣血

    所以关键问题变成了:怎么知道你的角色的结构体地址是什么?

    ━━━━━━━━━━━━━━━━━━━━

    实操步骤

    第一步:分别找到你和敌人的血量地址

    用精确搜索分别找到:
  • 你的角色的血量地址(比如
    1. 0x0A001018
    复制代码

  • 至少一个敌人的血量地址(比如
    1. 0x0B002018
    复制代码


    第二步:找到扣血代码

    对你的血量地址做 Find out what writes to this address

    点击被攻击的操作,找到扣血指令,比如:
    1. sub [rbx+18], eax
    复制代码

    这里
    1. rbx
    复制代码
    就是结构体指针,
    1. +18
    复制代码
    是血量在结构体中的偏移量。

    第三步:确认共享代码

    对敌人的血量地址也做 Find out what writes to this address

    攻击敌人,你会发现出现的指令地址和你的一模一样——同一条
    1. sub [rbx+18], eax
    复制代码


    这就证实了:扣血代码是共享的,区别仅在于执行时
    1. rbx
    复制代码
    的值不同。

    第四步:记录你的结构体地址

    当 CE 记录到你被攻击时的指令信息,在详情中可以看到当时
    1. rbx
    复制代码
    的值,比如
    1. 0x0A001000
    复制代码


    也就是说:
  • 你的结构体地址 =
    1. 0x0A001000
    复制代码
  • 你的血量 =
    1. [0x0A001000 + 0x18]
    复制代码
    = 地址
    1. 0x0A001018
    复制代码

    第五步:编写条件注入脚本

    打开反汇编视图,对扣血指令按 Ctrl+A,使用 Full Injection 模板,然后修改:
    1. [ENABLE]
    2. alloc(newmem, 2048)
    3. label(returnhere)
    4. label(originalcode)
    5. label(exit)
    6. // 存储玩家的结构体地址
    7. alloc(playerbase, 8)
    8. newmem:
    9.   // 比较当前 rbx 是不是玩家
    10.   push rax
    11.   mov rax, [playerbase]
    12.   cmp rbx, rax
    13.   pop rax
    14.   je isplayer            // 如果是玩家,跳到特殊处理
    15.   jmp originalcode       // 如果是敌人,执行原始代码
    16. isplayer:
    17.   add [rbx+18], 2        // 玩家:加血
    18.   jmp exit
    19. originalcode:
    20.   sub [rbx+18], eax      // 敌人:正常扣血
    21. exit:
    22.   jmp returnhere
    23. // 记录玩家结构体地址
    24. playerbase:
    25.   dq 0x0A001000          // 填入你的结构体地址
    26. "Tutorial-x86_64.exe"+XXXXX:
    27.   jmp newmem
    28.   nop
    29. returnhere:
    30. [DISABLE]
    31. dealloc(newmem)
    32. dealloc(playerbase)
    33. "Tutorial-x86_64.exe"+XXXXX:
    34.   sub [rbx+18], eax
    复制代码

    核心逻辑就是一个
    1. cmp
    复制代码
    (比较)+
    1. je
    复制代码
    (条件跳转):
    1. 如果 rbx == 玩家地址:
    2.     加血
    3. 否则:
    4.     正常扣血
    复制代码

    第六步:执行并验证

    执行脚本后,攻击敌人——敌人正常掉血。你被攻击——血量反而增加。

    完美的敌我识别。

    ━━━━━━━━━━━━━━━━━━━━

    更优雅的方案:用 CE 的结构体分析

    上面的方法有一个问题:玩家的结构体地址是硬编码的。如果游戏重启,地址变了,脚本就失效了。

    更好的方法是通过结构体中的某个特征来区分。比如:

  • 玩家的名字是 "Player",敌人的名字是 "Enemy"
  • 玩家的 teamID 是 1,敌人的 teamID 是 2

    CE 的 Structure Dissect 工具可以帮你分析结构体的布局,找到可以用来区分的字段。

    用结构体特征做判断的脚本:
    1. newmem:
    2.   // 假设结构体偏移 0x04 处存的是 teamID
    3.   cmp dword ptr [rbx+04], 1    // teamID == 1 ?
    4.   je isplayer
    5.   jmp originalcode
    6. isplayer:
    7.   add [rbx+18], 2
    8.   jmp exit
    9. originalcode:
    10.   sub [rbx+18], eax
    11. exit:
    12.   jmp returnhere
    复制代码

    这种方案不依赖硬编码地址,更加健壮。

    ━━━━━━━━━━━━━━━━━━━━

    Structure Dissect 快速入门

    Structure Dissect 是 CE 的结构体分析工具,可以查看一个内存地址附近的数据布局。

    基本用法:

  • 在 CE 中打开 Memory View
  • 菜单 Tools → Dissect Data/Structures
  • 输入结构体地址(比如你的角色的基地址
    1. 0x0A001000
    复制代码

  • CE 会显示从这个地址开始的连续内存数据,并尝试猜测每个偏移量处的数据类型

    你可以在这个视图中看到:
    1. 偏移 +00: 某个值(可能是指向虚表的指针)
    2. 偏移 +04: 1(这可能是 teamID)
    3. 偏移 +08: 某个浮点数(可能是 X 坐标)
    4. 偏移 +0C: 某个浮点数(可能是 Y 坐标)
    5. ...
    6. 偏移 +18: 100(血量)
    复制代码

    对比你的角色和敌人的结构体,找出不同的字段,就能用来做敌我识别。

    ━━━━━━━━━━━━━━━━━━━━

    常见问题

    Q:怎么确定哪个寄存器是结构体指针?

    A:看扣血指令。
    1. sub [rbx+18], eax
    复制代码
    中,
    1. rbx
    复制代码
    就是结构体指针。方括号里的寄存器就是。有时候是
    1. rsi
    复制代码
    1. rdi
    复制代码
    1. rcx
    复制代码
    等。

    Q:玩家和敌人的结构体偏移量不一样怎么办?

    A:那它们可能不是同一个类的实例,扣血代码也不会是共享的。这种情况下你只需要修改影响玩家的那个代码即可。

    Q:游戏里有多种敌人,需要分别处理吗?

    A:通常不需要。你只需要识别出"是不是玩家",不是玩家的一律走原始代码就行。除非你想对不同敌人做不同的修改。

    ━━━━━━━━━━━━━━━━━━━━

    小结

    今天我们解决了 CE 修改中最经典的难题——共享代码的敌我识别:

  • 游戏通常让玩家和敌人共用同一段代码
  • 区分方式是比较结构体指针或结构体中的特征字段
  • 用条件跳转实现"如果是玩家则特殊处理,否则正常执行"
  • Structure Dissect 工具可以帮助分析结构体布局

    恭喜!你已经通关了 CE Tutorial 的全部 9 个步骤。但 CE 的能力远不止这些。

    接下来三天,我们将离开 Tutorial 的舒适区,学习三个实战利器:指针扫描(Day 10)、Auto Assembler 进阶(Day 11)、Lua 脚本(Day 12)。

    从 Tutorial 靶场走向真实世界,准备好了吗?
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    果子博客
    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-3-29 20:21 , Processed in 0.213396 second(s), 19 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表