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