12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 279|回复: 0

【原创】CE HOOK实现pvz子弹分身教程

[复制链接]

3782

主题

3782

帖子

214748万

积分

超级版主

Rank: 8Rank: 8

积分
2147483647
发表于 2021-7-18 17:49:53 | 显示全部楼层 |阅读模式
媒介

上期教程更偏重于学习,并没有什么实际的功能效果,于是为了学以致用,今天给大家带来CE HOOK实现pvz子弹分身教程
适合学习人群:比萌新强两丝丝足矣
上期教程链接:https://www.52pojie.cn/thread-1361473-1-1.html
效果图

先给大家看看效果

事前准备

本人使用的工具:CE7.2汉化版(不用在意版本问题)
论坛有:https://down.52pojie.cn/Tools/Debuggers/Cheat%20Engine%20v7.2.exe
代码注入工具:https://www.52pojie.cn/thread-963707-1-1.html
PS:OD什么的不需要啦
本人使用的游戏:植物大战僵尸原版的汉化版(非年度版)     就是阳光基址是006A9EC0+768+5560的那个
教程内容

思路分析

首先搞清晰我们想要实现的功能:让发射的一个子弹变成多个
很显着与子弹的产生有关,于是我们的目的转化为:找到产生子弹的CALL→HOOK这个CALL让它一次产生多个子弹
怎样找到子弹产生的这个CALL?探求相关数据来获得
我们先暂时岂论子弹产生的内部细节,但有一点显而易见的便是子弹产生后一定会引起场上子弹数量的增长
function generateBullet()    .......        bulletNum++;        --子弹数量增长    .......    return ?end于是我们便以这个场上的子弹数量为突破口开启找CALL之路
探求子弹产生CALL

首先我们要找到这个记录场上子弹数量的地址

通过CE过滤子弹数量,当子弹数为1时搜1,为2时搜2.........最终可以得到记录当前场上子弹数量的地址

然后右键 找出是什么改写了这个地址

这里有2条记录,一条是子弹产生时引起数量增长,另有一条是掷中僵尸后子弹消失引起的数量减少
这里的第一条便是我们子弹产生时让子弹数量增长对应的汇编指令
我们点击显示反汇编步伐进去看看

PlantsVsZombies.exe+1DFC9 - 01 47 10              - add [edi+10],eax { 子弹增长 }找到这一里以后要干什么?向上一层找
为什么要向上一层找?
我们前面已经分析过子弹数量的增长只是子弹产生里的一个小步骤,可能是如下结构
function bulletNumAdd()                --子弹增长的函数    bulletNum++;                        --我们看到的add [edi+10],eax就对应这里    return ?endfunction generateBullet()    .......    bulletNumAdd()                        --调用子弹增长的函数    .......                                    return ?end这里我们想要调用产生子弹的CALL就要返回到调用CALL的同一层来查看详细的参数
于是我们可以在这里下个断点,让它断下

接着F8单步步过,在ret的这里停下,先不要返回

我们这是可以看一下右下角的堆栈窗口,PS:如果显示差异 可以在这个窗口这里 右键"堆栈跟踪"

堆栈窗口浅谈

首先我们都知道CALL XXXX以后是要返回的,要返回到哪里的相关数据都存在堆栈里,以是我们可以通过这个堆栈地址返回到上一层 上上一层 上上上层......,而这些返回地址的上一句就是CALL XXXX调用我们这个子弹数量增长的CALL
我们可以通过这里顺腾摸瓜 子弹数量增长语句→子弹数量增长CALL→子弹产生CALL (实际的实行顺序是反过来的)
如果我们不通过这个堆栈窗口,而是不停F8单步步过的到ret再返回实际效果也是云云
以是我们知道了 这个堆栈窗口里的某个CALL 一定就是产生子弹的CALL(我们可以先记录下这些CALL的位置 在相应CALL位置 右键设置书签)
怎样确定是哪个CALL?

看参数,产生子弹至少需要哪两个参数?,答案呼之欲出:坐标,子弹的X坐标和Y坐标,于是我们的这个函数至少有2个参数
怎样确定一个CALL的参数?

看CALL里面的返回值 ret xx
拿堆栈里的一个返回地址来举例,我们双击它来跳转到那边

PlantsVsZombies.exe+D62A - E8 31090100           - call PlantsVsZombies.exe+1DF60 { 子弹数量增长call }怎样确定这个call的参数呢?进到CALL里面去查看返回值
我们Ctrl+G或者右键转到地址

