五、封包
技術(shù) 通過對(duì)動(dòng)作模擬
技術(shù)的介紹,我們對(duì)
游戲外掛有了一定程度上的認(rèn)識(shí),也學(xué)會(huì)了使用動(dòng)作模擬
技術(shù)來實(shí)現(xiàn)簡(jiǎn)單的動(dòng)作模擬型
游戲外掛的制作。這種動(dòng)作模擬型
游戲外掛有一定的局限性,它僅僅只能解決使用計(jì)算機(jī)代替人力完成那么有規(guī)律、繁瑣而無聊的
游戲動(dòng)作。但是,隨著網(wǎng)絡(luò)
游戲的盛行和復(fù)雜度的增加,很多
游戲要求將客戶端動(dòng)作信息及時(shí)反饋回服務(wù)器,通過服務(wù)器對(duì)這些動(dòng)作信息進(jìn)行有效認(rèn)證后,再向客戶端發(fā)送下一步
游戲動(dòng)作信息,這樣動(dòng)作模擬
技術(shù)將失去原有的效應(yīng)。為了更好地“外掛”這些
游戲,
游戲外掛程序也進(jìn)行了升級(jí)換代,它們將以前針對(duì)
游戲用戶界面層的模擬推進(jìn)到數(shù)據(jù)通訊層,通過封包
技術(shù)在客戶端擋截
游戲服務(wù)器發(fā)送來的
游戲控制數(shù)據(jù)包,分析數(shù)據(jù)包并修改數(shù)據(jù)包;同時(shí)還需按照
游戲數(shù)據(jù)包結(jié)構(gòu)創(chuàng)建數(shù)據(jù)包,再模擬客戶端發(fā)送給
游戲服務(wù)器,這個(gè)過程其實(shí)就是一個(gè)封包的過程。
封包的
技術(shù)是實(shí)現(xiàn)第二類
游戲外掛的最核心的
技術(shù)。封包
技術(shù)涉及的知識(shí)很廣泛,實(shí)現(xiàn)方法也很多,如擋截WinSock、擋截API函數(shù)、擋截消息、VxD驅(qū)動(dòng)程序等。在此我們也不可能在此文中將所有的封包
技術(shù)都進(jìn)行詳細(xì)介紹,故選擇兩種在
游戲外掛程序中最常用的兩種方法:擋截WinSock和擋截API函數(shù)。
1. 擋截WinSock
眾所周知,Winsock是
Windows網(wǎng)絡(luò)編程接口,它工作于
Windows應(yīng)用層,它提供與底層傳輸協(xié)議無關(guān)的高層數(shù)據(jù)傳輸編程接口。在
Windows系統(tǒng)中,使用WinSock接口為應(yīng)用程序提供基于TCP/IP協(xié)議的網(wǎng)絡(luò)訪問服務(wù),這些服務(wù)是由Wsock32.DLL動(dòng)態(tài)鏈接庫提供的函數(shù)庫來完成的。
由上說明可知,任何
Windows基于TCP/IP的應(yīng)用程序都必須通過WinSock接口訪問網(wǎng)絡(luò),當(dāng)然網(wǎng)絡(luò)
游戲程序也不例外。由此我們可以想象一下,如果我們可以控制WinSock接口的話,那么控制
游戲客戶端程序與服務(wù)器之間的數(shù)據(jù)包也將易如反掌。按著這個(gè)思路,下面的工作就是如何完成控制WinSock接口了。由上面的介紹可知,WinSock接口其實(shí)是由一個(gè)動(dòng)態(tài)鏈接庫提供的一系列函數(shù),由這些函數(shù)實(shí)現(xiàn)對(duì)網(wǎng)絡(luò)的訪問。有了這層的認(rèn)識(shí),問題就好辦多了,我們可以制作一個(gè)類似的動(dòng)態(tài)鏈接庫來代替原WinSock接口庫,在其中實(shí)現(xiàn)WinSock32.dll中實(shí)現(xiàn)的所有函數(shù),并保證所有函數(shù)的參數(shù)個(gè)數(shù)和順序、返回值類型都應(yīng)與原庫相同。在這個(gè)自制作的動(dòng)態(tài)庫中,可以對(duì)我們感興趣的函數(shù)(如發(fā)送、接收等函數(shù))進(jìn)行擋截,放入外掛控制代碼,最后還繼續(xù)調(diào)用原WinSock庫中提供的相應(yīng)功能函數(shù),這樣就可以實(shí)現(xiàn)對(duì)網(wǎng)絡(luò)數(shù)據(jù)包的擋截、修改和發(fā)送等封包功能。
下面重點(diǎn)介紹創(chuàng)建擋截WinSock外掛程序的基本步驟:
(1) 創(chuàng)建DLL項(xiàng)目,選擇Win32 Dynamic-Link Library,再選擇An empty DLL project。
(2) 新建文件wsock32.h,按如下步驟輸入代碼:
① 加入相關(guān)變量聲明:
HMODULE hModule=NULL; //模塊句柄
char buffer[1000]; //緩沖區(qū)
FARPROC proc; //函數(shù)入口指針
② 定義指向原WinSock庫中的所有函數(shù)地址的指針變量,因WinSock庫共提供70多個(gè)函數(shù),限于篇幅,在此就只選擇幾個(gè)常用的函數(shù)列出,有關(guān)這些庫函數(shù)的說明可參考MSDN相關(guān)內(nèi)容。
//定義指向原WinSock庫函數(shù)地址的指針變量。
SOCKET (__stdcall *socket1)(int ,int,int);//創(chuàng)建Sock函數(shù)。
int (__stdcall *WSAStartup1)(WORD,LPWSADATA);//初始化WinSock庫函數(shù)。
int (__stdcall *WSACleanup1)();//清除WinSock庫函數(shù)。
int (__stdcall *recv1)(SOCKET ,char FAR * ,int ,int );//接收數(shù)據(jù)函數(shù)。
int (__stdcall *send1)(SOCKET ,const char * ,int ,int);//發(fā)送數(shù)據(jù)函數(shù)。
int (__stdcall *connect1)(SOCKET,const struct sockaddr *,int);//創(chuàng)建連接函數(shù)。
int (__stdcall *bind1)(SOCKET ,const struct sockaddr *,int );//綁定函數(shù)。
......其它函數(shù)地址指針的定義略。
(3) 新建wsock32.cpp文件,按如下步驟輸入代碼:
① 加入相關(guān)頭文件聲明:
#include "windows.h"
#include "stdio.h"
#include "wsock32.h"
② 添加DllMain函數(shù),在此函數(shù)中首先需要加載原WinSock庫,并獲取此庫中所有函數(shù)的地址。代碼如下:
BOOL WINAPI DllMain (HANDLE hInst,ULONG ul_reason_for_call,LPVOID lpReserved)
{
if(hModule==NULL){
//加載原WinSock庫,原WinSock庫已復(fù)制為wsock32.001。
hModule=LoadLibrary("wsock32.001");
}
else return 1;
//獲取原WinSock庫中的所有函數(shù)的地址并保存,下面僅列出部分代碼。
if(hModule!=NULL){
//獲取原WinSock庫初始化函數(shù)的地址,并保存到WSAStartup1中。
proc=GetProcAddress(hModule,"WSAStartup");
WSAStartup1=(int (_stdcall *)(WORD,LPWSADATA))proc;
//獲取原WinSock庫消除函數(shù)的地址,并保存到WSACleanup1中。
proc=GetProcAddress(hModule i,"WSACleanup");
WSACleanup1=(int (_stdcall *)())proc;
//獲取原創(chuàng)建Sock函數(shù)的地址,并保存到socket1中。
proc=GetProcAddress(hModule,"socket");
socket1=(SOCKET (_stdcall *)(int ,int,int))proc;
//獲取原創(chuàng)建連接函數(shù)的地址,并保存到connect1中。
proc=GetProcAddress(hModule,"connect");
connect1=(int (_stdcall *)(SOCKET ,const struct sockaddr *,int ))proc;
//獲取原發(fā)送函數(shù)的地址,并保存到send1中。
proc=GetProcAddress(hModule,"send");
send1=(int (_stdcall *)(SOCKET ,const char * ,int ,int ))proc;
//獲取原接收函數(shù)的地址,并保存到recv1中。
proc=GetProcAddress(hModule,"recv");
recv1=(int (_stdcall *)(SOCKET ,char FAR * ,int ,int ))proc;
......其它獲取函數(shù)地址代碼略。
}
else return 0;
return 1;
}
③ 定義庫輸出函數(shù),在此可以對(duì)我們感興趣的函數(shù)中添加外掛控制代碼,在所有的輸出函數(shù)的最后一步都調(diào)用原WinSock庫的同名函數(shù)。部分輸出函數(shù)定義代碼如下:
//庫輸出函數(shù)定義。
//WinSock初始化函數(shù)。
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
{
//調(diào)用原WinSock庫初始化函數(shù)
return WSAStartup1(wVersionRequired,lpWSAData);
}
//WinSock結(jié)束清除函數(shù)。
int PASCAL FAR WSACleanup(void)
{
return WSACleanup1(); //調(diào)用原WinSock庫結(jié)束清除函數(shù)。
}
//創(chuàng)建Socket函數(shù)。
SOCKET PASCAL FAR socket (int af, int type, int protocol)
{
//調(diào)用原WinSock庫創(chuàng)建Socket函數(shù)。
return socket1(af,type,protocol);
}
//發(fā)送數(shù)據(jù)包函數(shù)
int PASCAL FAR send(SOCKET s,const char * buf,int len,int flags)
{
//在此可以對(duì)發(fā)送的緩沖buf的內(nèi)容進(jìn)行修改,以實(shí)現(xiàn)欺騙服務(wù)器。
外掛代碼......
//調(diào)用原WinSock庫發(fā)送數(shù)據(jù)包函數(shù)。
return send1(s,buf,len,flags);
}
//接收數(shù)據(jù)包函數(shù)。
int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags)
{
//在此可以擋截到服務(wù)器端發(fā)送到客戶端的數(shù)據(jù)包,先將其保存到buffer中。
strcpy(buffer,buf);
//對(duì)buffer數(shù)據(jù)包數(shù)據(jù)進(jìn)行分析后,對(duì)其按照玩家的指令進(jìn)行相關(guān)修改。
外掛代碼......
//最后調(diào)用原WinSock中的接收數(shù)據(jù)包函數(shù)。
return recv1(s, buffer, len, flags);
}
.......其它函數(shù)定義代碼略。
(4)、新建wsock32.def配置文件,在其中加入所有庫輸出函數(shù)的聲明,部分聲明代碼如下:
LIBRARY "wsock32"
EXPORTS
WSAStartup @1
WSACleanup @2
recv @3
send @4
socket @5
bind @6
closesocket @7
connect @8
......其它輸出函數(shù)聲明代碼略。
(5)、從“工程”菜單中選擇“設(shè)置”,彈出Project Setting對(duì)話框,選擇Link標(biāo)簽,在“對(duì)象/庫模塊”中輸入Ws2_32.lib。
(6)、編譯項(xiàng)目,產(chǎn)生wsock32.dll庫文件。
(7)、將系統(tǒng)目錄下原wsock32.dll庫文件拷貝到被外掛程序的目錄下,并將其改名為wsock.001;再將上面產(chǎn)生的wsock32.dll文件同樣拷貝到被外掛程序的目錄下。重新啟動(dòng)
游戲程序,此時(shí)
游戲程序?qū)⑾燃虞d我們自己制作的wsock32.dll文件,再通過該庫文件間接調(diào)用原WinSock接口函數(shù)來實(shí)現(xiàn)訪問網(wǎng)絡(luò)。上面我們僅僅介紹了擋載WinSock的實(shí)現(xiàn)過程,至于如何加入外掛控制代碼,還需要外掛開發(fā)人員對(duì)
游戲數(shù)據(jù)包結(jié)構(gòu)、內(nèi)容、加密算法等方面的仔細(xì)分析(這個(gè)過程將是一個(gè)艱辛的過程),再生成外掛控制代碼。關(guān)于數(shù)據(jù)包分析方法和技巧,不是本文講解的范圍,如您感興趣可以到網(wǎng)上查查相關(guān)資料。
2.擋截API
擋截API
技術(shù)與擋截WinSock
技術(shù)在原理上很相似,但是前者比后者提供了更強(qiáng)大的功能。擋截WinSock僅只能擋截WinSock接口函數(shù),而擋截API可以實(shí)現(xiàn)對(duì)應(yīng)用程序調(diào)用的包括WinSock API函數(shù)在內(nèi)的所有API函數(shù)的擋截。如果您的外掛程序僅打算對(duì)WinSock的函數(shù)進(jìn)行擋截的話,您可以只選擇使用上小節(jié)介紹的擋截WinSock
技術(shù)。隨著大量外掛程序在功能上的擴(kuò)展,它們不僅僅只提供對(duì)數(shù)據(jù)包的擋截,而且還對(duì)
游戲程序中使用的
Windows API或其它DLL庫函數(shù)的擋截,以使外掛的功能更加強(qiáng)大。例如,可以通過擋截相關(guān)API函數(shù)以實(shí)現(xiàn)對(duì)非中文
游戲的漢化功能,有了這個(gè)利器,可以使您的外掛程序無所不能了。
擋截API
技術(shù)的原理核心也是使用我們自己的函數(shù)來替換掉
Windows或其它DLL庫提供的函數(shù),有點(diǎn)同擋截WinSock原理相似吧。但是,其實(shí)現(xiàn)過程卻比擋截WinSock要復(fù)雜的多,如像實(shí)現(xiàn)擋截Winsock過程一樣,將應(yīng)用程序調(diào)用的所有的庫文件都寫一個(gè)模擬庫有點(diǎn)不大可能,就只說
Windows API就有上
千個(gè),還有很多庫提供的函數(shù)結(jié)構(gòu)并未公開,所以寫一個(gè)模擬庫代替的方式不大現(xiàn)實(shí),故我們必須另謀良方。
擋截API的最終目標(biāo)是使用自定義的函數(shù)代替原函數(shù)。那么,我們首先應(yīng)該知道應(yīng)用程序何時(shí)、何地、用何種方式調(diào)用原函數(shù)。接下來,需要將應(yīng)用程序中調(diào)用該原函數(shù)的指令代碼進(jìn)行修改,使它將調(diào)用函數(shù)的指針指向我們自己定義的函數(shù)地址。這樣,外掛程序才能完全控制應(yīng)用程序調(diào)用的API函數(shù),至于在其中如何加入外掛代碼,就應(yīng)需求而異了。最后還有一個(gè)重要的問題要解決,如何將我們自定義的用來代替原API函數(shù)的函數(shù)代碼注入被外掛
游戲程序進(jìn)行地址空間中,因在
Windows系統(tǒng)中應(yīng)用程序僅只能訪問到本進(jìn)程地址空間內(nèi)的代碼和數(shù)據(jù)。
綜上所述,要實(shí)現(xiàn)擋截API函數(shù),至少需要解決如下三個(gè)問題:
● 如何定位
游戲程序中調(diào)用API函數(shù)指令代碼?
● 如何修改
游戲程序中調(diào)用API函數(shù)指令代碼?
● 如何將外掛代碼(自定義的替換函數(shù)代碼)注入到
游戲程序進(jìn)程地址空間?
下面我們逐一介紹這幾個(gè)問題的解決方法:
(1) 、定位調(diào)用API函數(shù)指令代碼
我們知道,在匯編語言中使用CALL指令來調(diào)用函數(shù)或過程的,它是通過指令參數(shù)中的函數(shù)地址而定位到相應(yīng)的函數(shù)代碼的。那么,我們?nèi)绻軐ふ业匠绦虼a中所有調(diào)用被擋截的API函數(shù)的CALL指令的話,就可以將該指令中的函數(shù)地址參數(shù)修改為替代函數(shù)的地址。雖然這是一個(gè)可行的方案,但是實(shí)現(xiàn)起來會(huì)很繁瑣,也不穩(wěn)健。慶幸的是,
Windows系統(tǒng)中所使用的可執(zhí)行文件(PE格式)采用了輸入地址表機(jī)制,將所有在程序調(diào)用的API函數(shù)的地址信息存放在輸入地址表中,而在程序代碼CALL指令中使用的地址不是API函數(shù)的地址,而是輸入地址表中該API函數(shù)的地址項(xiàng),如想使程序代碼中調(diào)用的API函數(shù)被代替掉,只用將輸入地址表中該API函數(shù)的地址項(xiàng)內(nèi)容修改即可。具體理解輸入地址表運(yùn)行機(jī)制,還需要了解一下PE格式文件結(jié)構(gòu),其中圖三列出了PE格式文件的大致結(jié)構(gòu)。

