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

上一章我们实现了植物无CD的功能,主要使通过定位CDTimers间接定位到了if判断的位置,并通过使用nop填充jle指令,改变了程序的执行流程,使其总是运行植物不处于CD状态中的代码

现在我们希望实现另一个功能,即在同一个位置能够重复的放置植物

同一格重复放置植物

思路

  1. 思路一:我们假设放置植物是通过一个二维数组记录的,当二维数组中记录如-1之类的值时,代表位置空闲,可以放置植物,当数组中记录植物编号时,代表位置被占用,不可放置,该思路需要我们能定位到该二维数组,在放置植物时保证二维数组中的值不变,使该位置为空位置

  2. 思路二:在我们放置植物时,我们需要一个if判断,用于判断该位置是否可放植物,若可放置,则阳光会减少放置植物所需的cost,即该思路需要我们定位一个cmp指令,即

1
2
3
if(可以放置植物){
阳光减少
}
  1. 通过对比我们可以发现思路二更简单,且方法与上一章实现植物无CD相同,同时,阳光在内存中的位置,我们在第一章中也已经定位到,因此使用思路二能使我们更快的定位到CMP位置,

定位是否可以放置跳转

  1. 我们已经知道,若在一个空位置放置植物,阳光会减少相应的cost,因此我们在阳光的内存处下一个内存写入断点,之后在一个空位放置向日葵,可以发现CE显示了一条指令,我们双击该指令观察esi的值,发现其中的值正是放置向日葵后的阳光数

  1. 我们记下该指令的地址,用x64Dbg附加游戏,找到该指令并在此处下一个断点,在游戏中再放一个向日葵,使游戏执行到该处,我们可以看到该指令之后将会返回至另一个位置,在堆栈窗口中,我们可以看到其返回的具体地址

  1. 跟进到返回后的位置,我们将该call假设为阳光减少call,我们发现该call周围有大量的跳转指令,且整个代码段中都有大量跳转指令,因此我们无法判断代码段中的哪个跳转指令,此时我们需要一个方法来筛选哪些跳转指令是我们需要关注的

  1. 一个可行的方法是,我们向上找到该代码段的起始位置,并下一个断点,在游戏中我们先在空闲位置放置一个向日葵,此时程序会断在代码段起始位置,我们单步调试,在每一个跳转指令处记录其是否跳转,以0标记不跳转,1标记跳转,直至该代码段结束
2022-03-01-空闲跳

之后我们返回游戏,选择向日葵,将其放置在一个有植物的位置,再次单步调试跟进每一个跳转指令,寻找发生变化的跳转指令,之后我们定位到该跳转指令发生了变化

  1. 为了印证该跳转是不是决定了该位置为空位置,我们先在此处下个断点,然后修改其执行流程,因为此次放置非空位置,若修改后可以在同一位置重复放置植物,则成功,若不能重复放置,方便我们从此处再对后续的跳转指令进行对比,这里我们将ZF标志位置为1,je指令从不跳转变为跳转

  1. 运行游戏,此时我们发现向日葵重复放置在了同一位置,因此该跳转使得我们可以实现重复放置的功能,即跳转则可放,不跳转则不可放,且通过分析反汇编我们不难得出,跳转指令之前的call实现了判断位置是否为空的功能

  1. 如果要让跳转指令一直跳转,我们可以将其改为jmp无条件跳转指令,此时回到游戏我们就已经实现了重复种植的功能

实现重复放置功能

1
2
3
4
5
6
7
8
9
10
// JMP 0x00410754的硬编码,硬编码的操作码部分为偏移地址
buffer[0] = 0xE9;
buffer[1] = 0x20;
buffer[2] = 0x09;
buffer[3] = 0x00;
buffer[4] = 0x00;
buffer[5] = 0x90;

// 填充至原je跳转指令处
WriteProcessMemory(hProcess_, (LPVOID)0x0040FE2F, (LPCVOID)buffer, sizeof(buffer), &pid_)