这里想跳转的地址填CALL XXXX 里的XXXX 我们这里就是填PlantsVsZombies.exe+1DF60
跳转后我们就已经进入到了CALL的内部,在下面还能看到我们之前的子弹数量增长语句(验证了堆栈窗口的作用,存储返回地址的相关数据,注意我这里说的是相关数据,不一定就直接是返回地址,但我们(体系)可以或许用相关数据算出返回地址,这涉及到段寄存器的相关知识,这里不做重点)

我们直接到函数的尾部查看返回值

PlantsVsZombies.exe+1E001 - C3                    - ret 我们可以看到这里没有返回值 直接就ret了
那是不是说明这个函数不需要参数,直接就可以调用了?并不是
我们先说一下
ret xxx和参数的关系

在汇编中 一个子步伐(call)有几个参数(push)就需要几个RETURN(堆栈平衡) 由于push压入的是四字节 以是有几个push 也就需要几个
ret  push数量*4)        打个比方如果压入了5个参数 则ret 的返回数值为 ret 0x14        注意这里是十六进制 0x14=20=5×4
push 只是将参数传给call的本领之一,也可以通过mov 寄存器,xxx等给寄存器赋值的方法来传递参数
如果我们这里直接用代码注入器调用这个CALL,没有给它传递参数,那么游戏直接崩溃,PS:代码注入器是拿来给我们外部直接调用CALL的

我们可以看一下CALL前面的代码:
PlantsVsZombies.exe+D620 - 56                    - push esiPlantsVsZombies.exe+D621 - 57                    - push ediPlantsVsZombies.exe+D622 - 8B F8                 - mov edi,eaxPlantsVsZombies.exe+D624 - 81 C7 C8000000        - add edi,000000C8 { 200 }PlantsVsZombies.exe+D62A - E8 31090100           - call PlantsVsZombies.exe+1DF60 { 子弹数量增长call}可以看到mov edi,eax 我们猜疑可以猜疑它将edi作为了参数,以是我们可以在call这里下个断点,然后将edi的值复制下来:

这里的EDI为15A8D050
我们修改一下代码注入器里的内容,然后重新测试

代码注入以后,我们发现游戏并没有崩溃,同时场上子弹数量增长了1,且屏幕左上角出现了一个子弹


这里主要是为了说明call的参数可能不但是push给的可能还与寄存器的值有关,这个CALL我们没有找到与坐标相关的数据,于是看下一个CALL(之前堆栈窗口里的第二个)

用同样的方法,查看这个call的参数(跳转地址到CALL XXXX里的XXXX,然后到尾部看返回值)

PlantsVsZombies.exe+D653 - C2 1400               - ret 0014 { 20 }从这里的ret 0014我们可以得到push了20/4=5个参数
于是我们在回到这个CALL这里 下个断点 看看它参数的内容

PlantsVsZombies.exe+672A5 - 50                    - push eax         {子弹范例}PlantsVsZombies.exe+672A6 - 8B 45 04              - mov eax,[ebp+04]        {植物基址}PlantsVsZombies.exe+672A9 - 53                    - push ebx         {行数}PlantsVsZombies.exe+672AA - 83 E9 01              - sub ecx,01 { 1 }PlantsVsZombies.exe+672AD - 51                    - push ecx                         {未知,貌似不影响效果,可以直接填0}PlantsVsZombies.exe+672AE - 56                    - push esi { y坐标 }PlantsVsZombies.exe+672AF - 57                    - push edi { x坐标 }PlantsVsZombies.exe+672B0 - E8 6B63FAFF           - call PlantsVsZombies.exe+D620 { 子弹产生call }我们通过分析数值 可以得出ESI和EDI分别是子弹的Y坐标和X坐标,怎样确定?
在PUSH 之前修改寄存器的值,修改后F8单步步过,确保被修改过的寄存器压入到堆栈中,然后返回游戏,可以发现子弹产生的位置发生了改变


由此我们可以认为这个CALL便是子弹产生的关键CALL,于是依葫芦画瓢,我们把填入相关的参数然后调用这个CALL


这里我们可以看到子弹成功产生了,以是这个CALL就是产生子弹的关键CALL
怎样获得参数的值

在参数压入之前下断

断下:

push eax  则把eax 变成此时的push 0x0
mov eax,[ebp+04]则把eax变成赋值后的数值即mov eax,0x13587F08(记得要F8单步步过一步步下来哦)
push ebx则把ebx变成此时的push 0x3
....以此类推
我们只需要关注call之前push xxx和mov xxx的值即可,然后把相关数值给它即可
怎样得知相关参数含义

