lyl610abc 发表于 2021-2-5 19:07

【原创】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-5 19:08

本帖最后由 lyl610abc 于 2021-2-10 14:15 编辑

新教程索引:
[**CE自写插件提供LUA接口实现pvz僵尸绘制**](https://www.52pojie.cn/thread-1369369-1-1.html)

lyl610abc 发表于 2021-2-6 17:01

遛你玩528 发表于 2021-2-6 16:41
楼主你好,看了你的教程 还是不明白pushfd到底能起到什么作用。
还有最后还原植物的Y坐标+行数为什么用eax ...

pushad: 将所有的32位通用寄存器压入堆栈
pushfd:然后将32位标志寄存器EFLAGS压入堆栈
这两个是拿来保存寄存器状态的,因为我们自己进行修改的过程需要用到寄存器,会改变寄存器的值,如果修改完寄存器的值以后没有恢复会造成游戏崩溃
最后还原植物坐标不一定要用eax来还原,只是随便拿个通用寄存器过来暂时作为赋值的媒介,你也可以把这里的eax改成edx,ebx换成ecx 没有问题的。

lyl610abc 发表于 2021-2-6 18:20

遛你玩528 发表于 2021-2-6 17:33
Pushad 我懂,就是把所有寄存器全部push一遍
那pushfd具体操作了什么呢?具体什么情况下需要用到pushfd

pushfd是保存标志位,比如cmp eax,0x0 然后je xxxx 这个je判断的其实就是标志位,cmp eax,0x0比较结果是给标志位的,如果你HOOK的代码里有用到cmp之类会改变标志位的指令的话就需要用到pushfd

璐璐诺 发表于 2021-2-5 19:12

大。。大佬 先前排再慢慢学习。。。。。

国际豆哥 发表于 2021-2-5 19:30

看见按这个大佬更新我上上就过来了很快啊

asd5478 发表于 2021-2-5 20:45

大佬牛逼啊,看的头晕{:301_985:}

吾爱蛋蛋 发表于 2021-2-5 21:28

谢谢大佬教程

天空の幻像 发表于 2021-2-5 21:45

太厉害了,研究的真细致

侃遍天下无二人 发表于 2021-2-5 21:51

有点意思,感觉略微能看懂一点,我一直想有所进步,不要再是只会替换代码实现无敌的水平了

moliy 发表于 2021-2-5 22:07

贼爱看,还看不懂

lyl610abc 发表于 2021-2-5 22:10

moliy 发表于 2021-2-5 22:07
贼爱看,还看不懂

哪里看不懂可以提出来,共同探讨,一起进步{:1_893:}
页: [1] 2 3 4 5
查看完整版本: 【原创】CE HOOK实现pvz子弹分身教程