通過對動作模擬技術的介紹,我們對游戲外掛有了一定程度上的認識,也學會了使用動作模擬技術來實現(xiàn)簡單的動作模擬型游戲外掛的制作。這種動作模擬型游戲外掛有一定的局限性,它僅僅只能解決使用計算機代替人力完成那么有規(guī)律、繁瑣而無聊的游戲動作。但是,隨著網絡游戲的盛行和復雜度的增加,很多游戲要求將客戶端動作信息及時反饋回服務器,通過服務器對這些動作信息進行有效認證后,再向客戶端發(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
網絡編程接口,它工作于
Windows
應用層,它提供與底層傳輸協(xié)議無關的高層數(shù)據(jù)傳輸編程接口。在
Windows
系統(tǒng)中,使用
WinSock
接口為應用程序提供基于
TCP/IP
協(xié)議的網絡訪問服務,這些服務是由
Wsock32.DLL
動態(tài)鏈接庫提供的函數(shù)庫來完成的。
由上說明可知,任何
Windows
基于
TCP/IP
的應用程序都必須通過
WinSock
接口訪問網絡,當然網絡游戲程序也不例外。由此我們可以想象一下,如果我們可以控制
WinSock
接口的話,那么控制游戲客戶端程序與服務器之間的數(shù)據(jù)包也將易如反掌。按著這個思路,下面的工作就是如何完成控制
WinSock
接口了。由上面的介紹可知,
WinSock
接口其實是由一個動態(tài)鏈接庫提供的一系列函數(shù),由這些函數(shù)實現(xiàn)對網絡的訪問。有了這層的認識,問題就好辦多了,我們可以制作一個類似的動態(tài)鏈接庫來代替原
WinSock
接口庫,在其中實現(xiàn)
WinSock32.dll
中實現(xiàn)的所有函數(shù),并保證所有函數(shù)的參數(shù)個數(shù)和順序、返回值類型都應與原庫相同。在這個自制作的動態(tài)庫中,可以對我們感興趣的函數(shù)(如發(fā)送、接收等函數(shù))進行擋截,放入外掛控制代碼,最后還繼續(xù)調用原
WinSock
庫中提供的相應功能函數(shù),這樣就可以實現(xiàn)對網絡數(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相關內容。
//
定義指向原
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ù)的最后一步都調用原WinSock庫的同名函數(shù)。部分輸出函數(shù)定義代碼如下:
//
庫輸出函數(shù)定義。
//WinSock
初始化函數(shù)。
int?PASCAL?FAR?WSAStartup(WORD?wVersionRequired,?LPWSADATA?lpWSAData)
{
//
調用原
WinSock
庫初始化函數(shù)
return?WSAStartup1(wVersionRequired,lpWSAData);
}
//WinSock
結束清除函數(shù)。
int?PASCAL?FAR?WSACleanup(void)
{
return?WSACleanup1();?//
調用原
WinSock
庫結束清除函數(shù)。
}
//
創(chuàng)建
Socket
函數(shù)。
SOCKET?PASCAL?FAR?socket?(int?af,?int?type,?int?protocol)
{
//
調用原
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
的內容進行修改,以實現(xiàn)欺騙服務器。
外掛代碼
......
//
調用原
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ù)進行分析后,對其按照玩家的指令進行相關修改。
外掛代碼
......
//
最后調用原
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)
、編譯項目,產生
wsock32.dll
庫文件。
(7)
、將系統(tǒng)目錄下原
wsock32.dll
庫文件拷貝到被外掛程序的目錄下,并將其改名為
wsock.001
;再將上面產生的
wsock32.dll
文件同樣拷貝到被外掛程序的目錄下。重新啟動游戲程序,此時游戲程序將先加載我們自己制作的
wsock32.dll
文件,再通過該庫文件間接調用原
WinSock
接口函數(shù)來實現(xiàn)訪問網絡。上面我們僅僅介紹了擋載
WinSock
的實現(xiàn)過程,至于如何加入外掛控制代碼,還需要外掛開發(fā)人員對游戲數(shù)據(jù)包結構、內容、加密算法等方面的仔細分析(這個過程將是一個艱辛的過程),再生成外掛控制代碼。關于數(shù)據(jù)包分析方法和技巧,不是本文講解的范圍,如您感興趣可以到網上查查相關資料。
2.
擋截
API
擋截
API
技術與擋截
WinSock
技術在原理上很相似,但是前者比后者提供了更強大的功能。擋截
WinSock
僅只能擋截
WinSock
接口函數(shù),而擋截
API
可以實現(xiàn)對應用程序調用的包括
WinSock?API
函數(shù)在內的所有
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
過程一樣,將應用程序調用的所有的庫文件都寫一個模擬庫有點不大可能,就只說
Windows?API
就有上千個,還有很多庫提供的函數(shù)結構并未公開,所以寫一個模擬庫代替的方式不大現(xiàn)實,故我們必須另謀良方。
擋截
API
的最終目標是使用自定義的函數(shù)代替原函數(shù)。那么,我們首先應該知道應用程序何時、何地、用何種方式調用原函數(shù)。接下來,需要將應用程序中調用該原函數(shù)的指令代碼進行修改,使它將調用函數(shù)的指針指向我們自己定義的函數(shù)地址。這樣,外掛程序才能完全控制應用程序調用的
API
函數(shù),至于在其中如何加入外掛代碼,就應需求而異了。最后還有一個重要的問題要解決,如何將我們自定義的用來代替原
API
函數(shù)的函數(shù)代碼注入被外掛游戲程序進行地址空間中,因在
Windows
系統(tǒng)中應用程序僅只能訪問到本進程地址空間內的代碼和數(shù)據(jù)。
綜上所述,要實現(xiàn)擋截
API
函數(shù),至少需要解決如下三個問題:
●?
如何定位游戲程序中調用
API
函數(shù)指令代碼?
●?
如何修改游戲程序中調用
API
函數(shù)指令代碼?
●?
如何將外掛代碼(自定義的替換函數(shù)代碼)注入到游戲程序進程地址空間?
下面我們逐一介紹這幾個問題的解決方法:
(1)?
、定位調用
API
函數(shù)指令代碼
我們知道,在匯編語言中使用
CALL
指令來調用函數(shù)或過程的,它是通過指令參數(shù)中的函數(shù)地址而定位到相應的函數(shù)代碼的。那么,我們如果能尋找到程序代碼中所有調用被擋截的
API
函數(shù)的
CALL
指令的話,就可以將該指令中的函數(shù)地址參數(shù)修改為替代函數(shù)的地址。雖然這是一個可行的方案,但是實現(xiàn)起來會很繁瑣,也不穩(wěn)健。慶幸的是,
Windows
系統(tǒng)中所使用的可執(zhí)行文件(
PE
格式)采用了輸入地址表機制,將所有在程序調用的
API
函數(shù)的地址信息存放在輸入地址表中,而在程序代碼
CALL
指令中使用的地址不是
API
函數(shù)的地址,而是輸入地址表中該
API
函數(shù)的地址項,如想使程序代碼中調用的
API
函數(shù)被代替掉,只用將輸入地址表中該
API
函數(shù)的地址項內容修改即可。具體理解輸入地址表運行機制,還需要了解一下
PE
格式文件結構,其中圖三列出了
PE
格式文件的大致結構。
圖三:
PE
格式大致結構圖
(003.jpg)
PE
格式文件一開始是一段
DOS
程序,當你的程序在不支持
Windows
的環(huán)境中運行時,它就會顯示
“This?Program?cannot?be?run?in?DOS?mode”
這樣的警告語句,接著這個
DOS
文件頭,就開始真正的
PE
文件內容了。首先是一段稱為
“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ù)段)值得我們去注意,該段中包含著一些被稱為輸入地址表(
IAT
,
Import?Address?Table
)的數(shù)據(jù)列表。每個用隱式方式加載的
API
所在的
DLL
都有一個
IAT
與之對應,同時一個
API
的地址也與
IAT
中一項相對應。當一個應用程序加載到內存中后,針對每一個
API
函數(shù)調用,相應的產生如下的匯編指令:?
JMP?DWORD?PTR?[XXXXXXXX]?
或
CALL?DWORD?PTR?[XXXXXXXX]
其中,
[XXXXXXXX]
表示指向了輸入地址表中一個項,其內容是一個
DWORD
,而正是這個
DWORD
才是
API
函數(shù)在內存中的真正地址。因此我們要想攔截一個
API
的調用,只要簡單的把那個
DWORD
改為我們自己的函數(shù)的地址。
(2)?
、修改調用
API
函數(shù)代碼
從上面對
PE
文件格式的分析可知,修改調用
API
函數(shù)代碼其實是修改被調用
API
函數(shù)在輸入地址表中
IAT
項內容。由于
Windows
系統(tǒng)對應用程序指令代碼地址空間的嚴密保護機制,使得修改程序指令代碼非常困難,以至于許多高手為之編寫
VxD
進入
Ring0
。在這里,我為大家介紹一種較為方便的方法修改進程內存,它僅需要調用幾個
Windows
核心
API
函數(shù),下面我首先來學會一下這幾個
API
函數(shù):
DWORD?VirtualQuery(
LPCVOID?lpAddress,?//?address?of?region
PMEMORY_BASIC_INFORMATION?lpBuffer,?//?information?buffer
DWORD?dwLength?//?size?of?buffer
);?
該函數(shù)用于查詢關于本進程內虛擬地址頁的信息。其中,
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ù)用于改變本進程內虛擬地址頁的保護屬性。其中,
lpAddress
表示被改變保護屬性頁區(qū)域地址;
dwSize
表示頁區(qū)域大小;
flNewProtect
表示新的保護屬性,可取值為
PAGE_READONLY
、
PAGE_READWRITE
、
PAGE_EXECUTE
等;
lpflOldProtect
表示用于保存改變前的保護屬性。如果函數(shù)調用成功返回
“T”
,否則返回
“F”
。
有了這兩個
API
函數(shù),我們就可以隨心所欲的修改進程內存了。首先,調用
VirtualQuery()
函數(shù)查詢被修改內存的頁信息,再根據(jù)此信息調用
VirtualProtect()
函數(shù)改變這些頁的保護屬性為
PAGE_READWRITE
,有了這個權限您就可以任意修改進程內存數(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)
、注入外掛代碼進入被掛游戲進程中
完成了定位和修改程序中調用
API
函數(shù)代碼后,我們就可以隨意設計自定義的
API
函數(shù)的替代函數(shù)了。做完這一切后,還需要將這些代碼注入到被外掛游戲程序進程內存空間中,不然游戲進程根本不會訪問到替代函數(shù)代碼。注入方法有很多,如利用全局鉤子注入、利用注冊表注入擋截
User32
庫中的
API
函數(shù)、利用
CreateRemoteThread
注入(僅限于
NT/2000
)、利用
BHO
注入等。因為我們在動作模擬技術一節(jié)已經接觸過全局鉤子,我相信聰明的讀者已經完全掌握了全局鉤子的制作過程,所以我們在后面的實例中,將繼續(xù)利用這個全局鉤子。至于其它幾種注入方法,如果感興趣可參閱
MSDN
有關內容。
有了以上理論基礎,我們下面就開始制作一個擋截
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
項并修改其內容為替代函數(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項內容為替代函數(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
的正文和標題內容,只顯示如下內容。
return?MessageBox(hWnd,?"Hook?API?OK!",?"Hook?API",?uType);?
}?
static?int?WINAPI?recv1(SOCKET?s,?char?FAR?*buf,?int?len,?int?flags?)
{
//
此處可以擋截游戲服務器發(fā)送來的網絡數(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
項目,產生
ActiveKey.dll
文件,將其拷貝到
Simulate.exe
目錄下。運行
Simulate.exe
并啟動全局鉤子。激活任意應用程序,按
F11
鍵后,運行此程序中可能調用
MessageBoxA
函數(shù)的操作,看看信息框是不是有所變化。同樣,如此程序正在接收網絡數(shù)據(jù)包,就可以實現(xiàn)封包功能了。
?