然后我们可以修改相关数值,比如把push edi 的edi由0x69改成0x169 可以发现子弹的x坐标发生了改变来确定相关参数的含义
也可以观察差异位置豌豆引发停止时寄存器数值的差异来分析出各参数的含义
得出注入代码

于是我们就得出了上面要注入的代码,mov eax,xxxx后面的那个值要修改成你本身的EAX的值
push 0x0mov eax,0x13587F08push 0x3mov ecx,0x513DDpush 0x000513DCpush 0x186push 0x69call 0040D620对照
PlantsVsZombies.exe+672A5 - 50                    - push eax         {子弹范例}PlantsVsZombies.exe+672A6 - 8B 45 04              - mov eax,[ebp+04]        {植物基址}PlantsVsZombies.exe+672A9 - 53                    - push ebx         {行数}PlantsVsZombies.exe+672AA - 83 E9 01              - sub ecx,01 { 1 }PlantsVsZombies.exe+672AD - 51                    - push ecx                         {未知,貌似不影响效果,可以直接填0}PlantsVsZombies.exe+672AE - 56                    - push esi { y坐标 }PlantsVsZombies.exe+672AF - 57                    - push edi { x坐标 }PlantsVsZombies.exe+672B0 - E8 6B63FAFF           - call PlantsVsZombies.exe+D620 { 子弹产生call }到这一步我们就已经找到产生子弹的CALL了,但是我们会发现参数很多多少啊,我们想让子弹分身只需要修改子弹的行数和子弹的y坐标就可以了,其它的参数和原本同等就行,我们无需关心。那么能不能换个参数少点的CALL呢,天然可以,我们直接去到下一个CALL(之前堆栈窗口的第三个)

PlantsVsZombies.exe+64BBF - 6A 00                 - push 00 { 0 }PlantsVsZombies.exe+64BC1 - 51                    - push ecx                {行数}PlantsVsZombies.exe+64BC2 - 6A 00                 - push 00 { 0 }PlantsVsZombies.exe+64BC4 - 57                    - push edi                {植物基址}PlantsVsZombies.exe+64BC5 - E8 36220000           - call PlantsVsZombies.exe+66E00 { call 植物动态基址 }用同样的方法得出这个CALL的参数是4个 然后我们试着调用一下这个CALL


push 0x0push 0x3push 0x0push 0x15B01A48call 00466E00对比
PlantsVsZombies.exe+64BBF - 6A 00                 - push 00 { 0 }PlantsVsZombies.exe+64BC1 - 51                    - push ecx                {行数}PlantsVsZombies.exe+64BC2 - 6A 00                 - push 00 { 0 }PlantsVsZombies.exe+64BC4 - 57                    - push edi                {植物基址}PlantsVsZombies.exe+64BC5 - E8 36220000           - call PlantsVsZombies.exe+66E00 { call 植物动态基址 }我们这里只给了两个参数,另有两个参数固定为0 就成功调用了子弹的产生
说一下原理:子弹的产生是由详细的某一个植物产生的,子弹产生首先要获取植物的基址,通过植物基址可以获取到植物的X坐标和Y坐标 以及子弹范例等等参数,然后根据植物的X坐标和Y坐标生成对应的子弹的坐标
以是其实只需要传入一个植物的基址就可以完成,但这里它额外多加了一个行数的参数
CALL调用流程

从前面的分析 我们可以知道调用的关系是
植物产生子弹(植物基址,行数)→产生子弹(子弹范例,植物基址,行数,未知,X坐标,Y坐标)→子弹数量增长()→子弹数量++
我们这里就选择HOOK 最外层的这个CALL  植物产生子弹(植物基址,行数),固然也可以HOOK 后面的那个产生子弹(CALL),但要填的参数较多就是了,感爱好可以当作业本身做一下~~~
修改参数测试CALL

前面我们用原本的参数添补了CALL,发现子弹可以正常产生没有问题,但是当我们想要让子弹换个行产生,我们修改一下行数的参数发现:子弹的显示位置还是在下面,但是阴影在上面

