pvzclass是如何實(shí)現(xiàn)的?pvzclass源代碼初步分析(4)Memory類 & AsmFuntions.h
本篇將分析PVZ.h中的Memory類,以及AsmFuntions.h & AsmFunctions.cpp) 。
文中會(huì)涉及到一些Windows API函數(shù),不過(guò)本文不會(huì)對(duì)它們深層次的實(shí)現(xiàn)作深入探討,請(qǐng)放心。
本文中的“PVZ本體”和“修改器本體”指的是運(yùn)行中的程序,而不是存在存儲(chǔ)器中的文件。

Memory類概覽

如上圖所示,Memory類中的所有成員都帶有static關(guān)鍵字,即它們都是靜態(tài)的。
不過(guò)為了使用它們,你仍然需要實(shí)例化PVZ對(duì)象。
除了InjectDll()(.dll文件的注入)的定義在PVZ.cpp中完成,其他方法的定義都在Classes文件夾的Memory.cpp中。
Memory類中的成員可以分為三類:PVZ的屬性、讀寫(xiě)PVZ內(nèi)存的方法,以及其他內(nèi)存相關(guān)方法。
不過(guò),因?yàn)镻VZ屬性相關(guān)的變量的賦值依賴于Memory類的方法,這些變量的分析放到最后。
讀寫(xiě)PVZ內(nèi)存的方法

ReadMemory、WriteMemory、ReadArray和WriteArray都用到了C++中的template,定義也是直接在PVZ.h中完成。
使用template,意味著我們?cè)趯?shí)際調(diào)用時(shí)完全不用擔(dān)心各種變量類型的轉(zhuǎn)換問(wèn)題。這種問(wèn)題放手交給pvzclass就行。
ReadMemory和ReadArray都使用了ReadProcessMemory,它的聲明類似于如下的代碼:
其中l(wèi)pBaseAddr表示讀取的起始地址,nSize表示讀取的內(nèi)存大小。
雖然返回值是Bool類型,但是實(shí)際上它只表示讀取是否成功,實(shí)際的讀取結(jié)果存儲(chǔ)到修改器本體從lpBuffer開(kāi)始的內(nèi)存。
WriteMemory和WriteArray類似,它們使用了WriteProcessMemory,其聲明類似于如下的代碼:
與ReadProcessMemory類似,lpBaseAddr表示寫(xiě)入的起始地址,nSize表示讀取的內(nèi)存大小。
寫(xiě)入的數(shù)據(jù)源則是修改器本體從lpBuffer開(kāi)始的內(nèi)存。
雖然返回值是Bool類型,但是實(shí)際上它只表示寫(xiě)入是否成功。
某種意義上講,ReadProcessMemory和WriteProcessMemory像是跨程序的memcpy。

ReadPointer的定義是在Memory.cpp中完成的:

要讀取指針指向的變量,如果不用ReadPointer,需要多次使用ReadMemory。
ReadPointer可以減少代碼量,用起來(lái)也非常方便。
實(shí)際上PVZ.cpp中GetAll類的方法,大都使用了ReadPointer來(lái)代替ReadMemory。
其他方法
剩余的方法也都用到了Windows API函數(shù)。
AllAccess使用VirtualProtectEx,可以將PVZ本體中的一段內(nèi)存轉(zhuǎn)變?yōu)榭勺x、可寫(xiě)、可執(zhí)行的狀態(tài)。
AllocMemory使用VirtualAllocEx,可以在PVZ本體中申請(qǐng)一段未使用的內(nèi)存,用來(lái)存放數(shù)據(jù)等內(nèi)容。
AllocMemory的返回值為申請(qǐng)的內(nèi)存的起始地址。
CreateThread使用CreateRemoteThread,可以從PVZ本體的某個(gè)位置直接開(kāi)始一段線程的運(yùn)行。
FreeMemory與AllocMemory相對(duì),使用VirtualFreeEx,可以釋放在PVZ本體中申請(qǐng)的內(nèi)存。
Execute的代碼如下:

Execute的作用是在PVZ本體中運(yùn)行一段在PVZ外寫(xiě)成的程序。
從上面的代碼中,我們可以看出Execute的運(yùn)行步驟:
申請(qǐng)內(nèi)存、注入代碼、運(yùn)行線程、釋放內(nèi)存、取走返回值。
其中兩個(gè)WriteMemory的作用,第一個(gè)是暫停PVZ本體其他線程的運(yùn)行,第二個(gè)則是恢復(fù)其他線程的運(yùn)行。
InjectDll的代碼如下:

與Execute的結(jié)構(gòu)高度相似。
不同的是,InjectDll執(zhí)行的代碼大部分是固定的,匯編代碼(即__asm__InjectDll)主要存儲(chǔ)在Asmfuntions.h中。
PVZ屬性相關(guān)
在Memory.cpp中,我們可以看到四個(gè)變量的初值……嗎?

這顯然不能看出它們的作用。
實(shí)際上對(duì)它們賦值的主要代碼在PVZ類的構(gòu)造函數(shù)中:

