通過對動作模擬技術的介紹,我們對游戲外掛有了一定程度上的認識,也學會了使用動作模擬技術來實現(xiàn)簡單的動作模擬型游戲外掛的制作。這種動作模擬型游戲外掛有一定的局限性,它僅僅只能解決使用計算機代替人力完成那么有規(guī)律、繁瑣而無聊的游戲動作。但是,隨著網(wǎng)絡游戲的盛行和復雜度的增加,很多游戲要求將客戶端動作信息及時反饋回服務器,通過服務器對這些動作信息進行有效認證后,再向客戶端發(fā)送下一步游戲動作信息,這樣動作模擬技術將失去原有的效應。為了更好地 “ 外掛 ” 這些游戲,游戲外掛程序也進行了升級換代,它們將以前針對游戲用戶界面層的模擬推進到數(shù)據(jù)通訊層,通過封包技術在客戶端擋截游戲服務器發(fā)送來的游戲控制數(shù)據(jù)包,分析數(shù)據(jù)包并修改數(shù)據(jù)包;同時還需按照游戲數(shù)據(jù)包結構創(chuàng)建數(shù)據(jù)包,再模擬客戶端發(fā)送給游戲服務器,這個過程其實就是一個封包的過程。
封包的技術是實現(xiàn)第二類游戲外掛的最核心的技術。封包技術涉及的知識很廣泛,實現(xiàn)方法也很多,如擋截 WinSock 、擋截 API 函數(shù)、擋截消息、 VxD 驅動程序等。在此我們也不可能在此文中將所有的封包技術都進行詳細介紹,故選擇兩種在游戲外掛程序中最常用的兩種方法:擋截 WinSock 和擋截 API 函數(shù)。
1 . 擋截WinSock
眾所周知, Winsock 是 Windows 網(wǎng)絡編程接口,它工作于 Windows 應用層,它提供與底層傳輸協(xié)議無關的高層數(shù)據(jù)傳輸編程接口。在 Windows 系統(tǒng)中,使用 WinSock 接口為應用程序提供基于 TCP/IP 協(xié)議的網(wǎng)絡訪問服務,這些服務是由 Wsock32.DLL 動態(tài)鏈接庫提供的函數(shù)庫來完成的。
由上說明可知,任何 Windows 基于 TCP/IP 的應用程序都必須通過 WinSock 接口訪問網(wǎng)絡,當然網(wǎng)絡游戲程序也不例外。由此我們可以想象一下,如果我們可以控制 WinSock 接口的話,那么控制游戲客戶端程序與服務器之間的數(shù)據(jù)包也將易如反掌。按著這個思路,下面的工作就是如何完成控制 WinSock 接口了。由上面的介紹可知, WinSock 接口其實是由一個動態(tài)鏈接庫提供的一系列函數(shù),由這些函數(shù)實現(xiàn)對網(wǎng)絡的訪問。有了這層的認識,問題就好辦多了,我們可以制作一個類似的動態(tài)鏈接庫來代替原 WinSock 接口庫,在其中實現(xiàn) WinSock32.dll 中實現(xiàn)的所有函數(shù),并保證所有函數(shù)的參數(shù)個數(shù)和順序、返回值類型都應與原庫相同。在這個自制作的動態(tài)庫中,可以對我們感興趣的函數(shù)(如發(fā)送、接收等函數(shù))進行擋截,放入外掛控制代碼,最后還繼續(xù)調(diào)用原 WinSock 庫中提供的相應功能函數(shù),這樣就可以實現(xiàn)對網(wǎng)絡數(shù)據(jù)包的擋截、修改和發(fā)送等封包功能。
下面重點介紹創(chuàng)建擋截 WinSock 外掛程序的基本步驟:
(1) 創(chuàng)建 DLL 項目,選擇 Win32 Dynamic-Link Library ,再選擇 An empty DLL project 。
(2) 新建文件 wsock32.h ,按如下步驟輸入代碼:
① 加入相關變量聲明:
HMODULE hModule=NULL; // 模塊句柄
char buffer[1000]; // 緩沖區(qū)
FARPROC proc; // 函數(shù)入口指針
② 定義指向原WinSock庫中的所有函數(shù)地址的指針變量,因WinSock庫共提供70多個函數(shù),限于篇幅,在此就只選擇幾個常用的函數(shù)列出,有關這些庫函數(shù)的說明可參考MSDN相關內(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 文件,按如下步驟輸入代碼:
① 加入相關頭文件聲明:
#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 庫已復制為 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ù),在此可以對我們感興趣的函數(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 結束清除函數(shù)。
int PASCAL FAR WSACleanup(void)
{
return WSACleanup1(); // 調(diào)用原 WinSock 庫結束清除函數(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)
{
// 在此可以對發(fā)送的緩沖 buf 的內(nèi)容進行修改,以實現(xiàn)欺騙服務器。
外掛代碼 ......
// 調(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)
{
// 在此可以擋截到服務器端發(fā)送到客戶端的數(shù)據(jù)包,先將其保存到 buffer 中。
strcpy(buffer,buf);
// 對 buffer 數(shù)據(jù)包數(shù)據(jù)進行分析后,對其按照玩家的指令進行相關修改。
外掛代碼 ......
// 最后調(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) 、從 “ 工程 ” 菜單中選擇 “ 設置 ” ,彈出 Project Setting 對話框,選擇 Link 標簽,在 “ 對象 / 庫模塊 ” 中輸入 Ws2_32.lib 。
(6) 、編譯項目,產(chǎn)生 wsock32.dll 庫文件。
(7) 、將系統(tǒng)目錄下原 wsock32.dll 庫文件拷貝到被外掛程序的目錄下,并將其改名為 wsock.001 ;再將上面產(chǎn)生的 wsock32.dll 文件同樣拷貝到被外掛程序的目錄下。重新啟動游戲程序,此時游戲程序將先加載我們自己制作的 wsock32.dll 文件,再通過該庫文件間接調(diào)用原 WinSock 接口函數(shù)來實現(xiàn)訪問網(wǎng)絡。上面我們僅僅介紹了擋載 WinSock 的實現(xiàn)過程,至于如何加入外掛控制代碼,還需要外掛開發(fā)人員對游戲數(shù)據(jù)包結構、內(nèi)容、加密算法等方面的仔細分析(這個過程將是一個艱辛的過程),再生成外掛控制代碼。關于數(shù)據(jù)包分析方法和技巧,不是本文講解的范圍,如您感興趣可以到網(wǎng)上查查相關資料。
2. 擋截 API
擋截 API 技術與擋截 WinSock 技術在原理上很相似,但是前者比后者提供了更強大的功能。擋截 WinSock 僅只能擋截 WinSock 接口函數(shù),而擋截 API 可以實現(xiàn)對應用程序調(diào)用的包括 WinSock API 函數(shù)在內(nèi)的所有 API 函數(shù)的擋截。如果您的外掛程序僅打算對 WinSock 的函數(shù)進行擋截的話,您可以只選擇使用上小節(jié)介紹的擋截 WinSock 技術。隨著大量外掛程序在功能上的擴展,它們不僅僅只提供對數(shù)據(jù)包的擋截,而且還對游戲程序中使用的 Windows API 或其它 DLL 庫函數(shù)的擋截,以使外掛的功能更加強大。例如,可以通過擋截相關 API 函數(shù)以實現(xiàn)對非中文游戲的漢化功能,有了這個利器,可以使您的外掛程序無所不能了。
擋截 API 技術的原理核心也是使用我們自己的函數(shù)來替換掉 Windows 或其它 DLL 庫提供的函數(shù),有點同擋截 WinSock 原理相似吧。但是,其實現(xiàn)過程卻比擋截 WinSock 要復雜的多,如像實現(xiàn)擋截 Winsock 過程一樣,將應用程序調(diào)用的所有的庫文件都寫一個模擬庫有點不大可能,就只說 Windows API 就有上千個,還有很多庫提供的函數(shù)結構并未公開,所以寫一個模擬庫代替的方式不大現(xiàn)實,故我們必須另謀良方。
擋截 API 的最終目標是使用自定義的函數(shù)代替原函數(shù)。那么,我們首先應該知道應用程序何時、何地、用何種方式調(diào)用原函數(shù)。接下來,需要將應用程序中調(diào)用該原函數(shù)的指令代碼進行修改,使它將調(diào)用函數(shù)的指針指向我們自己定義的函數(shù)地址。這樣,外掛程序才能完全控制應用程序調(diào)用的 API 函數(shù),至于在其中如何加入外掛代碼,就應需求而異了。最后還有一個重要的問題要解決,如何將我們自定義的用來代替原 API 函數(shù)的函數(shù)代碼注入被外掛游戲程序進行地址空間中,因在 Windows 系統(tǒng)中應用程序僅只能訪問到本進程地址空間內(nèi)的代碼和數(shù)據(jù)。
綜上所述,要實現(xiàn)擋截 API 函數(shù),至少需要解決如下三個問題:
● 如何定位游戲程序中調(diào)用 API 函數(shù)指令代碼?
● 如何修改游戲程序中調(diào)用 API 函數(shù)指令代碼?
● 如何將外掛代碼(自定義的替換函數(shù)代碼)注入到游戲程序進程地址空間?
下面我們逐一介紹這幾個問題的解決方法:
(1) 、定位調(diào)用 API 函數(shù)指令代碼
我們知道,在匯編語言中使用 CALL 指令來調(diào)用函數(shù)或過程的,它是通過指令參數(shù)中的函數(shù)地址而定位到相應的函數(shù)代碼的。那么,我們?nèi)绻軐ふ业匠绦虼a中所有調(diào)用被擋截的 API 函數(shù)的 CALL 指令的話,就可以將該指令中的函數(shù)地址參數(shù)修改為替代函數(shù)的地址。雖然這是一個可行的方案,但是實現(xiàn)起來會很繁瑣,也不穩(wěn)健。慶幸的是, Windows 系統(tǒng)中所使用的可執(zhí)行文件( PE 格式)采用了輸入地址表機制,將所有在程序調(diào)用的 API 函數(shù)的地址信息存放在輸入地址表中,而在程序代碼 CALL 指令中使用的地址不是 API 函數(shù)的地址,而是輸入地址表中該 API 函數(shù)的地址項,如想使程序代碼中調(diào)用的 API 函數(shù)被代替掉,只用將輸入地址表中該 API 函數(shù)的地址項內(nèi)容修改即可。具體理解輸入地址表運行機制,還需要了解一下 PE 格式文件結構,其中圖三列出了 PE 格式文件的大致結構。
圖三: PE 格式大致結構圖 (003.jpg)
PE 格式文件一開始是一段 DOS 程序,當你的程序在不支持 Windows 的環(huán)境中運行時,它就會顯示 “This Program cannot be run in DOS mode” 這樣的警告語句,接著這個 DOS 文件頭,就開始真正的 PE 文件內(nèi)容了。首先是一段稱為 “IMAGE_NT_HEADER” 的數(shù)據(jù),其中是許多關于整個 PE 文件的消息,在這段數(shù)據(jù)的尾端是一個稱為 Data Directory 的數(shù)據(jù)表,通過它能快速定位一些 PE 文件中段( section )的地址。在這段數(shù)據(jù)之后,則是一個 “IMAGE_SECTION_HEADER” 的列表,其中的每一項都詳細描述了后面一個段的相關信息。接著它就是 PE 文件中最主要的段數(shù)據(jù)了,執(zhí)行代碼、數(shù)據(jù)和資源等等信息就分別存放在這些段中。
在所有的這些段里,有一個被稱為 “.idata” 的段(輸入數(shù)據(jù)段)值得我們?nèi)プ⒁猓摱沃邪恍┍环Q為輸入地址表( IAT , Import Address Table )的數(shù)據(jù)列表。每個用隱式方式加載的 API 所在的 DLL 都有一個 IAT 與之對應,同時一個 API 的地址也與 IAT 中一項相對應。當一個應用程序加載到內(nèi)存中后,針對每一個 API 函數(shù)調(diào)用,相應的產(chǎn)生如下的匯編指令:
JMP DWORD PTR [XXXXXXXX]
或
CALL DWORD PTR [XXXXXXXX]
其中, [XXXXXXXX] 表示指向了輸入地址表中一個項,其內(nèi)容是一個 DWORD ,而正是這個 DWORD 才是 API 函數(shù)在內(nèi)存中的真正地址。因此我們要想攔截一個 API 的調(diào)用,只要簡單的把那個 DWORD 改為我們自己的函數(shù)的地址。
(2) 、修改調(diào)用 API 函數(shù)代碼
從上面對 PE 文件格式的分析可知,修改調(diào)用 API 函數(shù)代碼其實是修改被調(diào)用 API 函數(shù)在輸入地址表中 IAT 項內(nèi)容。由于 Windows 系統(tǒng)對應用程序指令代碼地址空間的嚴密保護機制,使得修改程序指令代碼非常困難,以至于許多高手為之編寫 VxD 進入 Ring0 。在這里,我為大家介紹一種較為方便的方法修改進程內(nèi)存,它僅需要調(diào)用幾個 Windows 核心 API 函數(shù),下面我首先來學會一下這幾個 API 函數(shù):
DWORD VirtualQuery(
LPCVOID lpAddress, // address of region
PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer
DWORD dwLength // size of buffer
);
該函數(shù)用于查詢關于本進程內(nèi)虛擬地址頁的信息。其中, lpAddress 表示被查詢頁的區(qū)域地址; lpBuffer 表示用于保存查詢頁信息的緩沖; dwLength 表示緩沖區(qū)大小。返回值為實際緩沖大小。
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ù)用于改變本進程內(nèi)虛擬地址頁的保護屬性。其中, lpAddress 表示被改變保護屬性頁區(qū)域地址; dwSize 表示頁區(qū)域大小; flNewProtect 表示新的保護屬性,可取值為 PAGE_READONLY 、 PAGE_READWRITE 、 PAGE_EXECUTE 等; lpflOldProtect 表示用于保存改變前的保護屬性。如果函數(shù)調(diào)用成功返回 “T” ,否則返回 “F” 。
有了這兩個 API 函數(shù),我們就可以隨心所欲的修改進程內(nèi)存了。首先,調(diào)用 VirtualQuery() 函數(shù)查詢被修改內(nèi)存的頁信息,再根據(jù)此信息調(diào)用 VirtualProtect() 函數(shù)改變這些頁的保護屬性為 PAGE_READWRITE ,有了這個權限您就可以任意修改進程內(nèi)存數(shù)據(jù)了。下面一段代碼演示了如何將進程虛擬地址為 0x0040106c 處的字節(jié)清零。
BYTE* pData = 0x0040106c;
MEMORY_BASIC_INFORMATION mbi_thunk;
// 查詢頁信息。
VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
// 改變頁保護屬性為讀寫。
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
PAGE_READWRITE, &mbi_thunk.Protect);
// 清零。
*pData = 0x00;
// 恢復頁的原保護屬性。
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
mbi_thunk.Protect, &dwOldProtect);
(3) 、注入外掛代碼進入被掛游戲進程中
完成了定位和修改程序中調(diào)用 API 函數(shù)代碼后,我們就可以隨意設計自定義的 API 函數(shù)的替代函數(shù)了。做完這一切后,還需要將這些代碼注入到被外掛游戲程序進程內(nèi)存空間中,不然游戲進程根本不會訪問到替代函數(shù)代碼。注入方法有很多,如利用全局鉤子注入、利用注冊表注入擋截 User32 庫中的 API 函數(shù)、利用 CreateRemoteThread 注入(僅限于 NT/2000 )、利用 BHO 注入等。因為我們在動作模擬技術一節(jié)已經(jīng)接觸過全局鉤子,我相信聰明的讀者已經(jīng)完全掌握了全局鉤子的制作過程,所以我們在后面的實例中,將繼續(xù)利用這個全局鉤子。至于其它幾種注入方法,如果感興趣可參閱 MSDN 有關內(nèi)容。
有了以上理論基礎,我們下面就開始制作一個擋截 MessageBoxA 和 recv 函數(shù)的實例,在開發(fā)游戲外掛程序 時,可以此實例為框架,加入相應的替代函數(shù)和處理代碼即可。此實例的開發(fā)過程如下:
(1) 打開前面創(chuàng)建的 ActiveKey 項目。
(2) 在 ActiveKey.h 文件中加入 HOOKAPI 結構,此結構用來存儲被擋截 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 文件,首先加入一個函數(shù),用于定位輸入庫在輸入數(shù)據(jù)段中的 IAT 地址。代碼如下:
extern "C" __declspec(dllexport)PIMAGE_IMPORT_DESCRIPTOR
LocationIAT(HMODULE hModule, LPCSTR szImportMod)
// 其中, hModule 為進程模塊句柄; szImportMod 為輸入庫名稱。
{
// 檢查是否為 DOS 程序,如是返回 NULL ,因 DOS 程序沒有 IAT 。
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;
if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
// 檢查是否為 NT 標志,否則返回 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;
// 定位第一個 IAT 位置。
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
// 根據(jù)輸入庫名稱循環(huán)檢查所有的 IAT ,如匹配則返回該 IAT 地址,否則檢測下一個 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;
}
再加入一個函數(shù),用來定位被擋截 API 函數(shù)的 IAT 項并修改其內(nèi)容為替代函數(shù)地址。代碼如下:
extern "C" __declspec(dllexport)
HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi)
// 其中, hModule 為進程模塊句柄; szImportMod 為輸入庫名稱; pHookAPI 為 HOOKAPI 結構指針。
{
// 定位 szImportMod 輸入庫在輸入數(shù)據(jù)段中的 IAT 地址。
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod);
if (pImportDesc == NULL) return FALSE;
// 第一個 Thunk 地址。
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
// 第一個 IAT 項的 Thunk 地址。
PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk));
// 循環(huán)查找被截 API 函數(shù)的 IAT 項,并使用替代函數(shù)地址修改其值。
while(pOrigThunk->u1.Function)
{
// 檢測此 Thunk 是否為 IAT 項。
if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
// 獲取此 IAT 項所描述的函數(shù)名稱。
PIMAGE_IMPORT_BY_NAME pByName =(PIMAGE_IMPORT_BY_NAME)((DWORD)hModule+(DWORD)(pOrigThunk->u1.AddressOfData));
if(pByName->Name[0] == ‘\\0‘) return FALSE;
// 檢測是否為擋截函數(shù)。
if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0)
{
MEMORY_BASIC_INFORMATION mbi_thunk;
// 查詢修改頁的信息。
VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
// 改變修改頁保護屬性為 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項內(nèi)容為替代函數(shù)地址。
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//恢復修改頁保護屬性。
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect);
}
}
pOrigThunk++;
pRealThunk++;
}
SetLastError(ERROR_SUCCESS); //設置錯誤為ERROR_SUCCESS,表示成功。
return TRUE;
}
(4) 定義替代函數(shù),此實例中只給 MessageBoxA 和 recv 兩個 API 進行擋截。代碼如下:
static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
// 過濾掉原 MessageBoxA 的正文和標題內(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 )
{
// 此處可以擋截游戲服務器發(fā)送來的網(wǎng)絡數(shù)據(jù)包,可以加入分析和處理數(shù)據(jù)代碼。
return recv(s,buf,len,flags);
}
(5) 在 KeyboardProc 函數(shù)中加入激活擋截 API 代碼,在 if( wParam == 0X79 ) 語句中后面加入如下 else if 語句:
......
// 當激活 F11 鍵時,啟動擋截 API 函數(shù)功能。
else if( wParam == 0x7A )
{
HOOKAPI api[2];
api[0].szFunc ="MessageBoxA";// 設置被擋截函數(shù)的名稱。
api[0].pNewProc = (PROC)MessageBoxA1;// 設置替代函數(shù)的地址。
api[1].szFunc ="recv";// 設置被擋截函數(shù)的名稱。
api[1].pNewProc = (PROC)recv1; // 設置替代函數(shù)的地址。
// 設置擋截 User32.dll 庫中的 MessageBoxA 函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"User32.dll",&api[0]);
// 設置擋截 Wsock32.dll 庫中的 recv 函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&api[1]);
}
......
(6) 在 ActiveKey.cpp 中加入頭文件聲明 "#include "wsock32.h" 。 從“工程”菜單中選擇“設置”,彈出Project Setting對話框,選擇Link標簽,在“對象/庫模塊”中輸入Ws2_32..lib。
(7) 重新編譯 ActiveKey 項目,產(chǎn)生 ActiveKey.dll 文件,將其拷貝到 Simulate.exe 目錄下。運行 Simulate.exe 并啟動全局鉤子。激活任意應用程序,按 F11 鍵后,運行此程序中可能調(diào)用 MessageBoxA 函數(shù)的操作,看看信息框是不是有所變化。同樣,如此程序正在接收網(wǎng)絡數(shù)據(jù)包,就可以實現(xiàn)封包功能了。