植物大战僵尸变态辅助开发系列教程(E语言实现和VC6实现)(中)
- 26、第一种方法实现变态加速功能
- 27、第二种方法找出变态攻击加速的方法
- 28、加快阳光、金币生产速度
- 29、全屏僵尸
- 29、全屏减速第一课
- 30、全屏减速第二课
- 31、全屏奶油的找法
- 32、全屏减速的实现
- 33、全屏奶油的找法
- 34、全屏减速、奶酪的选择性实现
- 35、CALL寻找植物攻击力
26、第一种方法实现变态加速功能
由于豌豆射手的攻击间隔不是很明显,所以咱们用西瓜来找植物攻击的间隔范围,当西瓜往下压的时候间隔值是最大的。
咱们得先把僵尸的血给找到,找到后把值改大一些,这样才不会很轻易的被植物灭掉。
先搜索270(这个值是僵尸的血量),当僵尸被豌豆射手打了以后,在搜索250:
咱们给它锁住。
减慢游戏速度,等西瓜往下压的时候间隔的数值肯定是最大的,先搜索未知的初始数值,然后回到游戏中在西瓜弹出来之前(有投掷的动作),搜索减少的数值:
不断回到游戏,再回到CE搜索减少的数值,反复这样操作:
但是,当西瓜投出来了以后,就不要搜索减少的数值了,这样分析出来的数据是不对的,等它准备再有下压投掷动作的时候,回到CE中搜索增加的数值:
咱们再过滤一下搜到的数值,植物攻击范围和植物攻击动作的范围一般是一样的,这个数值肯定不会很大,所以比3000小的数值都可以删除了:
然后回到游戏中,观察CE这几个地址的数值,看哪个不断减少接近0:
上图选中的是攻击间隔,我们不要,删除它。
当这个数值是1的时候,炮弹投掷出去了,当西瓜一填弹、压了一个西瓜上来,这个数值就变成0了。
我们看到西瓜一往下压这个投掷器,这个数值就有值了,并开始倒数:
扔出去以后这个值就变为1了,等投掷器上一有西瓜,该值就变为0了:
咱们把这个地址复制一下,解除CE附加,到OD中转到该地址:
按两下回车,让该命令生效,在内存窗口该地址处下一个硬件写入断点:
程序会断到:
如上图所示点击右上方的WBP按钮,把硬件断点删除。
我们看到EDI的值为0C9143D8,我们把这个值复制到CE中验证一下:
可以看到这两个地址是一样的。
咱们把这句指令改为:
可以发现它一直投不出来西瓜,这是怎么回事呢?
当eax为1的时候,执行到下面它会被减1,就变成0了,我们之前测试过,只有当它为1的时候这个西瓜才会投弹。
咱们给它如上修改,让eax的被下面指令减1后一直为1,然后我们再看一下效果:
但是有个问题就是它一直为1的话它就会不停的攻击,甭管有没有僵尸它都攻击,所以应该还有一个判断。
回到游戏,等出现僵尸后,在同一行上种植西瓜,然后回到OD中下断:
断下来了以后咱们按F8往下走:
这里有一个对比,判断这个倒计时是不是为1:
这个jnz跳转就是往上跳转到:
这就是这么一个攻击的循环,咱们怎么改呢?咱们先把断点删除:
咱们如果给它改成JMP的话,子弹还是发射不出来:
咱么就不让它往回跳了,给它NOP掉:
现在我们可以看到,这个动作只执行了一次,但是它一次发射了很多炮弹,这个就是一种更为变态的攻击加速。
27、第二种方法找出变态攻击加速的方法
咱们还是用老方法,在CE中分析这些CMP指令所在的地址:
改成JMP后发现子弹发射不出来了,还原回去后用NOP空指令替换:
成功实现想要的效果了。
复制该关键地址,到OD中转到该地址:
可以看到用这种方法找到的地址,正好也是上节课我们调试跟踪分析出来的地址。
28、加快阳光、金币生产速度
咱们搜索产生的间隔,在OD里面把这个间隔倒数的时候给它加快就可以了。
然后这个时候它会进行倒数(等待产生阳光),然后咱们搜索减少的数值:
不断的进游戏,再切换回CE,点击再次扫描减少的数值,在阳光产生前反复进行这种操作:
等出现了一个阳光后,咱们再搜索增加的数值:
第一个地址的数值肯定不对,因为它是一个倒数的数值,不会那么大,所以只看下面两个地址:
第一个地址修改后没有效果,咱们改第二个的数值为1,发现立刻就会出现阳光:
咱们给它修改为1后锁定住,会发现出现了一大堆的阳光:
给它改成5或者10锁定也还产生一大堆阳光,改成100的话就不产生阳光了。
咱们把这个地址先记录下来,从OD里面转到这个地址并下断:
它就是不停的减1,咱们把EDI的值复制下来到CE里面看看这是个什么数值:
这个地址跟我们刚才在CE中搜索到的地址一样,把它的数值改为1就立刻产生一个阳光,所以咱们可以在OD中把减去的数值1改大一些:
我们发现生产阳光的速度就比较快了,咱们再改大点:
发现不能这样改,字节数不对了,还原回去。
如果想把这个数值改大点的话(比如-100、-128),可以找一处空白位置,将该地址处的指令复制过去并将减数改为-128,并在后面加一条跳回去的指令,将该地址改为跳转到新位置的指令就可以了。
这里我们就改成0x64就够用了。
记录一下修改过的字节集。
并记录正常的字节集:
29、全屏僵尸
全屏僵尸就是让僵尸一下子全跑出来,那么这该怎么搜索呢?
咱们先搜索僵尸的数量,然后找到数量增加的关键指令,下断点找到创建僵尸的关键CALL,在这个CALL往上找,找到创建僵尸相关的判断,再做修改就可以了。
用CE将游戏速度设置为5加速进度,等出现一只僵尸后搜索数值1:
干掉一只僵尸后再搜索数值1:
现在出现3个僵尸,那肯定是第2个地址,保存当前僵尸数量的地址,它的数值肯定是动态的,不可能是静态的。
我们找阳光的地址来和僵尸数量这个地址对比一下:
可以看到这两个地址比较接近,都差不多,所以肯定就是僵尸的数量,右键附加CE的调试器找出是什么改写了这个地址:
咱们不要杀死僵尸,等待创建僵尸的时候得到的指令,又创建了2只僵尸:
这个地址就是僵尸数量增加的关键地址,记录一下:
咱们关掉CE,把OD打开,转到该地址:
点一下工具栏上的k键打开调用堆栈窗口:
咱们把每个CALL都标记一下,并给它下个断点,并把僵尸数量增加的断点删除:
点击运行后直接就断下来的肯定不是,只有在僵尸增加的时候断下来的才是正确的地址。
第2、5、4都不是,现在就剩第3层了,然后咱们测试这一层CALL,咱们把CE打开,加速游戏,发现在僵尸增加的时候果然断到了这个CALL;
既然这个是创建僵尸的CALL的话,咱们给它NOP掉,应该就不会创建僵尸了;
我们发现过了好长时间也不增加僵尸,咱们给指令恢复一下,就又开始创建僵尸了。
咱们ctrl+A分析一下代码,然后继续往这个CALL上面分析,发现有一个jnz跳转,它上面有一个判断,当判断不为0的话就会跳转,直接就跳走了,就不会执行创建僵尸的CALL了,咱们要让它创建的话就不能让它跳转,所以咱们就把这个跳转给它NOP掉(用空指令填充),不让它跳走,然后发现僵尸就一下全出来了。
咱们记录一下修改后的字节集,恢复修改的代码后,再记录一下正常的字节集。
29、全屏减速第一课
找数据的话也有两个方法。
冰冻效果的这种减速,是一个倒计时,到一定程度以后才恢复正常的状态;
咱们先把僵尸的血量找到并给它锁定上。
回到游戏打一下僵尸后返回CE如下搜索(豌豆的攻击力是20):
回到游戏铲除冰冻豌豆,在僵尸仍处于冰冻状态时,回到CE中搜索未知的初始数值:
然后回到游戏,再返回CE,这是冰冻效果倒计时又减少,所以继续搜索减少的数值:
回到游戏,冰冻效果减少时间,再回到CE继续搜索减少的数值,这样反复操作:
如果冰冻效果消失了的话,再种上一个冰冻豌豆,让僵尸继续被冰冻,然后CE中只进行一次搜索增加的数值:
返回游戏,再返回CE,继续搜索减少的数值:
前面4个地址比00400000小,都是堆栈地址,删除它们;
我们发现改成1或者0,冰冻效果并没有消失,说明没找对(可能刚才僵尸已经恢复正常后,我多搜索了一次减少的数值导致的)。
我们还是按照刚才的方法再重新搜索一下。
现在僵尸变了,咱们再搜索变动的数值:
铲除冰冻豌豆,返回CE搜索减少的数值:
还是之前的操作,返回游戏,等1、2秒,返回CE继续搜索减少的数值,重复这样操作:
多次这样操作后,估摸着冰冻效果快消失的时候,就要在游戏里等冰冻效果消失后,再种植一个冰冻豌豆,冰冻一下僵尸,然后返回CE搜索一次增加的数值:
咱们直接把上图这个883改成1看看:
我们发现冰冻效果立刻消失,而且数值变成了0,我们发现0的话就是正常的状态,如果我们给这个地址写入一个数值999的话:
这个数值就会倒数,就是冰冻状态的倒计时,当这个倒计时为0的时候,冰冻效果消失。
种一个冰冻豌豆,击中僵尸以后会出现两个指令:
我们观察发现,esi和edi的地址值都一样,而且我们看到这两条指令前面的计数,第一条指令是1,第二条指令是19,第二条指令是倒数的:
我们看到这两条指令前面的计数,说明第一条指令记录的是攻击了几次,第二条指令在倒数;
我们把找到的地址的数值改为500:
回到游戏中,我们观察到,在射出炮弹还没碰到僵尸的时候,这个500数值会不断地减少;
而当炮弹攻击到僵尸以后,这个数值会变成比500大的数值(从1000开始减少):
所以正常来说咱们应该选择第一条汇编指令,记录一下该指令的地址:
在CE中解除附加游戏,到OD中附加该游戏,转到该地址处:
实现功能我们在下节课再讲。
30、全屏减速第二课
咱们先搜索僵尸的血量,这种僵尸的血量是270:
种一个冰冻豌豆,打中了僵尸两下,还剩下230:
找到后锁住血量,然后右击该地址,附加CE调试器,找出是什么改写了这个地址:
然后回到游戏让炮弹打中僵尸,出现一条汇编指令:
这个是减血,我们看反汇编代码往上看,找到sub之类的减的指令:
EDI的值0xD2的十进制是210,正好是僵尸当前的血量(由于我们把血量锁定住了,所以需要先取消,才能更好的观察僵尸血量变化):
EDI的值0xBE的十进制是190,正好是这个僵尸当前的血量,而这个指令是mov传送指令,咱们把EBP的值0C561AE8复制,手动加入地址:
这个地址正好保存的是僵尸的血量。
把这条汇编指令的地址记录一下:
血量那个地址如果不加C8的话我们看看它是什么:
我们看到0C561AE8这个地址保存的数据跟基址006A9EC0保存的数据是一样的,所以0C561AE8这个地址可以作为这个数组的起始位置,可以暂时理解为僵尸的临时基址(当然这种说法是不正确的,暂时先这样理解);
咱们把0C561AE8这个地址作为内存扫描选项的起始地址:
观察这块内存区域的变化,跟之前操作是一样的:
给僵尸的血量改大一些:
把这个地址复制一下,粘贴到内存扫描选项的结束那里:
然后回到游戏,在射出炮弹还没打住僵尸的时候,继续搜索未变动的数值;
再回到游戏,炮弹打住僵尸变成冰冻状态了以后,搜索变动的数值:
我们把它的数值改为1,僵尸的冰冻状态就消失了,恢复为正常状态了:
僵尸一挨冰冻炮弹,这个数值就又变为从1000开始倒数了:
如果我们把这个地址锁定了以后,它就永远是冰冻状态了;锁定为0的话,就是冰冻豌豆打中它,它也会立刻恢复为正常状态(CE不断的写入0)。
这种方法比较重要,在以后分析数据的时候,这种方法可以分析出很多相关的数据;
前面讲的自动收集阳光也是用这种方法来找到相关数据的;
包括玉米投手(投出的奶油打中僵尸以后,僵尸会不动了),打中以后它的数据也会一直减少,所以用这种范围搜索方法,根据僵尸的基址,找到这个范围,搜索变动的数值,也是一样。
31、全屏奶油的找法
这节课复习上节课讲过的范围搜索状态,找玉米投手的奶油定住僵尸的状态相关数据。
先找到僵尸的血量,等僵尸出现以后,搜索270,种植一个豌豆,打僵尸一下,再铲除这个豌豆,搜索250:
我们上节课说了,僵尸的那个临时基址加上C8就是僵尸的血量,咱们把僵尸血量的地址搜到了以后,直接减去C8就是僵尸临时的那个基址,可以作为数组的起始地址用来分析游戏数据;
所以咱们不必每次都附加CE调试器,找出是什么改写了这个地址,咱们只要记住偏移量就可以了,偏移量才是固定的。
咱们把僵尸当前血锁定,并再复制一份,并将地址减C8,得到的就是当前这只僵尸的临时基址:
现在变化的是上图这几个位置,然后咱们种植两个玉米投手,因为只有投出大个的奶油才能定住僵尸:
当大个的奶油投出去了以后,尤其当大个奶油定住了僵尸以后,咱们注意观察内存区域的变化:
咱们把这个地址复制一下,然后把游戏中的玉米投手都铲除掉,然后粘贴到内存扫描选项中的结束那里,起始那里需要手动输入僵尸当前的临时基址:
在没种植玉米投手的时候,搜索未变动的数值,种植1个玉米投手后,再次搜索未变动的数值,再种植两个玉米投手,在投出大个奶油定住僵尸后,搜索变动的数值:
回到游戏中,铲除3个玉米投手,回到CE发现数值一直在不断地减少,直到为0,僵尸恢复正常:
咱们给数值改为555:
再改成999,这个僵尸就走不动了:
这个地址怎么来的呢?咱们用这个地址减去基址0C55DAE8,得到B0:
咱么可以再找另外一只僵尸:
回到游戏让豌豆打中它一下:
这个血量地址减去C8就是当前这只僵尸的基址,然后加上B0,就是当前僵尸是否被奶油击中状态的地址,咱们把这个数值改为999:
现在出现了一个撑杆跳僵尸,它的血量咱们不知道,可以先搜索未知的初始数值:
种植一个土豆挡住撑杆跳僵尸,种植一个豌豆(攻击力20)打中这个僵尸一下:
又打中了一下:
这就实现了把一个僵尸定住的功能。
32、全屏减速的实现
在游戏开始的时候,这局僵尸都给你列出来了,有一种方法,你可以把这些僵尸的基址都遍历出来,用循环把每只僵尸的基址都遍历出来,写入冰冻状态的数值,但这样的话比较麻烦,如果僵尸特别多的话,你的程序可能会不准,或者程序有可能崩溃掉,那怎么办呢?
第一步还得先找到冰冻状态的传送指令,僵尸的基址+AC就是冰冻状态。
这个基址加上C8就是这只僵尸当前的血量:
如果不加C8就是当前这只僵尸的临时基址,加上AC就是它的冰冻状态:
复制这条指令的地址,剥离CE附加,到OD中转到该地址,这是一条传送指令,下个断点,看一下数据。
实现方法就是,找到这个僵尸的基址,然后加上AC,往这个地址写入一个数值,那么这个僵尸的状态就变成冰冻住了。
在OD中观察eax值的变化,如果这个数值不是很大,你又不是一直在写入的话,僵尸就会恢复正常状态了,那怎么办呢?
僵尸肯定会移动,它肯定有一个基址,加上有一定量的偏移能够得到僵尸当前的坐标,所以说只要僵尸移动,咱们就把这个基址记录下来,咱们跳转走,把这个基址加上AC,写入一个数据以后,咱们再给它跳回来就实现了冰冻。
用CE找到僵尸当前的坐标,未知的数值,一移动,就搜索减少(因为坐标原点是屏幕的左上角,从右往左移动坐标x的值肯定是减少),多次搜索后把00400000小的地址都删除,选那些大的数值,类型是float,僵尸的移动是在一条线上移动,找到后给它的数值改一下来测试;
右键找出是什么改写了这个坐标,找到僵尸坐标减少的关键指令,复制该指令地址,转到OD该地址处下断点;
我们可以看到,esi+2C就是当前僵尸的坐标的地址,咱们把esi的值复制到CE里验证一下,esi的地址值加上2C,可以看到这个就是当前僵尸的坐标,+c8就是血量,+ac就是冰冻状态(写入一个数值999)。
咱们怎么修改呢?
因为咱们在这个位置直接修改代码,写入数值999的话,位置不够,字节会超出;
所以咱们首先要找到一个空白位置,OD中的Strong插件有一个申请空白内存的,但是申请的内存都不可以使用,你只要游戏一关,这个地址就没了,要么有可能就被占用了,因为这些地址都是临时申请的;
咱就自己往下找一片空白的地址,或者程序开头(00400000)后面那里找一个空白位置。
- 跳转到空白位置(将jnz改为jmp空白地址,记住勾选使用nop填充);这个空白位置要挂有物理页,否则会崩溃;
- 在空白处写入冰冻状态;咱们第一个冰冻状态的基址是edi,咱们移动坐标的时候找到的它的基址是保存在esi,所以说这个位置不能使用edi了,edi加上2c偏移肯定不是僵尸当前的坐标,因为edi根本就不是僵尸的基址, 僵尸的基址是esi,所以第一个修改是esi;第二个修改是eax,把eax改成其他数值0x3E7(十进制为999);分析原地址处的指令执行逻辑,将相关指令搬到空白位置那里(原来是什么被修改了,在空白位置这里就写什么),并在最后jmp跳回原地址后面未修改指令那里;
僵尸只要一移动就跳转到咱们空白位置,在空白位置写入999(十六进制是0x3E7)这么一个状态。
记录一下正常的字节集,以及修改后的字节集,空白位置的字节集也要记录。
僵尸一出现肯定会移动,移动了就得到了当前僵尸的基址esi,基址加2C就是僵尸坐标,只要一移动就跳到空白位置写入冰冻状态(数值999),原来是什么指令,该怎么写还怎么写,写完了以后,如果jnz跳走了就跳走了,如果没跳走就直接跳回原位置后面未修改的指令那里接着执行。
就相当于,这个jnz跳转和cmp之间多出了写入状态的这么一个指令,实现让僵尸一移动就是冰冻的状态。
33、全屏奶油的找法
实现方法都是先找到僵尸移动的坐标,然后跳转过去:
僵尸就不会移动了,但是我们发现这个奶油并不是很好使,因为僵尸刚出来的话,最右边都看不到这些僵尸了(被定住了移动不了了),豌豆射手也不发射子弹了。
其实也可以改进这种情况,就是说当僵尸当前坐标读出来以后,当僵尸的坐标走过了某个坐标值的时候,咱们再让它写入冰冻数值就行了;
也就是空白位置的指令,加入一个条件判断逻辑就行了。
34、全屏减速、奶酪的选择性实现
咱们上次改的是这个位置,只要僵尸一移动就跳转到:
在这个空白位置写好功能了以后,原来是什么这还写什么:
然后再跳回之前的位置:
这里只是说一下,并没有写入任何的状态,只是演示一下上节课的方法。
在继续往下执行。
这里直接把相应指令写到空白位置看看效果:
那么如何实现又能冰冻、又能奶油定住僵尸呢?在你关闭冰冻的同时,奶油仍在呢?
因为如果你把所有功能写在一起的话,要关就所有都关,要开就一起开:
怎么给它们分开实现呢?
不用死心眼,可以在下图选中位置继续修改(JMP跳到一个空白位置)(也可以再往下某个位置修改):
先把要修改的指令都复制并记录一下:
再把之前原位置处复制的那个JE的指令写到这里:
这里要注意,如果je指令条件不满足、不跳的话,还得跳回原来位置fld那条指令那里,从fld指令往下继续执行,所以这里我们JMP到0052AB4E位置:
但是我们测试发现没有实现功能,这是怎么回事呢?
原来是上面减速那里,如果jnz条件满足就跳走了,跳到0052ABE8位置那里,把应该要执行的0052AB4D、0052AB4E那部分代码都跳过去了:
如果jnz条件没跳走的话,就会按照我们所想的继续执行改奶油的执行。
所以我们要在这里做个双保险,要在上图0052ABE8这里进行修改,先把上图选中部分复制记录下来:
当然你不能直接跳转到改奶油那里,那样又判断、又跳转回去的,程序就乱了;
所以你应该再找一片空白的位置:
我们要修改0052ABE8位置处的指令(4个字节)为JMP 00400C08(5个字节),就会导致下面的jnz指令被破坏(0052ABF2位置处的fld指令完好无损):
所以要把上图的jnz指令也写到空白位置2那里:
现在程序的流程就是:
这是正常的流程。
如果空白位置1那里减速的jnz指令生效跳走了的话:
比如咱们如果不要减速,把代码恢复回去的话,也不影响改奶酪的功能:
这个看起来有点乱,就是说为什么跳了两次呢?原因就是当两个功能都实现的时候呢,关键是在下图选中的指令位置:
这个jnz指令这里要分两种情况,jnz是上面的cmp指令的结果不是0的话则跳走,它如果跳走的话就直接把咱们下面的改奶酪部分的指令都隔过去了,肯定就修改不了奶酪的状态了,如果没跳过去那么自然就执行咱们修改的指令了,可以正常修改;
所以说,如果跳走的话:
咱们还要跳到00400C08空白位置2那里改奶酪。
如上图所示,两个地方都是改奶酪(双保险),这样保证了jnz跳或不跳都可以执行到改奶酪。
如果让减速生效的话,流程为:
这里的jnz就是刚才所说的那条指令,分两种情况,所以要做双保险;
恢复的话也要注意这种跳转,也要恢复两处位置的指令。
这节课主要就是讲了一下两个功能的同时实现,和功能的分别实现。
35、CALL寻找植物攻击力
由于攻击力是不会变得,所以只能通过找CALL来找到植物攻击力,通过僵尸减血来找到关键CALL。
复制减血指令所在的地址并记录下来,到OD中转到该地址处:
我们看到这个CALL有3个参数,咱们进入到这个CALL里面看一下它的返回值:
所以第一层这个CALL不是,因为植物攻击力的话应该会压入一个参数。
咱们再看第二个CALL:
咱们看到第二个CALL只压入了1个参数,咱们直接进去看一下返回值:
这个4就说明第二层CALL压入了一个参数,标记一下第二层CALL:
再看第三层CALL:
这一层CALL没有返回值,所以肯定不是植物攻击相关的。
上图第四层CALL带有1个参数,也可能是。
上图带有问号的CALL,这种就可以直接不看。
没有返回值的CALL我们直接就过滤掉。
这样咱们把之前的断点删掉,在第2层和第3层CALL上面的push参数那里各下一个断点:
然后运行游戏后直接断下来了,我们要看一下压入了什么内容:
我们看到ebx是一个地址,这个CALL是把一个地址压进来了,这个CALL不是植物的攻击力;
回到游戏继续运行,当炮弹打中僵尸后OD断下:
edx的值0x14,它的十进制是20,所以这个就是植物攻击CALL;
咱们在这个CALL上下一个断点:
断下来以后,鼠标右击修改堆栈中所压入的数值0x14为0x128,让它的攻击力变成296,取消这个断点运行游戏,发现僵尸直接就被打死了,所以就可以认定这个所压入的edx里的数值就是植物攻击力;
然后我们要找edx的来源,我们看到[ecx*4+0x69F1C8]就是一个数组,在这里下个断点:
我们看到ecx的值是0,所以豌豆射手的攻击力就是直接把0x69F1C8地址里面的内容;
咱们打开CE直接搜0x69F1C8这个地址,发现搜不到,那么就把这个地址手工加入地址:
我们看到这个地址是一个模块地址,所以这个地址是一个固定的地址,可以说这个地址保存的就是豌豆的攻击力。
咱们把OD解除附加,到CE里把僵尸血量的地址搜索到:
把豌豆攻击力改为1,这样就可以通过僵尸的血量观察到减少值。
可以看到打一下僵尸减1滴血,那么这个地址就是豌豆射手的攻击力,把这个数值改成比较大的数值1000的话,那么僵尸直接就被打死了;
所以通过这种方法就可以找到其他植物的攻击力。
后面我们还会讲全屏秒杀、聚怪(吸怪到一起)、种植CALL的查找与调用等等。