這樣這四個(gè)變量的作用就比較明晰了:
processId存儲(chǔ)PVZ本體的進(jìn)程ID,通過(guò)Open或其他方法獲??;
hProcess存儲(chǔ)PVZ本體的句柄,由OpnProcess獲?。?/p>
mainwindowhandle存儲(chǔ)PVZ窗口的句柄,從PVZ本體的內(nèi)存中讀取;
Variable則存儲(chǔ)pvzclass申請(qǐng)的一段內(nèi)存。
hProcess和mainwindowhandle可以作為某些Windows API函數(shù)的參數(shù)。
pvzclass中也有不少功能是用機(jī)器碼實(shí)現(xiàn)的,這些功能臨時(shí)占用的空間、部分參數(shù)的存儲(chǔ)位置、返回值的暫存位置,全都在Variable對(duì)應(yīng)的內(nèi)存之中。
AsmFunctions
這里吐個(gè)槽,AsmFunction.h的拼寫(xiě)一直是錯(cuò)誤的"AsmFuntion.h"("Function"沒(méi)有"c")。下文采用"AsmFunction.h"。
這里的"Asm"指的是"Assembly Language",即“匯編語(yǔ)言”。
AsmFunctions(.h/.cpp)包含的,正是為pvzclass擴(kuò)充匯編語(yǔ)言的支持的代碼。
如果沒(méi)有匯編語(yǔ)言基礎(chǔ)的話,看這一節(jié)之前還是先去了解一下相關(guān)的內(nèi)容吧。
當(dāng)然,這不影響你使用pvzclass的其他絕大部分內(nèi)容。
AsmFunctions.h包括三部分:定義匯編代碼和INVOKE宏、定義封裝用宏、聲明匯編代碼。

我們依次分析。
asm define部分中有一個(gè)INUMBER宏,它看上去很奇怪:
如果你熟悉位運(yùn)算的話,應(yīng)該能看出來(lái),這是將一個(gè)32位整數(shù)8位8位地分割,然后將四部分的順序顛倒過(guò)來(lái)而已。
這么做是針對(duì)機(jī)器碼處理32位整數(shù)的方法。

即使是熟悉匯編語(yǔ)言的人,可能也會(huì)對(duì)代碼名的后綴感到頭皮發(fā)麻。
這里大概解釋一下命名風(fēng)格:
每個(gè)宏的命名由“代碼名”和“后綴”兩部分構(gòu)成。
“代碼名”對(duì)應(yīng)的是某條指令在匯編語(yǔ)言中的名稱。
“后綴”則表示該條指令的參數(shù)、參數(shù)類型、格式等。
如"DWORD"表示相關(guān)參數(shù)占4字節(jié)(而非默認(rèn)的1字節(jié)),"_EAX"表示參數(shù)中含有寄存器eax,等等等等。
這里不詳細(xì)展開(kāi)。讀者可以利用Cheat Engine等軟件自己摸索。
接下來(lái)介紹一個(gè)比較重要的宏:INVOKE宏。

在匯編語(yǔ)言中,call指令使用相對(duì)引用。
也就是說(shuō),機(jī)器碼都為"0xE8 0x64 0x00 0x00 0x00"的代碼,在0xE38000時(shí)是"call 0xE38007",但在其他位置則是另外的"call"了。
但對(duì)于常用匯編語(yǔ)言的創(chuàng)作者而言,絕對(duì)引用的call是非常有必要的,因?yàn)镻VZ的代碼不會(huì)自己跑到內(nèi)存的其他地方。
在這種情況下,INVOKE宏應(yīng)運(yùn)而生。
INVOKE宏可以視為一個(gè)替代CALL的解決方案。它可以作為一個(gè)絕對(duì)引用的CALL而不會(huì)產(chǎn)生其他副作用,避免了計(jì)算相對(duì)引用的繁雜過(guò)程。
在pvzclass中,INVOKE宏也相當(dāng)常用。
讀者也可以嘗試在自己的pvzclass自用匯編代碼中使用INVOKE宏。
在初始的INVOKE宏下方,還有多個(gè)INVOKE宏的變種,但它們都只是在INVOKE宏的基礎(chǔ)上添加了參數(shù)而已。

這里定義的是pvzclass自用的匯編代碼中使用的宏。
可以看到,這些宏只是在具體地應(yīng)用INVOKE宏而已。

這里聲明的是pvzclass自用的匯編代碼。
在pvzclass中,機(jī)器碼(包括自匯編代碼轉(zhuǎn)化而來(lái)的)用byte數(shù)組的形式存儲(chǔ)。
這些的實(shí)裝主要在AsmFunctions.cpp中完成。

pvzclass是針對(duì)PVZ的項(xiàng)目。
PVZ, PVZ, 怎么能沒(méi)有P(植物)和Z(僵尸)呢?
下一篇開(kāi)始分析pvzclass中,有關(guān)植物和僵尸的代碼。