【原创】CE HOOK实现pvz子弹分身教程
本帖最后由 lyl610abc 于 2021-9-8 09:47 编辑# 前言
上期教程更侧重于学习,并没有什么实际的功能效果,于是为了学以致用,今天给大家带来CE HOOK实现pvz子弹分身教程
适合学习人群:比萌新强两丝丝足矣
上期教程链接:https://www.52pojie.cn/thread-1361473-1-1.html
# 效果图
先给大家看看效果
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/pvz1.gif)
# 事前准备
本人使用的工具: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的那个
附带一个下载地址:https://lanzoui.com/i9u7o3i
# 教程内容
## 思路分析
首先搞清楚我们想要实现的功能:**让发射的一个子弹变成多个**
很明显与子弹的产生有关,于是我们的目的转化为:**找到产生子弹的CALL→HOOK这个CALL让它一次产生多个子弹**
如何找到子弹产生的这个CALL?寻找**相关数据**来获得
我们先暂且不论子弹产生的内部细节,但有一点显而易见的便是子弹产生后一定会引起**场上子弹数量的增加**
```lua
function generateBullet()
.......
bulletNum++; --子弹数量增加
.......
return ?
end
```
于是我们便以这个场上的子弹数量为突破口开启找CALL之路
## 寻找子弹产生CALL
首先我们要找到这个记录场上子弹数量的地址
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205130914263.png)
通过CE过滤子弹数量,当子弹数为1时搜1,为2时搜2.........最终可以得到记录当前场上子弹数量的地址
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205131126067.png)
然后右键 找出是什么改写了这个地址
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205131302353.png)
这里有2条记录,一条是子弹产生时引起数量增加,还有一条是命中僵尸后子弹消失引起的数量减少
这里的第一条便是我们子弹产生时让子弹数量增加对应的汇编指令
我们点击显示反汇编程序进去看看
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205131650345.png)
```assembly
PlantsVsZombies.exe+1DFC9 - 01 47 10 - add ,eax { 子弹增加 }
```
找到这一里以后要干什么?向上一层找
为什么要向上一层找?
我们前面已经分析过子弹数量的增加只是子弹产生里的一个小步骤,可能是如下结构
```lua
function bulletNumAdd() --子弹增加的函数
bulletNum++; --我们看到的add ,eax就对应这里
return ?
end
function generateBullet()
.......
bulletNumAdd() --调用子弹增加的函数
.......
return ?
end
```
这里我们想要调用产生子弹的CALL就要返回到调用CALL的**同一层**来查看具体的参数
于是我们可以在这里下个断点,让它断下
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205141000067.png)
接着F8单步步过,在ret的这里停下,先不要返回
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205141125569.png)
我们这是可以看一下右下角的堆栈窗口,PS:如果显示不同 可以在这个窗口这里 右键"堆栈跟踪"
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205141206972.png)
### 堆栈窗口浅谈
首先我们都知道CALL XXXX以后是要返回的,要返回到哪里的**相关数据**都存在**堆栈**里,所以我们可以通过这个堆栈地址返回到上一层 上上一层 上上上层......,而这些返回地址的上一句就是CALL XXXX调用我们这个子弹数量增加的CALL
我们可以通过这里顺腾摸瓜 子弹数量增加语句→子弹数量增加CALL→子弹产生CALL (实际的执行顺序是反过来的)
如果我们不通过这个堆栈窗口,而是一直F8单步步过的到ret再返回实际结果也是如此
所以我们知道了 这个堆栈窗口里的某个CALL 一定就是产生子弹的CALL(我们可以先记录下这些CALL的位置 在相应CALL位置 右键设置书签)
### 如何确定是哪个CALL?
**看参数**,产生子弹至少需要哪两个参数?,答案呼之欲出:坐标,子弹的X坐标和Y坐标,于是我们的这个函数至少有2个参数
### 如何确定一个CALL的参数?
**看CALL里面的返回值** ret xx
拿堆栈里的一个返回地址来举例,我们双击它来跳转到那里
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205143758137.png)
```assembly
PlantsVsZombies.exe+D62A - E8 31090100 - call PlantsVsZombies.exe+1DF60 { 子弹数量增加call }
```
如何确定这个call的参数呢?进到CALL里面去查看返回值
我们Ctrl+G或者右键转到地址
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205144232518.png)
这里想跳转的地址填CALL XXXX 里的XXXX 我们这里就是填PlantsVsZombies.exe+1DF60
跳转后我们就已经进入到了CALL的内部,在下面还能看到我们之前的子弹数量增加语句(验证了堆栈窗口的作用,存储返回地址的相关数据,注意我这里说的是**相关数据**,不一定就直接是返回地址,但我们(系统)能够用相关数据算出返回地址,这涉及到段寄存器的相关知识,这里不做重点)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205144619477.png)
我们直接到函数的尾部查看返回值
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205145051654.png)
```assembly
PlantsVsZombies.exe+1E001 - C3 - ret
```
我们可以看到这里没有返回值 直接就ret了
那是不是说明这个函数不需要参数,直接就可以调用了?并不是
我们先说一下
#### ret xxx和参数的关系
在汇编中 一个子程序(call)有几个参数(push)就需要几个RETURN(堆栈平衡) 因为push压入的是四字节 所以有几个push 也就需要几个
retpush数量*4) 打个比方如果压入了5个参数 则ret 的返回数值为 ret 0x14 注意这里是十六进制 0x14=20=5×4
push **只是将参数传给call的手段之一**,也可以通过mov 寄存器,xxx等给寄存器赋值的方法来传递参数
如果我们这里直接用代码注入器调用这个CALL,没有给它传递参数,那么游戏直接崩溃,PS:代码注入器是拿来给我们外部直接调用CALL的
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205150223661.png)
我们可以看一下CALL前面的代码:
```assembly
PlantsVsZombies.exe+D620 - 56 - push esi
PlantsVsZombies.exe+D621 - 57 - push edi
PlantsVsZombies.exe+D622 - 8B F8 - mov edi,eax
PlantsVsZombies.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的值复制下来:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205150913051.png)
这里的EDI为15A8D050
我们修改一下代码注入器里的内容,然后重新测试
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205151307797.png)
代码注入以后,我们发现游戏并没有崩溃,同时场上子弹数量增加了1,且屏幕左上角出现了一个子弹
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205151734218.png)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205151422962.png)
这里主要是为了说明**call的参数可能不只是push给的**,**可能还与寄存器的值有关**,这个CALL我们没有找到与坐标相关的数据,于是看**下一个CALL**(之前堆栈窗口里的第二个)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205161942619.png)
用同样的方法,查看这个call的参数(跳转地址到CALL XXXX里的XXXX,然后到尾部看返回值)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205153419441.png)
```assembly
PlantsVsZombies.exe+D653 - C2 1400 - ret 0014 { 20 }
```
从这里的ret 0014我们可以得到push了20/4=5个参数
于是我们在回到这个CALL这里 下个断点 看看它参数的内容
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205162110428.png)
```assembly
PlantsVsZombies.exe+672A5 - 50 - push eax {子弹类型}
PlantsVsZombies.exe+672A6 - 8B 45 04 - mov eax, {植物基址}
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单步步过,**确保被修改过的寄存器压入到堆栈中**,然后返回游戏,可以发现子弹产生的位置发生了改变
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205162211359.png)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205154304453.png)
由此我们可以认为这个CALL便是子弹产生的关键CALL,于是依葫芦画瓢,我们把填入相关的参数然后调用这个CALL
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205160831700.png)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205161010386.png)
这里我们可以看到子弹成功产生了,所以**这个CALL就是产生子弹的关键CALL**
#### 如何获得参数的值
**在参数压入之前**下断
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205161153256.png)
断下:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205161232219.png)
push eax则把eax 变成此时的push 0x0
mov eax,则把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的值
```assembly
push 0x0
mov eax,0x13587F08
push 0x3
mov ecx,0x513DD
push 0x000513DC
push 0x186
push 0x69
call 0040D620
```
对照
```assembly
PlantsVsZombies.exe+672A5 - 50 - push eax {子弹类型}
PlantsVsZombies.exe+672A6 - 8B 45 04 - mov eax, {植物基址}
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(之前堆栈窗口的第三个)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205164455977.png)
```assembly
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
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205165048428.png)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205165039977.png)
```assembly
push 0x0
push 0x3
push 0x0
push 0x15B01A48
call 00466E00
```
对比
```assembly
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,发现子弹可以正常产生没有问题,但是当我们想要让子弹换个行产生,我们修改一下行数的参数发现:子弹的显示位置还是在下面,但是阴影在上面
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205171213451.png)
怎么解决呢?前面的原理说过子弹的产生是由植物的X坐标和Y坐标**计算**而来的,我们这里之所以子弹的Y坐标没有改变自然是因为植物的Y位置还是原本的那个值,所以我们如果想要子弹的Y坐标改变就得改变植物的Y坐标,然后再恢复植物的Y坐标即可
那么植物的Y坐标的偏移是多少呢?可以从前面一个CALL来追溯(较麻烦不推荐)
```assembly
PlantsVsZombies.exe+672A5 - 50 - push eax {子弹类型}
PlantsVsZombies.exe+672A6 - 8B 45 04 - mov eax, {植物基址}
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坐标的来源
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205173237108.png)
```assembly
PlantsVsZombies.exe+671DD - 8D 74 08 DF - lea esi,
```
于是我们得到了子弹是eax+ecx-21得来的,很显然是通过计算得来的,我们这里去追溯eax和ecx就可以得到植物的偏移
另一个办法就比较简单,遍历植物的基址,我们可以通过遍历基址比对来分析植物的数据结构,这里碍于篇幅问题就不具体展开了
植物的数据结构分析教程不少,疑惑的可以去看看,这里也不做重点,直接给出**植物的y坐标偏移是0c**
于是我们修改一下注入代码
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205174420758.png)
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205174358141.png)
注入以后发现子弹能够在我们指定的位置产生了
贴上注入代码:
```assembly
pushad //将所有的32位通用寄存器压入堆栈
pushfd //将32位标志寄存器EFLAGS压入堆栈
mov eax,0x15B01A48 //植物基址
mov ebx,0x64 //要修改的植物y坐标
mov ecx, //保存修改前的植物y坐标,修改完要还原
mov ,ebx //修改植物的y坐标
mov edx,0x98000 //此处的0x98000为任意一处可读写空地址
mov ,ecx //将到时候要还原的y坐标保存到空地址中
push 0x00 //固定值0
push 0x00 //行数 从0开始 0是第一行 4是最后一行
push 0x00 //固定值0
push eax //植物基址
call 00466E00
mov eax,0x15B01A48 //植物基址
mov ebx,0x98000 //之前保存y坐标的地址
mov ebx, //取出y坐标
mov ,ebx //还原y坐标
popfd //将32位标志寄存器EFLAGS取出堆栈
popad //将所有的32位通用寄存器取出堆栈
```
为什么要额外用空地址来保存修改前的y坐标? **CALL执行后寄存器和堆栈的数据可能会发生变化**
这里的空地址地址是哪来的?
CE直接搜索数组 十六进制然后填一堆零 即可得到空地址 搜索选项下面的**可写记得勾上**
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205175242635.png)
这里因为00098000已经被我修改过了,所以没在搜索结果里
## HOOK 子弹产生
前面我们已经实现了在任意行发射子弹,接下来我们就是寻找要HOOK的点并HOOK
要在哪里HOOK呢,首先HOOK的点肯定是要在子弹发射的时候,并且能够获得植物基址
这里我选择了在我们CALL返回的位置HOOK
先贴修改前的代码:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205182526261.png)
```assembly
PlantsVsZombies.exe+64BCA - 5F - pop edi
PlantsVsZombies.exe+64BCB - 5E - pop esi
PlantsVsZombies.exe+64BCC - 5B - pop ebx
PlantsVsZombies.exe+64BCD - 8B E5 - mov esp,ebp
PlantsVsZombies.exe+64BCF - 5D - pop ebp
PlantsVsZombies.exe+64BD0 - C3 - ret
```
修改后的代码:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205182622098.png)
```assembly
PlantsVsZombies.exe+64BCA - E9 31B45900 - jmp 00A00000
PlantsVsZombies.exe+64BCF - 5D - pop ebp
PlantsVsZombies.exe+64BD0 - C3 - ret
```
HOOK的代码:
```assembly
//code from here to '' will be used to enable the cheat
alloc(newmem,2048)
alloc(oriaddr,32) //申请地址 用来存储原本植物基址
alloc(oriy,16) //申请地址 用来存储原本植物的y坐标
alloc(orirow,16) //申请地址 用来存储用本植物的行数
alloc(inity,16) //申请地址 用来存储初始的植物y坐标,每次增加0x64=100=一行的间隔
alloc(initrow,16) //申请地址 用来存储初始的值与行数,每次增加1
label(loopcode) //要循环的代码段
label(returnhere)
label(originalcode)
label(exit)
label(endcode) //退出循环后要复原的代码
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
pushad
pushfd
mov ,edi //保存植物基址
mov ,0x0 //要改变的植物y坐标
mov ,0x0 //要改变的植物行数
mov ecx, //暂存植物y坐标,植物基址+c偏移为植物y坐标
mov ,ecx //保存植物y坐标到申请的空间
mov ecx, //暂存植物行数,植物基址+1c偏移为植物行数
mov ,ecx //保存植物行数到申请的空间
jmp loopcode
originalcode:
pop edi
pop esi
pop ebx
mov esp,ebp
exit:
jmp returnhere
loopcode:
cmp ,0x5 //比较是否到了最后一行,如果是则跳出循环
jeendcode
mov edi,
add ,0x64
add ,0x1
mov ebx, //要改变的植物y坐标
mov ,ebx //改变植物的y坐标
mov ebx, //要改变的植物行数
mov ,ebx //改变植物行数
mov ebx,
sub ebx,0x1
cmp ebx, //比较是否和原来的行相同,不重复发射
je loopcode //相同则跳过,不重复发射
push 0x00
push ebx
push 0x00
push edi
call 00466E00
mov ecx,
cmp ,0x1f4 //这里的1f4=500 即比较是否到了最后一行
jb loopcode //如果小于,则继续循环
jmpendcode
endcode:
mov eax,
mov ebx,
mov ,ebx //还原植物的y坐标
mov ebx,
mov ,ebx //还原植物的行数
popfd
popad
pop edi
pop esi
pop ebx
mov esp,ebp
jmp 00464BCF
"PlantsVsZombies.exe"+64BCA:
jmp newmem
returnhere:
//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 edi
pop esi
pop ebx
mov esp,ebp
//Alt: db 5F 5E 5B 8B E5
```
CT代码里已经写好了注释,主体思想就是从第一行开始到第五行,依次调用前面我们的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表截图
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210205190922978.png)
本帖最后由 lyl610abc 于 2021-2-10 14:15 编辑
新教程索引:
[**CE自写插件提供LUA接口实现pvz僵尸绘制**](https://www.52pojie.cn/thread-1369369-1-1.html) 遛你玩528 发表于 2021-2-6 16:41
楼主你好,看了你的教程 还是不明白pushfd到底能起到什么作用。
还有最后还原植物的Y坐标+行数为什么用eax ...
pushad: 将所有的32位通用寄存器压入堆栈
pushfd:然后将32位标志寄存器EFLAGS压入堆栈
这两个是拿来保存寄存器状态的,因为我们自己进行修改的过程需要用到寄存器,会改变寄存器的值,如果修改完寄存器的值以后没有恢复会造成游戏崩溃
最后还原植物坐标不一定要用eax来还原,只是随便拿个通用寄存器过来暂时作为赋值的媒介,你也可以把这里的eax改成edx,ebx换成ecx 没有问题的。 遛你玩528 发表于 2021-2-6 17:33
Pushad 我懂,就是把所有寄存器全部push一遍
那pushfd具体操作了什么呢?具体什么情况下需要用到pushfd
pushfd是保存标志位,比如cmp eax,0x0 然后je xxxx 这个je判断的其实就是标志位,cmp eax,0x0比较结果是给标志位的,如果你HOOK的代码里有用到cmp之类会改变标志位的指令的话就需要用到pushfd 大。。大佬 先前排再慢慢学习。。。。。 看见按这个大佬更新我上上就过来了很快啊 大佬牛逼啊,看的头晕{:301_985:} 谢谢大佬教程 太厉害了,研究的真细致 有点意思,感觉略微能看懂一点,我一直想有所进步,不要再是只会替换代码实现无敌的水平了 贼爱看,还看不懂 moliy 发表于 2021-2-5 22:07
贼爱看,还看不懂
哪里看不懂可以提出来,共同探讨,一起进步{:1_893:}