圖三:PE格式大致結(jié)構(gòu)圖
PE格式文件一開始是一段DOS程序,當(dāng)你的程序在不支持
Windows的環(huán)境中運(yùn)行時(shí),它就會(huì)顯示“This Program cannot be run in DOS mode”這樣的警告語句,接著這個(gè)DOS文件頭,就開始真正的PE文件內(nèi)容了。首先是一段稱為“IMAGE_NT_HEADER”的數(shù)據(jù),其中是許多關(guān)于整個(gè)PE文件的消息,在這段數(shù)據(jù)的尾端是一個(gè)稱為Data Directory的數(shù)據(jù)表,通過它能快速定位一些PE文件中段(section)的地址。在這段數(shù)據(jù)之后,則是一個(gè)“IMAGE_SECTION_HEADER”的列表,其中的每一項(xiàng)都詳細(xì)描述了后面一個(gè)段的相關(guān)信息。接著它就是PE文件中最主要的段數(shù)據(jù)了,執(zhí)行代碼、數(shù)據(jù)和資源等等信息就分別存放在這些段中。
在所有的這些段里,有一個(gè)被稱為“.idata”的段(輸入數(shù)據(jù)段)值得我們?nèi)プ⒁猓摱沃邪恍┍环Q為輸入地址表(IAT,Import Address Table)的數(shù)據(jù)列表。每個(gè)用隱式方式加載的API所在的DLL都有一個(gè)IAT與之對(duì)應(yīng),同時(shí)一個(gè)API的地址也與IAT中一項(xiàng)相對(duì)應(yīng)。當(dāng)一個(gè)應(yīng)用程序加載到內(nèi)存中后,針對(duì)每一個(gè)API函數(shù)調(diào)用,相應(yīng)的產(chǎn)生如下的匯編指令:
JMP DWORD PTR [XXXXXXXX]
或
CALL DWORD PTR [XXXXXXXX]
其中,[XXXXXXXX]表示指向了輸入地址表中一個(gè)項(xiàng),其內(nèi)容是一個(gè)DWORD,而正是這個(gè)DWORD才是API函數(shù)在內(nèi)存中的真正地址。因此我們要想攔截一個(gè)API的調(diào)用,只要簡(jiǎn)單的把那個(gè)DWORD改為我們自己的函數(shù)的地址。
(2) 、修改調(diào)用API函數(shù)代碼
從上面對(duì)PE文件格式的分析可知,修改調(diào)用API函數(shù)代碼其實(shí)是修改被調(diào)用API函數(shù)在輸入地址表中IAT項(xiàng)內(nèi)容。由于
Windows系統(tǒng)對(duì)應(yīng)用程序指令代碼地址空間的嚴(yán)密保護(hù)機(jī)制,使得修改程序指令代碼非常困難,以至于許多高手為之編寫VxD進(jìn)入Ring0。在這里,我為大家介紹一種較為方便的方法修改進(jìn)程內(nèi)存,它僅需要調(diào)用幾個(gè)
Windows核心API函數(shù),下面我首先來學(xué)會(huì)一下這幾個(gè)API函數(shù):
DWORD VirtualQuery(
LPCVOID lpAddress, // address of region
PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer
DWORD dwLength // size of buffer
);
該函數(shù)用于查詢關(guān)于本進(jìn)程內(nèi)虛擬地址頁的信息。其中,lpAddress表示被查詢頁的區(qū)域地址;lpBuffer表示用于保存查詢頁信息的緩沖;dwLength表示緩沖區(qū)大小。返回值為實(shí)際緩沖大小。
BOOL VirtualProtect(
LPVOID lpAddress, // region of committed pages
SIZE_T dwSize, // size of the region
DWORD flNewProtect, // desired access protection
PDWORD lpflOldProtect // old protection
);
該函數(shù)用于改變本進(jìn)程內(nèi)虛擬地址頁的保護(hù)屬性。其中,lpAddress表示被改變保護(hù)屬性頁區(qū)域地址;dwSize表示頁區(qū)域大小;flNewProtect表示新的保護(hù)屬性,可取值為PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE等;lpflOldProtect表示用于保存改變前的保護(hù)屬性。如果函數(shù)調(diào)用成功返回“T”,否則返回“F”。
有了這兩個(gè)API函數(shù),我們就可以隨心所欲的修改進(jìn)程內(nèi)存了。首先,調(diào)用VirtualQuery()函數(shù)查詢被修改內(nèi)存的頁信息,再根據(jù)此信息調(diào)用VirtualProtect()函數(shù)改變這些頁的保護(hù)屬性為PAGE_READWRITE,有了這個(gè)權(quán)限您就可以任意修改進(jìn)程內(nèi)存數(shù)據(jù)了。下面一段代碼演示了如何將進(jìn)程虛擬地址為0x0040106c處的字節(jié)清零。
BYTE* pData = 0x0040106c;
MEMORY_BASIC_INFORMATION mbi_thunk;
//查詢頁信息。
VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
//改變頁保護(hù)屬性為讀寫。
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
PAGE_READWRITE, &mbi_thunk.Protect);
//清零。
*pData = 0x00;
//恢復(fù)頁的原保護(hù)屬性。
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
mbi_thunk.Protect, &dwOldProtect);
(3)、注入外掛代碼進(jìn)入被掛
游戲進(jìn)程中
完成了定位和修改程序中調(diào)用API函數(shù)代碼后,我們就可以隨意設(shè)計(jì)自定義的API函數(shù)的替代函數(shù)了。做完這一切后,還需要將這些代碼注入到被外掛
游戲程序進(jìn)程內(nèi)存空間中,不然
游戲進(jìn)程根本不會(huì)訪問到替代函數(shù)代碼。注入方法有很多,如利用全局鉤子注入、利用注冊(cè)表注入擋截User32庫中的API函數(shù)、利用CreateRemoteThread注入(僅限于NT/2000)、利用BHO注入等。因?yàn)槲覀冊(cè)趧?dòng)作模擬
技術(shù)一節(jié)已經(jīng)接觸過全局鉤子,我相信聰明的讀者已經(jīng)完全掌握了全局鉤子的制作過程,所以我們?cè)诤竺娴膶?shí)例中,將繼續(xù)利用這個(gè)全局鉤子。至于其它幾種注入方法,如果感興趣可參閱MSDN有關(guān)內(nèi)容。
有了以上理論基礎(chǔ),我們下面就開始制作一個(gè)擋截MessageBoxA和recv函數(shù)的實(shí)例,在開發(fā)
游戲外掛程序 時(shí),可以此實(shí)例為框架,加入相應(yīng)的替代函數(shù)和處理代碼即可。此實(shí)例的開發(fā)過程如下:
(1) 打開前面創(chuàng)建的ActiveKey項(xiàng)目。
(2) 在ActiveKey.h文件中加入HOOKAPI結(jié)構(gòu),此結(jié)構(gòu)用來存儲(chǔ)被擋截API函數(shù)名稱、原API函數(shù)地址和替代函數(shù)地址。
typedef struct tag_HOOKAPI
{
LPCSTR szFunc;//被HOOK的API函數(shù)名稱。
PROC pNewProc;//替代函數(shù)地址。
PROC pOldProc;//原API函數(shù)地址。
}HOOKAPI, *LPHOOKAPI;
(3) 打開ActiveKey.cpp文件,首先加入一個(gè)函數(shù),用于定位輸入庫在輸入數(shù)據(jù)段中的IAT地址。代碼如下:
extern "C" __declspec(dllexport)PIMAGE_IMPORT_DESCRIPTOR
LocationIAT(HMODULE hModule, LPCSTR szImportMod)
//其中,hModule為進(jìn)程模塊句柄;szImportMod為輸入庫名稱。
{
//檢查是否為DOS程序,如是返回NULL,因DOS程序沒有IAT。
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;
if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
//檢查是否為NT標(biāo)志,否則返回NULL。
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader->e_lfanew));
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return NULL;
//沒有IAT表則返回NULL。
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0) return NULL;
//定位第一個(gè)IAT位置。
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
//根據(jù)輸入庫名稱循環(huán)檢查所有的IAT,如匹配則返回該IAT地址,否則檢測(cè)下一個(gè)IAT。
while (pImportDesc->Name)
{
//獲取該IAT描述的輸入庫名稱。
PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc->Name));
if (stricmp(szCurrMod, szImportMod) == 0) break;
pImportDesc++;
}
if(pImportDesc->Name == NULL) return NULL;
return pImportDesc;
}
再加入一個(gè)函數(shù),用來定位被擋截API函數(shù)的IAT項(xiàng)并修改其內(nèi)容為替代函數(shù)地址。代碼如下:
extern "C" __declspec(dllexport)
HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi)
//其中,hModule為進(jìn)程模塊句柄;szImportMod為輸入庫名稱;pHookAPI為HOOKAPI結(jié)構(gòu)指針。
{
//定位szImportMod輸入庫在輸入數(shù)據(jù)段中的IAT地址。
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod);
if (pImportDesc == NULL) return FALSE;
//第一個(gè)Thunk地址。
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
//第一個(gè)IAT項(xiàng)的Thunk地址。
PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk));
//循環(huán)查找被截API函數(shù)的IAT項(xiàng),并使用替代函數(shù)地址修改其值。
while(pOrigThunk->u1.Function)
{
//檢測(cè)此Thunk是否為IAT項(xiàng)。
if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
//獲取此IAT項(xiàng)所描述的函數(shù)名稱。
PIMAGE_IMPORT_BY_NAME pByName =(PIMAGE_IMPORT_BY_NAME)((DWORD)hModule+(DWORD)(pOrigThunk->u1.AddressOfData));
if(pByName->Name[0] == '\0') return FALSE;
//檢測(cè)是否為擋截函數(shù)。
if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0)
{
MEMORY_BASIC_INFORMATION mbi_thunk;
//查詢修改頁的信息。
VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
//改變修改頁保護(hù)屬性為PAGE_READWRITE。
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
//保存原來的API函數(shù)地址。
if(pHookApi->pOldProc == NULL)
pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
//修改API函數(shù)IAT項(xiàng)內(nèi)容為替代函數(shù)地址。
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//恢復(fù)修改頁保護(hù)屬性。
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect);
}
}
pOrigThunk++;
pRealThunk++;
}
SetLastError(ERROR_SUCCESS); //設(shè)置錯(cuò)誤為ERROR_SUCCESS,表示成功。
return TRUE;
}
(4) 定義替代函數(shù),此實(shí)例中只給MessageBoxA和recv兩個(gè)API進(jìn)行擋截。代碼如下:
static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
//過濾掉原MessageBoxA的正文和標(biāo)題內(nèi)容,只顯示如下內(nèi)容。
return MessageBox(hWnd, "Hook API OK!", "Hook API", uType);
}
static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags )
{
//此處可以擋截
游戲服務(wù)器發(fā)送來的網(wǎng)絡(luò)數(shù)據(jù)包,可以加入分析和處理數(shù)據(jù)代碼。
return recv(s,buf,len,flags);
}
(5) 在KeyboardProc函數(shù)中加入激活擋截API代碼,在if( wParam == 0X79 )語句中后面加入如下else if語句:
......
//當(dāng)激活F11鍵時(shí),啟動(dòng)擋截API函數(shù)功能。
else if( wParam == 0x7A )
{
HOOKAPI api[2];
api[0].szFunc ="MessageBoxA";//設(shè)置被擋截函數(shù)的名稱。
api[0].pNewProc = (PROC)MessageBoxA1;//設(shè)置替代函數(shù)的地址。
api[1].szFunc ="recv";//設(shè)置被擋截函數(shù)的名稱。
api[1].pNewProc = (PROC)recv1; //設(shè)置替代函數(shù)的地址。
//設(shè)置擋截User32.dll庫中的MessageBoxA函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"User32.dll",&api[0]);
//設(shè)置擋截Wsock32.dll庫中的recv函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&api[1]);
}
......
(6) 在ActiveKey.cpp中加入頭文件聲明 "#include "wsock32.h"。 從“工程”菜單中選擇“設(shè)置”,彈出Project Setting對(duì)話框,選擇Link標(biāo)簽,在“對(duì)象/庫模塊”中輸入Ws2_32..lib。
(7) 重新編譯ActiveKey項(xiàng)目,產(chǎn)生ActiveKey.dll文件,將其拷貝到Simulate.exe目錄下。運(yùn)行Simulate.exe并啟動(dòng)全局鉤子。激活任意應(yīng)用程序,按F11鍵后,運(yùn)行此程序中可能調(diào)用MessageBoxA函數(shù)的操作,看看信息框是不是有所變化。同樣,如此程序正在接收網(wǎng)絡(luò)數(shù)據(jù)包,就可以實(shí)現(xiàn)封包功能了。
六、結(jié)束語
除了以上介紹的幾種
游戲外掛程序常用的
技術(shù)以外,在一些外掛程序中還使用了
游戲數(shù)據(jù)修改
技術(shù)、
游戲加速
技術(shù)等。在這篇文章里,就不逐一介紹了。