怎么解决呢?前面的原理说过子弹的产生是由植物的X坐标和Y坐标计算而来的,我们这里之以是子弹的Y坐标没有改变天然是由于植物的Y位置还是原本的那个值,以是我们如果想要子弹的Y坐标改变就得改变植物的Y坐标,然后再规复植物的Y坐标即可
那么植物的Y坐标的偏移是多少呢?可以从前面一个CALL来追溯(较麻烦不保举)
PlantsVsZombies.exe+672A5 - 50                    - push eax         {子弹范例}PlantsVsZombies.exe+672A6 - 8B 45 04              - mov eax,[ebp+04]        {植物基址}PlantsVsZombies.exe+672A9 - 53                    - push ebx         {行数}PlantsVsZombies.exe+672AA - 83 E9 01              - sub ecx,01 { 1 }PlantsVsZombies.exe+672AD - 51                    - push ecx                         {未知,貌似不影响效果,可以直接填0}PlantsVsZombies.exe+672AE - 56                    - push esi { y坐标 }PlantsVsZombies.exe+672AF - 57                    - push edi { x坐标 }PlantsVsZombies.exe+672B0 - E8 6B63FAFF           - call PlantsVsZombies.exe+D620 { 子弹产生call }我们知道前面的这个CALL的 esi是植物的y坐标,于是我们可以追溯这个y坐标的来源

PlantsVsZombies.exe+671DD - 8D 74 08 DF           - lea esi,[eax+ecx-21]于是我们得到了子弹是eax+ecx-21得来的,很显然是通过计算得来的,我们这里去追溯eax和ecx就可以得到植物的偏移
另一个办法就比较简单,遍历植物的基址,我们可以通过遍历基址比对来分析植物的数据结构,这里碍于篇幅问题就不详细展开了
植物的数据结构分析教程不少,疑惑的可以去看看,这里也不做重点,直接给出植物的y坐标偏移是0c
于是我们修改一下注入代码


注入以后发现子弹可以或许在我们指定的位置产生了
贴上注入代码:
pushad                                                //将全部的32位通用寄存器压入堆栈pushfd                                                //将32位标志寄存器EFLAGS压入堆栈mov eax,0x15B01A48                        //植物基址mov ebx,0x64                                //要修改的植物y坐标mov ecx,[eax+c]                                //保存修改前的植物y坐标,修改完要还原mov [eax+c],ebx                                //修改植物的y坐标mov edx,0x98000                                //此处的0x98000为任意一处可读写空隙址mov [edx],ecx                                //将到时候要还原的y坐标保存到空隙址中push 0x00                                        //固定值0push 0x00                                        //行数 从0开始 0是第一行 4是最后一行push 0x00                                        //固定值0push eax                                        //植物基址call 00466E00                                mov eax,0x15B01A48                        //植物基址mov ebx,0x98000                                //之前保存y坐标的地址mov ebx,[ebx]                                //取出y坐标mov [eax+c],ebx                                //还原y坐标popfd                                                //将32位标志寄存器EFLAGS取出堆栈popad                                                //将全部的32位通用寄存器取出堆栈为什么要额外用空隙址来保存修改前的y坐标? CALL实行后寄存器和堆栈的数据可能会发生变化
这里的空隙址地址是哪来的?
CE直接搜索数组 十六进制然后填一堆零 即可得到空隙址 搜索选项下面的可写记得勾上

这里由于00098000已经被我修改过了,以是没在搜索效果里
HOOK 子弹产生

前面我们已经实现了在任意行发射子弹,接下来我们就是探求要HOOK的点并HOOK
要在哪里HOOK呢,首先HOOK的点肯定是要在子弹发射的时候,并且可以或许获得植物基址
这里我选择了在我们CALL返回的位置HOOK
先贴修改前的代码:

PlantsVsZombies.exe+64BCA - 5F                    - pop ediPlantsVsZombies.exe+64BCB - 5E                    - pop esiPlantsVsZombies.exe+64BCC - 5B                    - pop ebxPlantsVsZombies.exe+64BCD - 8B E5                 - mov esp,ebpPlantsVsZombies.exe+64BCF - 5D                    - pop ebpPlantsVsZombies.exe+64BD0 - C3                    - ret 修改后的代码:

PlantsVsZombies.exe+64BCA - E9 31B45900           - jmp 00A00000PlantsVsZombies.exe+64BCF - 5D                    - pop ebpPlantsVsZombies.exe+64BD0 - C3                    - ret HOOK的代码:
[ENABLE]//code from here to '[DISABLE]' will be used to enable the cheatalloc(newmem,2048)alloc(oriaddr,32)                //申请地址 用来存储原本植物基址alloc(oriy,16)                        //申请地址 用来存储原本植物的y坐标alloc(orirow,16)                //申请地址 用来存储用本植物的行数alloc(inity,16)                        //申请地址 用来存储初始的植物y坐标,每次增长0x64=100=一行的间隔alloc(initrow,16)                //申请地址 用来存储初始的值与行数,每次增长1label(loopcode)                        //要循环的代码段label(returnhere)label(originalcode)label(exit)label(endcode)          //退出循环后要复原的代码newmem: //this is allocated memory, you have read,write,execute access//place your code herepushadpushfdmov [oriaddr],edi     //保存植物基址mov [inity],0x0       //要改变的植物y坐标mov [initrow],0x0       //要改变的植物行数mov ecx,[edi+c]       //暂存植物y坐标,植物基址+c偏移为植物y坐标mov [oriy],ecx        //保存植物y坐标到申请的空间mov ecx,[edi+1c]      //暂存植物行数,植物基址+1c偏移为植物行数mov [orirow],ecx        //保存植物行数到申请的空间jmp loopcodeoriginalcode:pop edipop esipop ebxmov esp,ebpexit:jmp returnhereloopcode:cmp [initrow],0x5       //比较是否到了最后一行,如果是则跳出循环je  endcodemov edi,[oriaddr]add [inity],0x64add [initrow],0x1mov ebx,[inity]         //要改变的植物y坐标mov [edi+c],ebx       //改变植物的y坐标mov ebx,[initrow]       //要改变的植物行数mov [edi+1c],ebx         //改变植物行数mov ebx,[initrow]sub ebx,0x1cmp ebx,[orirow]         //比较是否和原来的行相同,不重复发射je loopcode              //相同则跳过,不重复发射push 0x00push ebxpush 0x00push edicall 00466E00mov ecx,[inity]cmp [inity],0x1f4    //这里的1f4=500 即比较是否到了最后一行jb loopcode          //如果小于,则继续循环jmp  endcodeendcode:mov eax,[oriaddr]mov ebx,[oriy]mov [eax+c],ebx       //还原植物的y坐标mov ebx,[orirow]mov [eax+1c],ebx      //还原植物的行数popfdpopadpop edipop esipop ebxmov esp,ebpjmp 00464BCF"PlantsVsZombies.exe"+64BCA:jmp newmemreturnhere:[DISABLE]//code from here till the end of the code will be used to disable the cheat//申请的空间记得撤销dealloc(newmem)                dealloc(oriaddr)dealloc(oriy)dealloc(inity)"PlantsVsZombies.exe"+64BCA:pop edipop esipop ebxmov esp,ebp//Alt: db 5F 5E 5B 8B E5CT代码里已经写好了注释,主体头脑就是从第一行开始到第五行,依次调用前面我们的CALL来产生子弹,期间加入了变量的保存和读取以及重复子弹的判断
总结(注意事项)

找CALL最重要的就是思路,根据相关变量来确定CALL,本教程的相关变量是场上的子弹数量
找CALL过滤CALL的时候可以看参数来过滤,因此分析目标CALL的结构至关重要
找CALL看CALL的参数时可以通过CALL里的ret返回值来判断PUSH的参数,RET和PUSH是对应的
CALL的参数传值不但范围于PUSH(堆栈传值),也有可能通过寄存器来传值
分析CALL参数含义时可以通过修改CALL在 PUSH前寄存器的值来验证 也可以通过差异的植物产生子弹的调用比较差异参数来判断
测试CALL的时候一定要注意堆栈平衡,该给的参数一定不能少,不然很容易造成游戏崩溃
HOOK的时候一定要注意保存现场,确保HOOK后寄存器和堆栈中的值没有受到影响,以此保证步伐的正常运转
作业

HOOK前面找到的参数较多 较为里面那一层的CALL来实现相同的功能
找一找植物的基址,分析出其数据结构
个人感言

这个教程肝了我一天(是我比较菜的原因),后面那部分HOOK子弹产生的讲解比较少,主要都是代码的实现,思路在前面就已经放开了,可能不是很好理解,本人能力水平也不是很高,望大家高抬贵手,多多担待,如果这个教程对你有用的话,希望能给我点点赞,你们的支持是对我最大的鼓励
最后附上CT表,(CT表里包含了上次教程的作业答案----通过HOOK实现秒杀全部僵尸 包括僵尸BOSS)
CT表截图



PlantsVsZombies.zip
1.79 KB, 下载次数: 22, 下载积分: 吾爱币 -1 CB

CT表

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-24 14:47 , Processed in 0.140625 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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