遊戲外挂設計技術探討(上 下) |
|
conundrum
尊榮會員 發表:893 回覆:1272 積分:643 註冊:2004-01-06 發送簡訊給我 |
遊戲外挂設計技術探討(上) http://www.czvc.com/view.asp?id=313 一、 前言 所謂遊戲外挂,其實是一種遊戲外輔程式,它可以協助玩家自動?生遊戲動作、修改遊戲網路資料包以及修改遊戲記憶體資料等,以實現玩家用最少的時間和金錢去完成功力升級和過關斬將。雖然,現在對遊戲外挂程式的“合法”身份?說紛紜,在這裏我不想對此發表任何個人意見,讓時間去說明一切吧。 不管遊戲外挂程式是不是“合法”身份,但是它卻是具有一定的技術含量的,在這些小小程式中使用了許多高端技術,如攔截Sock技術、攔截API技術、類比鍵盤與滑鼠技術、直接修改程式記憶體技術等等。本文將對常見的遊戲外挂中使用的技術進行全面剖析。 二、認識外挂 遊戲外挂的歷史可以追溯到單機版遊戲時代,只不過當時它使用了另一個更通俗易懂的名字??遊戲修改器。它可以在遊戲中追蹤鎖定遊戲主人公的各項能力數值。這樣玩家在遊戲中可以達到主角不掉血、不耗費魔法、不消耗金錢等目的。這樣降低了遊戲的難度,使得玩家更容易通關。 隨著網路遊戲的時代的來臨,遊戲外挂在原有的功能之上進行了新的發展,它變得更加多種多樣,功能更加強大,操作更加簡單,以至有些遊戲的外挂已經成?一個體系,比如《石器時代》,外挂品種達到了幾十種,自動戰鬥、自動行走、自動練級、自動補血、加速、不遇敵、原地遇敵、快速增加經驗值、按鍵精靈……幾乎無所不包。 遊戲外挂的設計主要是針對於某個遊戲開發的,我們可以根據它針對的遊戲的類型可大致可將外挂分?兩種大類。 一類是將遊戲中大量繁瑣和無聊的攻擊動作使用外挂自動完成,以幫助玩家輕鬆搞定攻擊物件並可以快速的增加玩家的經驗值。比如在《龍族》中有一種工作的設定,玩家的工作等級越高,就可以駕馭越好的裝備。但是增加工作等級卻不是一件有趣的事情,毋寧說是重復枯燥的機械勞動。如果你想做法師用的杖,首先需要做基本工作--?砍樹。砍樹的方法很簡單,在一棵大樹前不停的點滑鼠就可以了,每10000的經驗升一級。這就意味著玩家要在大樹前不停的點擊滑鼠,這種無聊的事情通過"按鍵精靈"就可以解決。外挂的"按鍵精靈"功能可以讓玩家擺脫無趣的點擊滑鼠的工作。 另一類是由外挂程式?生欺騙性的網路遊戲封包,並將這些封包發送到網路遊戲伺服器,利用這些虛假資訊欺騙伺服器進行遊戲數值的修改,達到修改角色能力數值的目的。這類外挂程式針對性很強,一般在設計時都是針對某個遊戲某個版本來做的,因?每個網路遊戲伺服器與用戶端交流的資料包各不相同,外挂程式必須要對欺騙的網路遊戲伺服器的資料包進行分析,才能?生伺服器識別的資料包。這類外挂程式也是當前最流利的一類遊戲外挂程式。 另外,現在很多外挂程式功能強大,不僅實現了自動動作代理和封包功能,而且還提供了對網路遊戲的用戶端程式的資料進行修改,以達到欺騙網路遊戲伺服器的目的。我相信,隨著網路遊戲商家的反外挂技術的進展,遊戲外挂將會?生更多更優秀的技術,讓我們期待著看場技術大戰吧...... 三、外挂技術綜述 可以將開發遊戲外挂程式的過程大體上劃分?兩個部分: 前期部分工作是對外挂的主體遊戲進行分析,不同類型的外挂分析主體遊戲的內容也不相同。如外挂?上述談到的外挂類型中的第一類時,其分析過程常是針對遊戲的場景中的攻擊物件的位置和分佈情況進行分析,以實現外挂自動進行攻擊以及位置移動。如外挂?外挂類型中的第二類時,其分析過程常是針對遊戲伺服器與用戶端之間通訊包資料的結構、內容以及加密演算法的分析。因網路遊戲公司一般都不會公佈其遊戲?品的通訊包資料的結構、內容和加密演算法的資訊,所以對於開發第二類外挂成功的關鍵在於是否能正確分析遊戲包資料的結構、內容以及加密演算法,雖然可以使用一些工具輔助分析,但是這還是一種堅苦而複雜的工作。 後期部分工作主要是根據前期對遊戲的分析結果,使用大量的程式開發技術編寫外挂程式以實現對遊戲的控制或修改。如外挂程式?第一類外挂時,通常會使用到滑鼠類比技術來實現遊戲角色的自動位置移動,使用鍵盤類比技術來實現遊戲角色的自動攻擊。如外挂程式?第二類外挂時,通常會使用到擋截Sock和擋截API函數技術,以擋截遊戲伺服器傳來的網路資料包並將資料包修改後封包後傳給遊戲伺服器。另外,還有許多外挂使用對遊戲用戶端程式記憶體資料修改技術以及遊戲加速技術。 本文主要是針對開發遊戲外挂程式後期使用的程式開發技術進行探討,重點介紹的如下幾種在遊戲外挂中常使用的程式開發技術: ● 動作類比技術:主要包括鍵盤類比技術和滑鼠類比技術。 ● 封包技術:主要包括擋截Sock技術和擋截API技術。 四、動作類比技術 我們在前面介紹過,幾乎所有的遊戲都有大量繁瑣和無聊的攻擊動作以增加玩家的功力,還有那些數不完的迷宮,這些好像已經成?了角色遊戲的代名詞。現在,外挂可以幫助玩家從這些繁瑣而無聊的工作中擺脫出來,專注於遊戲情節的進展。外挂程式?了實現自動角色位置移動和自動攻擊等功能,需要使用到鍵盤類比技術和滑鼠類比技術。下面我們將重點介紹這些技術並編寫一個簡單的實例幫助讀者理解動作類比技術的實現過程。 1. 滑鼠類比技術 幾乎所有的遊戲中都使用了滑鼠來改變角色的位置和方向,玩家僅用一個小小的滑鼠,就可以使角色暢遊天下。那?,我們如何實現在沒有玩家的參與下角色也可以自動行走呢。其實實現這個並不難,僅僅幾個Windows API函數就可以搞定,讓我們先來認識認識這些API函數。 (1) 類比滑鼠動作API函數mouse_event,它可以實現類比滑鼠按下和放開等動作。 VOID mouse_event( DWORD dwFlags, // 滑鼠動作標識。 DWORD dx, // 滑鼠水平方向位置。 DWORD dy, // 滑鼠垂直方向位置。 DWORD dwData, // 滑鼠輪子轉動的數量。 DWORD dwExtraInfo // 一個關聯滑鼠動作輔加資訊。 ); 其中,dwFlags表示了各種各樣的滑鼠動作和點擊活動,它的常用取值如下: MOUSEEVENTF_MOVE 表示類比滑鼠移動事件。 MOUSEEVENTF_LEFTDOWN 表示類比按下滑鼠左鍵。 MOUSEEVENTF_LEFTUP 表示類比放開滑鼠左鍵。 MOUSEEVENTF_RIGHTDOWN 表示類比按下滑鼠右鍵。 MOUSEEVENTF_RIGHTUP 表示類比放開滑鼠右鍵。 MOUSEEVENTF_MIDDLEDOWN 表示類比按下滑鼠中鍵。 MOUSEEVENTF_MIDDLEUP 表示類比放開滑鼠中鍵。 (2)、設置和獲取當前滑鼠位置的API函數。獲取當前滑鼠位置使用GetCursorPos()函數,設置當前滑鼠位置使用SetCursorPos()函數。 BOOL GetCursorPos( LPPOINT lpPoint // 返回滑鼠的當前位置。 ); BOOL SetCursorPos( int X, // 滑鼠的水平方向位置。 int Y //滑鼠的垂直方向位置。 ); 通常遊戲角色的行走都是通過滑鼠移動至目的地,然後按一下滑鼠的按鈕就搞定了。下面我們使用上面介紹的API函數來類比角色行走過程。 CPoint oldPoint,newPoint; GetCursorPos(&oldPoint); //保存當前滑鼠位置。 newPoint.x = oldPoint.x 40; newPoint.y = oldPoint.y 10; SetCursorPos(newPoint.x,newPoint.y); //設置目的地位置。 mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//類比按下滑鼠右鍵。 mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//類比放開滑鼠右鍵。 2. 鍵盤類比技術 在很多遊戲中,不僅提供了滑鼠的操作,而且還提供了鍵盤的操作,在對攻擊物件進行攻擊時還可以使用快捷鍵。?了使這些攻擊過程能夠自動進行,外挂程式需要使用鍵盤類比技術。像滑鼠類比技術一樣,Windows API也提供了一系列API函數來完成對鍵盤動作的類比。 類比鍵盤動作API函數keydb_event,它可以類比對鍵盤上的某個或某些鍵進行按下或放開的動作。 VOID keybd_event( BYTE bVk, // 虛擬鍵值。 BYTE bScan, // 硬體掃描碼。 DWORD dwFlags, // 動作標識。 DWORD dwExtraInfo // 與鍵盤動作關聯的輔加資訊。 ); 其中,bVk表示虛擬鍵值,其實它是一個BYTE類型值的宏,其取值範圍?1-254。有關虛擬鍵值表請在MSDN上使用關鍵字“Virtual-Key Codes”查找相關資料。bScan表示當鍵盤上某鍵被按下和放開時,鍵盤系統硬體?生的掃描碼,我們可以MapVirtualKey()函數在虛擬鍵值與掃描碼之間進行轉換。dwFlags表示各種各樣的鍵盤動作,它有兩種取值:KEYEVENTF_EXTENDEDKEY和KEYEVENTF_KEYUP。 下面我們使用一段代碼實現在遊戲中按下Shift R快捷鍵對攻擊物件進行攻擊。 keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),0,0); //按下CTRL鍵。 keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//鍵下R鍵。 keybd_event(0x52,MapVirtualKey(0x52,0), KEYEVENTF_KEYUP,0);//放開R鍵。 keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0), KEYEVENTF_KEYUP,0);//放開CTRL鍵。 3. 啟動外挂 上面介紹的滑鼠和鍵盤類比技術實現了對遊戲角色的動作部分的類比,但要想外挂能工作於遊戲之上,還需要將其與遊戲的場景視窗聯繫起來或者使用一個啟動鍵,就象按鍵精靈的那個啟動鍵一樣。我們可以用GetWindow函數來枚舉視窗,也可以用Findwindow函數來查找特定的視窗。另外還有一個FindWindowEx函數可以找到視窗的子視窗,當遊戲切換場景的時候我們可以用FindWindowEx來確定一些當前視窗的特徵,從而判斷是否還在這個場景,方法很多了,比如可以GetWindowInfo來確定一些東西,比如當查找不到某個按鈕的時候就說明遊戲場景已經切換了等等辦法。當使用啟動鍵進行關聯,需要使用Hook技術開發一個全局鍵盤?子,在這裏就不具體介紹全局?子的開發過程了,在後面的實例中我們將會使用到全局?子,到時將學習到全局?子的相關知識。 4. 實例實現 通過上面的學習,我們已經基本具備了編寫動作式遊戲外挂的能力了。下面我們將創建一個畫筆程式外挂,它實現自動移動畫筆字遊標的位置並寫下一個紅色的“R”字。以這個實例?基礎,加入相應的遊戲動作規則,就可以實現一個完整的遊戲外挂。這裏作者不想使用某個遊戲作?例子來開發外挂(因沒有遊戲商家的授權啊!),如讀者感興趣的話可以找一個遊戲試試,最好僅做測試技術用。 首先,我們需要編寫一個全局?子,使用它來啟動外挂,啟動鍵?F10。創建全局?子步驟如下: (1).選擇MFC AppWizard(DLL)創建專案ActiveKey,並選擇MFC Extension DLL(共用MFC拷貝)類型。 (2).插入新文件ActiveKey.h,在其中輸入如下代碼: #ifndef _KEYDLL_H #define _KEYDLL_H class AFX_EXT_CLASS CKeyHook:public CObject { public: CKeyHook(); ~CKeyHook(); HHOOK Start(); //安裝?子 BOOL Stop(); //卸載?子 }; #endif (3).在ActiveKey.cpp文件中加入聲明"#include ActiveKey.h"。 (4).在ActiveKey.cpp文件中加入共用資料段,代碼如下: //Shared data section #pragma data_seg("sharedata") HHOOK glhHook=NULL; //?子控制碼。 HINSTANCE glhInstance=NULL; //DLL實例控制碼。 #pragma data_seg() (5).在ActiveKey.def文件中設置共用資料段屬性,代碼如下: SETCTIONS shareddata READ WRITE SHARED (6).在ActiveKey.cpp文件中加入CkeyHook類的實現代碼和?子函數代碼: //鍵盤?子處理函數。 extern "C" LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam) { if( nCode >= 0 ) { if( wParam == 0X79 )//當按下F10鍵時,啟動外挂。 { //外挂實現代碼。 CPoint newPoint,oldPoint; GetCursorPos(&oldPoint); newPoint.x = oldPoint.x 40; newPoint.y = oldPoint.y 10; SetCursorPos(newPoint.x,newPoint.y); mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//類比按下滑鼠左鍵。 mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//類比放開滑鼠左鍵。 keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),0,0); //按下SHIFT鍵。 keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//按下R鍵。 keybd_event(0x52,MapVirtualKey(0x52,0),KEYEVENTF_KEYUP,0);//放開R鍵。 keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),KEYEVENTF_KEYUP,0);//放開SHIFT鍵。 SetCursorPos(oldPoint.x,oldPoint.y); } } return CallNextHookEx(glhHook,nCode,wParam,lParam); } CKeyHook::CKeyHook(){} CKeyHook::~CKeyHook() { if( glhHook ) Stop(); } //安裝全局?子。 HHOOK CKeyHook::Start() { glhHook = SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);//設置鍵盤?子。 return glhHook; } //卸載全局?子。 BOOL CKeyHook::Stop() { BOOL bResult = TRUE; if( glhHook ) bResult = UnhookWindowsHookEx(glhHook);//卸載鍵盤?子。 return bResult; } (7).修改DllMain函數,代碼如下: extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { //如果使用lpReserved參數則刪除下面這行 UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("NOtePadHOOK.DLL Initializing!\n"); //擴展DLL僅初始化一次 if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance)) return 0; new CDynLinkLibrary(ActiveKeyDLL); //把DLL加入動態MFC類庫中 glhInstance = hInstance; //插入保存DLL實例控制碼 } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("NotePadHOOK.DLL Terminating!\n"); //終止這個程式庫前調用它 AfxTermExtensionModule(ActiveKeyDLL); } return 1; } (8).編譯專案ActiveKey,生成ActiveKey.DLL和ActiveKey.lib。 接著,我們還需要創建一個外殼程式將全局?子安裝了Windows系統中,這個外殼程式編寫步驟如下: (1).創建一個對話方塊模式的應用程式,專案名?Simulate。 (2).在主對話方塊中加入一個按鈕,使用ClassWizard?其創建CLICK事件。 (3).將ActiveKey專案Debug目錄下的ActiveKey.DLL和ActiveKey.lib拷貝到Simulate專案目錄下。 (4).從“工程”功能表中選擇“設置”,彈出Project Setting對話方塊,選擇Link標簽,在“物件/庫模組”中輸入ActiveKey.lib。 (5).將ActiveKey專案中的ActiveKey.h頭文件加入到Simulate專案中,並在Stdafx.h中加入#include ActiveKey.h。 (6).在按鈕單擊事件函數輸入如下代碼: void CSimulateDlg::OnButton1() { // TODO: Add your control notification handler code here if( !bSetup ) { m_hook.Start();//啟動全局?子。 } else { m_hook.Stop();//撤消全局?子。 } bSetup = !bSetup; } (7).編譯專案,並運行程式,單擊按鈕啟動外挂。 (8).?動畫筆程式,選擇文本工具並將筆的?色設置?紅色,將滑鼠放在任意位置後,按F10鍵,畫筆程式自動移動滑鼠並寫下一個紅色的大寫R。圖一展示了按F10鍵前的畫筆程式的狀態,圖二展示了按F10鍵後的畫筆程式的狀態。 圖一:按F10前狀態(001.jpg) 圖二:按F10後狀態(002.jpg) 遊戲外挂設計技術探討(上)完,請看下部:遊戲外挂設計技術探討(下) 遊戲外挂設計技術探討(下) http://www.czvc.com/view.asp?id=314 五、封包技術 通過對動作類比技術的介紹,我們對遊戲外挂有了一定程度上的認識,也學會了使用動作類比技術來實現簡單的動作類比型遊戲外挂的製作。這種動作類比型遊戲外挂有一定的局限性,它僅僅只能解決使用電腦代替人力完成那?有規律、繁瑣而無聊的遊戲動作。但是,隨著網路遊戲的盛行和複雜度的增加,很多遊戲要求將用戶端動作資訊及時反饋回伺服器,通過伺服器對這些動作資訊進行有效認證後,再向用戶端發送下一步遊戲動作資訊,這樣動作類比技術將失去原有的效應。?了更好地“外挂”這些遊戲,遊戲外挂程式也進行了升級換代,它們將以前針對遊戲用戶介面層的類比推進到資料通訊層,通過封包技術在用戶端擋截遊戲伺服器發送來的遊戲控制資料包,分析資料包並修改資料包;同時還需按照遊戲資料包結構創建資料包,再類比用戶端發送給遊戲伺服器,這個過程其實就是一個封包的過程。 封包的技術是實現第二類遊戲外挂的最核心的技術。封包技術涉及的知識很廣泛,實現方法也很多,如擋截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格式大致結構圖 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函數的操作,看看資訊框是不是有所變化。 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |