植物大戰僵屍無冷卻分析


植物大戰僵屍無冷卻分析

起因

網上已經有很多帖子分析植物大戰僵屍冷卻的。有的看不出是個什么邏輯,一會搜索1,一會搜索0的,看不出來是什么門道。

沒辦法只能自己操刀按自己的想法分析看看了,就當學習。

准備工具

  • x32dbg
  • 植物大戰僵屍
  • Cheat Engine 7.1

過程

先把陽光數量變成可控的,陽光基址就不說怎么找了。其實不找也行只要把陽光變的多點就行,目的就是能隨時用陽光。

按照網上的思路反復搜索1,搜索0的確是能做到無CD,但是看不出門道。換一個思路,冷卻時間是最為直觀的。就是種上植物之后,卡片會變成冷卻狀態,有一個冷卻進度,如下圖的豌豆射手。

image-20201225211205630

所以目標就是先搜索出來記錄這個冷卻時間的地址。

這個冷卻時間有兩種可能,

  1. 種上植物后,程序設置一個冷卻時間,之后慢慢的往下減,減到0冷卻就沒了。
  2. 種上植物后,程序將變量置0,開始計時,慢慢的增加,增加到某個閾值,就是無冷卻了。(其實是這個)

搜索方法為了統一,不管它是增加計時,還是減數計時,搜索方法就一個。

搜索記錄冷卻時間的地址

搜索方法

  1. 搜索未知的初始值
  2. 冷卻陰影變動,就再搜索變動了的數值
  3. 未發現第一格冷卻計時地址,接着重復第2步

先用CE附加植物大戰僵屍進程。

接着種上植物,CE搜索未知的初始值。

首次掃描

image-20201225213248582

冷卻陰影變動后按下圖操作

再次掃描

image-20201225213604224

一直重復再次掃描的步驟,慢慢的篩選,當一個冷卻結束后,可以再種一個植物(必須是同一格)。

其實中間可以穿插一些未變動的數值搜索,這樣可以更快的檢索出結果來。

搜索結果

image-20201225214626128

看動圖可以種植的時候是0,有冷卻時間的時候隨時間增長。

zwzdjssq12

很明顯就是第一格的冷卻時間,把他拉下來,右擊后選擇「找出是什么改寫了這個地址」

image-20201225220434452

記錄下改寫指令的匯編地址004B2FEA

image-20201225220818798

實現無冷卻

CE 先使用一階段,關掉CE,打開X32dbg附加植物大戰僵屍

image-20201225221130189

按下Alt+A附加上植物大戰僵屍進程

image-20201225221242752

Ctrl + g到跳轉窗口,跳轉到剛剛記錄下來的004B2FEA地址。

image-20201225221640322

跳轉到地址004B2FEA指令處的分析。

image-20201225222524355

// 偽代碼大概就是這個意思
// ...
coolDownTime++;
if ( coolDownTime > 當前植物的冷卻需要的總時長) {
    // 取消冷卻 
    cancelCooling(當前植物);
}
// ...

實現無冷卻有一下兩種方法。

方法一

nop掉004B2FF3跳轉指令

image-20201225223333171

方法二

把比較換成與0比較

// 改成這個樣子
// ...
coolDownTime++;
if ( coolDownTime > 0) {
    // 取消冷卻 
    cancelCooling(當前植物);
}
// ...

image-20201225223547239

這樣改過之后打上補丁玩個游戲就沒意思了,可以用CE代碼注入,實現動態修改。

這里只演示第二種方法,第二種會了第一種方法就是大同小異。

x32dbg剝離植物大戰僵屍進程

image-20201226220554244

CE附加上植物大戰僵屍進程,點擊查看內存。

image-20201226220656272

ctrl + g跳轉到剛記錄的004B2FEA指令地址(就是+1指令的地址)

image-20201226220953310

實現的是第二種方法,所以我們選中下面這條指令

PlantsVsZombies.exe+B2FF0 - 3B 47 28              - cmp eax,[edi+28]

image-20201226221247759

安下面圖片一步一步來

image-20201226221407775

image-20201226221539274

image-20201226221630868

下面是生成出來的代碼,沒有任何改動

image-20201226221831655

修改后的代碼

image-20201226222017507

[ENABLE]
//code from here to '[DISABLE]' 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,[edi+28]
cmp eax, 0
jle PlantsVsZombies.exe+B3007

exit:
jmp returnhere

"PlantsVsZombies.exe"+B2FF0:
jmp newmem
returnhere:

 
[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"PlantsVsZombies.exe"+B2FF0:
cmp eax,[edi+28]
jle PlantsVsZombies.exe+B3007
//Alt: db 3B 47 28 7E 12

接着就是分配到當前的CT表,保存完這個頁面就可以關了。

image-20201226222123239

在主界面就能看到剛剛保存的腳本

image-20201226222301695

這樣可以動態修改指令。勾上就是無冷卻,取消就是正常代碼。

第一種方法和第二種方法都可以實現,第一種方法留下自己實現吧。

看看效果

lq

游戲,索然無味。

到這里還沒有結束,不是為了實現冷卻,而是盡量的多分析一下看看。

繼續深入一點分析-挖掘基址

把剛剛匯編的地方還原,跳出當前函數看看上一層的邏輯。

斷在004B2FEA之處后,先點擊運行到返回 => 在單步步過一下 ( 或者雙擊右下角堆棧窗口的0019F9EC地址數據,也可以跳轉到上一層邏輯)

image-20201225225145077

上一層函數的邏輯

image-20201225225921563

形成偽代碼就是

// ...
cardList = [豌豆射手, 向日葵, 櫻桃炸彈 ....]
cardCount = [eax + 24] // 當前可使用的植物卡片數量
count = 0              // xor ebx, ebx
if (cardCount > 0) {
    do {
    	card = cardList[count];
    	handlerCard(card);  // 最開始分析的call, 里面執行+1 重置冷卻時間
    	count++;    	
    } while(count < cardCount);
}
// ...

邏輯大致是清晰了,現在找基址一下基址。現在關注一些上下文信息

image-20201225231801986

# 匯編語句
00428E40            | 8B87 5C010000        | mov eax,dword ptr ds:[edi+15C]        |
00428E46(斷點處EIP)  | 33F6                 | xor esi,esi                           | esi = 0
00428E48            | 3958 24              | cmp dword ptr ds:[eax+24],ebx         | [eax + 24] 是格子數量   最上面(xor ebx, ebx)
00428E4B            | 7E 1B                | jle plantsvszombies.428E68            | if
00428E4D            | 8D49 00              | lea ecx,dword ptr ds:[ecx]            |
00428E50            | 8D4403 28            | lea eax,dword ptr ds:[ebx+eax+28]     | 這是數組
00428E54            | E8 57A10800          | call plantsvszombies.4B2FB0           | 剛剛分析的就是這個call
00428E59            | 8B87 5C010000        | mov eax,dword ptr ds:[edi+15C]        |
00428E5F            | 46                   | inc esi                               | i++
00428E60            | 83C3 50              | add ebx,50                            |
00428E63            | 3B70 24              | cmp esi,dword ptr ds:[eax+24]         | esi < [eax + 24]
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需要傳入植物卡片的地址([ebx+eax+28] ),eax 是上下文提供的,ebx是偏移相當於高級語言中i++的作用。

現在eax 的值不確定,有兩種方法,

  1. 一個是向上看eax是誰給的,慢慢向上一層一層的找
  2. 使用CE搜索eax寄存器的值19B9EC90。(eax = [edi+15C], 搜索edi(200DB408)就行)

我選擇第二種,因為第一種我試過了,不好使不方便。

第二種方式:x32dbg剝離植物大戰僵屍進程。

image-20201225232827074

換成CE加載,搜索edi寄存器的值200DB408(注意先勾上十六進制

image-20201225234628935

拉下來右鍵=>『找出是什么訪問了這個地址』。

搜索ESI值0295B120

基址都記錄下來吧,以后可能都會用到。(其實隨便選一個就行)

<Address>PlantsVsZombies.exe+3794F8</Address>
<Address>PlantsVsZombies.exe+37959C</Address>
<Address>PlantsVsZombies.exe+379670</Address>
<Address>PlantsVsZombies.exe+379618</Address>

組合分析使用基址

// 猜測
CARDS {
    // ....
    int count;     // +24
    Card list[X];  // +28  每個Card大小為50  
    // 后面分析這個數組好像是固定長度  控制 [CARDS + 0x24] 可以擴容和減少格數
    // 設置超過數組的 size 就會程序異常
    // .....
}

//植物卡片對象 地址
CARDS = [[[PlantsVsZombies.exe+379618] + 0x868] + 0x15C]
//植物卡片個數  可以實現擴容和減少格數
[CARDS + 0x24]
// 第N格植物卡片地址 N > 0
[0x50 * N + CARDS + 0x28]
// 第N格植物卡片地址 N > 0 冷卻記錄
[0x50 * N + CARDS + 0x28 + 0x24]
// 第N格植物卡片地址 N > 0 冷卻時間
[0x50 * N + CARDS + 0x28 + 0x28]
// 模塊起始地址
00400000 - PlantsVsZombies.exe

有了這些分析就可以寫代碼了。

Python 實現輔助

下面放一部分,具體的全部代碼可以看附錄里面的代碼,大多讀寫內存的操作都是封裝的Win32Api(還不如用C++寫)

# 省略 .......
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([n+1, plant_info["cooling"], 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))
# 省略 .......

效果:向上滾動滾輪增加陽關,向下滾動滾輪減少陽關(注意動態中陽關的變化)。可以看到有幾個格子,冷卻時間,總時間。

231

修改的功能就只有一個陽光,其他的就不寫了。有興趣的可以下載附件中的CT(里面包含無冷卻,格子數量的控制功能)。

總結

整個過程的分析是完成了,就像開篇說的搜索1/0的方法還是沒有探索出來是怎么影響的,知道『原理』的可以撩一撩,是從什么角度分析的,學習一下。

文中提到的方式都可以實現無冷卻,分析過程中也隨便扒拉出了其他信息,比如當前格子的冷卻,當前植物冷卻時間的總時間,還有格子數量[CARDS + 0x24]。分析該到一段落了,大多分析結果都是通過少量已知的上下文條件,和我的經驗推斷出來的(不一定准確),如有異議可以回帖交流啦。

前往下載附件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM