『原创』植物大战僵尸分析及Python辅助实现
本帖最后由 syncking 于 2020-12-27 15:11 编辑# 植物大战僵尸分析及Python辅助实现
## 起因
网上已经有很多帖子分析植物大战僵尸冷却的。有的看不出是个什么逻辑,一会搜索1,一会搜索0的,脑子笨想不出来是什么门道。
没办法只能自己操刀按自己的想法分析看看了,就当学习。
## 准备工具
- x32dbg
- 植物大战僵尸
- Cheat Engine 7.1
## 过程
> 先把阳光数量变成可控的,阳光基址就不说怎么找了。其实不找也行只要把阳光变的多点就行,目的就是能随时用阳光。
按照网上的思路反复搜索1,搜索0的确是能做到无CD,但是看不出门道。换一个思路,冷却时间是最为直观的。就是种上植物之后,卡片会变成冷却状态,有一个冷却进度,如下图的豌豆射手。
所以目标就是先搜索出来记录这个冷却时间的地址。
这个冷却时间有两种可能,
1. 种上植物后,程序设置一个冷却时间,之后慢慢的往下减,减到0冷却就没了。
2. 种上植物后,程序将变量置0,开始计时,慢慢的增加,增加到某个阈值,就是无冷却了。(其实是这个)
搜索方法为了统一,不管它是增加计时,还是减数计时,搜索方法就一个。
### 搜索记录冷却时间的地址
**搜索方法**
1. 搜索未知的初始值
2. 冷却阴影变动,就再搜索变动了的数值
3. 未发现第一格冷却计时地址,接着重复第2步
先用CE附加植物大战僵尸进程。
接着种上植物,CE搜索未知的初始值。
**首次扫描**
冷却阴影变动后按下图操作
**再次扫描**
一直重复**再次扫描**的步骤,慢慢的筛选,当一个冷却结束后,可以再种一个植物(必须是同一格)。
>其实中间可以穿插一些**未变动的数值**搜索,这样可以更快的检索出结果来。
搜索结果
看动图可以种植的时候是0,有冷却时间的时候随时间增长。
很明显就是第一格的冷却时间,把他拉下来,右击后选择「找出是什么改写了这个地址」
记录下改写指令的汇编地址`004B2FEA`
### 实现无冷却
`CE `先使用一阶段,关掉`CE`,打开`X32dbg`附加植物大战僵尸
按下`Alt+A`附加上植物大战僵尸进程
`Ctrl + g`到跳转窗口,跳转到刚刚记录下来的`004B2FEA`地址。
跳转到地址`004B2FEA`指令处的分析。
```cpp
// 伪代码大概就是这个意思
// ...
coolDownTime++;
if ( coolDownTime > 当前植物的冷却需要的总时长) {
// 取消冷却
cancelCooling(当前植物);
}
// ...
```
实现无冷却有一下两种方法。
#### 方法一
nop掉`004B2FF3`跳转指令
```cpp
// 改成这个样子
// ...
coolDownTime++;
// 取消冷却
cancelCooling(当前植物);
// ...
```
#### 方法二
把比较换成与0比较
```cpp
// 改成这个样子
// ...
coolDownTime++;
if ( coolDownTime > 0) {
// 取消冷却
cancelCooling(当前植物);
}
// ...
```
这样改过之后打上补丁玩个游戏就没意思了,可以用CE代码注入,实现动态修改。
这里只演示第二种方法,第二种会了第一种方法就是大同小异。
`x32dbg`剥离植物大战僵尸进程
换`CE`附加上植物大战僵尸进程,点击查看内存。
`ctrl + g`跳转到刚记录的`004B2FEA`指令地址(就是+1指令的地址)
实现的是第二种方法,所以我们选中下面这条指令
```
PlantsVsZombies.exe+B2FF0 - 3B 47 28 - cmp eax,
```
安装下面图片一步一步来
下面是生成出来的代码,没有任何改动
修改后的代码
```assembly
//code from here to '' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
originalcode:
// cmp eax,
cmp eax, 0
jle PlantsVsZombies.exe+B3007
exit:
jmp returnhere
"PlantsVsZombies.exe"+B2FF0:
jmp newmem
returnhere:
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"PlantsVsZombies.exe"+B2FF0:
cmp eax,
jle PlantsVsZombies.exe+B3007
//Alt: db 3B 47 28 7E 12
```
接着就是`分配到当前的CT表`,保存完这个页面就可以关了。
在主界面就能看到刚刚保存的脚本
这样可以动态修改指令。勾上就是无冷却,取消就是正常代码。
第一种方法和第二种方法都可以实现,第一种方法留下自己实现吧。
**看看效果**
游戏,索然无味。
到这里还没有结束,不是为了实现冷却,而是尽量的多分析一下看看。
### 继续深入一点分析-挖掘基址
把刚刚汇编的地方还原,跳出当前函数看看上一层的逻辑。
断在`004B2FEA`之处后,先点击运行到返回 =>在单步步过一下 ( 或者双击右下角堆栈窗口的`0019F9EC`地址数据,也可以跳转到上一层逻辑)
上一层函数的逻辑
形成伪代码就是
```cpp
// ...
cardList = [豌豆射手, 向日葵, 樱桃炸弹 ....]
cardCount = // 当前可使用的植物卡片数量
count = 0 // xor ebx, ebx
if (cardCount > 0) {
do {
card = cardList;
handlerCard(card);// 最开始分析的call, 里面执行+1 重置冷却时间
count++;
} while(count < cardCount);
}
// ...
```
逻辑大致是清晰了,现在找基址一下基址。现在关注一些上下文信息
```assembly
# 汇编语句
00428E40 | 8B87 5C010000 | mov eax,dword ptr ds: |
00428E46(断点处EIP)| 33F6 | xor esi,esi | esi = 0
00428E48 | 3958 24 | cmp dword ptr ds:,ebx | 是格子数量 最上面(xor ebx, ebx)
00428E4B | 7E 1B | jle plantsvszombies.428E68 | if
00428E4D | 8D49 00 | lea ecx,dword ptr ds: |
00428E50 | 8D4403 28 | lea eax,dword ptr ds: | 这是数组
00428E54 | E8 57A10800 | call plantsvszombies.4B2FB0 | 刚刚分析的就是这个call
00428E59 | 8B87 5C010000 | mov eax,dword ptr ds: |
00428E5F | 46 | inc esi | i++
00428E60 | 83C3 50 | add ebx,50 |
00428E63 | 3B70 24 | cmp esi,dword ptr ds: | esi <
00428E66 | 7C E8 | jl plantsvszombies.428E50 |
# 寄存器信息
EAX : 19B9EC90
EBX : 00000000
ECX : 0000019E
EDX : 00000163
EBP : 0019FA5C
ESP : 0019F9F0
ESI : 1FF12C18
EDI : 200DB408
EIP : 00428E46
```
`call plantsvszombies.4B2FB0`需要传入植物卡片的地址(`` ),`eax `是上下文提供的,`ebx`是偏移相当于高级语言中`i++`的作用。
现在`eax `的值不确定,有两种方法,
1. 一个是向上看`eax`是谁给的,慢慢向上一层一层的找
2. 使用`CE`搜索`eax`寄存器的值`19B9EC90`。(`eax = `, 搜索`edi(200DB408`)就行)
我选择第二种,因为第一种我试过了,不好使不方便。
第二种方式:`x32dbg`剥离植物大战僵尸进程。
换成CE加载,搜索`edi`寄存器的值`200DB408`(注意先勾上`十六进制`)
拉下来右键=>『找出是什么访问了这个地址』。
搜索ESI值`0295B120`
基址都记录下来吧,以后可能都会用到。(其实随便选一个就行)
```xml
<Address>PlantsVsZombies.exe+3794F8</Address>
<Address>PlantsVsZombies.exe+37959C</Address>
<Address>PlantsVsZombies.exe+379670</Address>
<Address>PlantsVsZombies.exe+379618</Address>
```
### 组合分析使用基址
```cpp
// 猜测
CARDS {
// ....
int count; // +0x24
Card list;// +0x28每个Card大小为0x50
// 后面分析这个数组好像是固定长度控制 可以扩容和减少格数
// 设置超过数组的 size 就会程序异常
// .....
}
//植物卡片对象 地址
CARDS = [[ + 0x868] + 0x15C]
//植物卡片个数可以实现扩容和减少格数
// 第N格植物卡片地址 N > 0
// 第N格植物卡片地址 N > 0 冷却记录
// 第N格植物卡片地址 N > 0 冷却时间
// 模块起始地址
00400000 - PlantsVsZombies.exe
```
有了这些分析就可以写代码了。
### Python 实现辅助
下面放一部分,具体的全部代码可以看附录里面的代码,大多读写内存的操作都是封装的Win32Api(还不如用C++写)
```python
# 省略 .......
pid = get_pid("PlantsVsZombies.exe")
print("PlantsVsZombies.exe 进程id: " + str(pid))
processHandle = open_process(pid)
def showCard(processHandle):
count = plant_cards_count(processHandle)
os.system("cls")
x = PrettyTable(["格子", "当前冷却时间", "总时间"])
for n in range(count):
plant_info = get_plant_n(processHandle, n)
x.add_row(, plant_info["cool_time"]])
title = "阳光:{}".format(getSunCount(processHandle))
print(x.get_string(title=title))
def drawTable():
while True:
showCard(processHandle)
time.sleep(1)
# 滚动滚轮
def on_scroll(x, y, dx, dy):
# dy == -1 下滑
# dy ==1 上滑
sunCount = getSunCount(processHandle)
setSunCount(processHandle, sunCount + (5 * dy))
# 省略 .......
```
**效果:**向上滚动滚轮增加阳光,向下滚动滚轮减少阳光(注意动态中阳光的变化)。可以看到有几个格子,冷却时间,总时间。
修改的功能就只有一个阳光,其他的就不写了,写多了也没意义,现在不会还有人玩这游戏的吧哈哈👀。有兴趣的可以下载附件中的CT(里面包含无冷却,格子数量的控制功能)。写这个Python辅助脚本的目的就是验证上面分析出来的基址和信息对不对,别无目的。
## 总结
整个过程的分析是完成了,就像开篇说的搜索`1/0`的方法还是没有探索出来是怎么影响的,知道『原理』的可以撩一撩,是从什么角度分析的,学习一下。
文中提到的方式都可以实现无冷却,分析过程中也随便扒拉出了其他信息,比如当前格子的冷却,当前植物冷却时间的总时间,还有格子数量``。分析该到一段落了,大多分析结果都是通过少量已知的上下文条件,和我的经验推断出来的(不一定准确),如有异议可以回帖交流啦。
推荐下载相同版本的植物大战僵尸,不同版本CT文件可能不通用
链接:https://pan.baidu.com/s/13XiuPfzr0PStDCpJE5OYYg提取码:kmgq (里面包含CT文件)
点个赞 满满的回忆啊
曲断情 发表于 2020-12-27 00:02
点个赞 满满的回忆啊
可以说是回忆了,以前打两天也没打通关{:301_973:} 技术流啊,我最近还在玩呢:lol 谢谢分享,感谢感谢 谢谢大佬的分享! 技术才是王道啊!老哥真是666 这是纯技术贴了,感谢分享 谢谢楼主,虽然代码看不懂,但是CT文件却很好用{:301_993:}