逆向之植物大战僵尸(二)

回顾一下上一章实现无限阳光的方法,我们首先猜想有一个变量存储了当前阳光的值,即在内存中有一块空间存储了阳光的值,只需要找到该空间,再将期望的阳光数写入该空间即可实现无限阳光的功能,通过CE多次搜索阳光,我们最终通过基址 + 2次间址的形式定位到了阳光的具体地址

取消植物放置CD

思路

现在我们实现取消植物放置CD的功能,可以猜想有一个变量存放了某个植物的放置CD,而另一个变量存放了CD计时器,然后通过一个函数判断,当CD计时器从0开始增长到大于等于CD时CD计时器从CD值开始减少到0时,可以再次放置植物,即

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int plantCD = xxx
int CDTimers = 0

// CDTimers增加
if(CDTimers >= plantCD){
可以放置植物;
}

// or

// CDTimers减少
CDTimers = plantCD
if(CDtimer <= 0){
可以放置植物;
}

在此猜想上我们不难发现两种实现思路:
1. 方法一是置植物CD为0
2. 方法二是让if判断能一直成立

对于方法一,因为每个植物都有其独立的CD,要搜索每一个植物的CD值工作量相对较大,且我们并不知道植物CD的具体数值,在搜索中也较为困难,且在搜索到植物CD后还需要再定位其静态基址

对于方法二,无论植物CD是多少,能否放置植物都依赖于函数的判断,如果我们能定位到该if判断的位置,那么我们就能通过修改程序执行的流程,使其无条件的执行可以放置植物的代码

因此我们的思路就是: 首先找到CDTimers,然后定位if判断的位置,修改程序执行的流程

寻找CDTimers

  1. 因为我们无法确定CDTimers的初始值,所以在CE的搜索方式中我们选择未知初始值,并点击First Scan

  1. 我们以向日葵作为对象,此时放置向日葵,并选择changed value,然后再次搜索

  1. 我们发现CE得到了非常多的搜索结果,此时游戏处于暂停阶段,CDTimers并未改变,所以我们可以在CE中选择unchanged value,并多次搜索过滤大量无关数据

  1. 此时搜索结果减少的量已经很少了,此时我们让游戏继续一段时间,此时CDTimers的值改变,因此再CE中再次选择changed value并搜索

  1. 我们重复步骤3和4,直至搜索到少量结果(向日葵CD结束可再放置一次重复搜索)

  1. 我们发现最后一个结果的值172比较符合我们的猜想,并发现在游戏进行时,该值不断增大,此时向日葵处于不可放置的状态,那么该值是否会增大到某一个界限后导致向日葵可以重新安放呢,为印证猜想,我们在此处将该值改为1000

我们发现此时向日葵恢复了可放置的状态,猜想正确,该值就是CDTimers

定位If判断的位置

  1. 我们知道当植物处于不可放置状态时,CDTimers的值会不断增加,因此我们可以得知必定有一条指令像该内存写入了数据,因此我们可以对CDTimer下一个内存写入断点

  1. 此时我们选择show disassembler可以看到如下内容,我们发现该值增加后存入了eax寄存器,而之后eax与内存中[edi+0x24]处的值进行了对比,我们猜测该CMP指令就是我们要找的if判断指令,我们记下该CMP指令的地址

  1. 用x64Dbg附加植物大战僵尸进程(若附加失败可关闭CE后再尝试附加),之后我们在x64Dbg中通过地址找到CMP指令,并在该处下断点,是程序运行到CMP指令处

  1. 此时我们单步执行一次,发现跳转指令jle执行,而此时向日葵是处于无法安置状态的,如果该CMP就是我们需要的if判断,那么可以推测出,若跳转执行,则植物仍在CD,不可放置;若跳转不执行,则植物没有CD,可以放置

  1. 为验证猜想,我们可以改变程序的执行流程,即让jle指令不跳转,因为jle跳转与否是由标志寄存器(ZF == 1 || SF != OF)决定,此时SF != OF,跳转执行,因此我们将SF的值该为0,此时跳转就不会执行,之后我们运行程序,可以观察到植物的CD取消

  1. 此时我们可以确定该CMP就是我们需要的if判断,而其结果决定了jle指令是否跳转,若我们让jle指令永不跳转,那就实现了植物放置无CD的功能,因此我们在x64Dbg中将jle指令用nop填充并取消断点,回到游戏中,我们发现放置植物之后将不会有CD,功能实现成功

实现取消CD功能

其过程就是用代码的形式将jle指令用nop填充

1
2
3
4
5
6
// nop指令的机器码,保证与原jle指令长度相同
buffer[0] = 0x90;
buffer[1] = 0x90;

// 在jle指令地址处,用nop填充
WriteProcessMemory(hProcess_, (LPVOID)0x00487296, (LPCVOID)buffer, sizeof(buffer), &pid_)