posts - 495,comments - 227,trackbacks - 0
          <2007年3月>
          25262728123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          常用鏈接

          留言簿(46)

          隨筆分類(476)

          隨筆檔案(495)

          最新隨筆

          搜索

          •  

          積分與排名

          • 積分 - 1396599
          • 排名 - 16

          最新評論

          閱讀排行榜

          評論排行榜

          通過對動作模擬技術的介紹,我們對游戲外掛有了一定程度上的認識,也學會了使用動作模擬技術來實現(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)封包功能了。
          ?

          posted on 2007-03-08 17:23 SIMONE 閱讀(4525) 評論(2)  編輯  收藏 所屬分類: C++

          FeedBack:
          # 招聘游戲平臺插件外掛設計人員
          2008-06-23 13:54 | wangwang
          招聘游戲平臺插件外掛設計人員
          計算機相關專業(yè)
          有相關游戲平臺插件外掛設計經驗,需要有實戰(zhàn)經驗
          我公司已經獲得投資資金支持,實力雄厚,服務器已經多達上百臺,可以來我公司實地查看和交流,專職兼職均可
          能力強可以提供較大的發(fā)展機會,提供收入提成
          需要良好的團隊合作和溝通技能,強烈的責任感及團隊合作意識
          我公司在上海,詳細請聯(lián)系我1350-19568-14
            回復  更多評論
            
          # re: 外掛開發(fā)中的封包技術
          2011-04-19 11:47 | zzz
          adsaa  回復  更多評論
            
          主站蜘蛛池模板: 黔东| 呼图壁县| 民权县| 全州县| 山阳县| 梧州市| 鄂托克前旗| 厦门市| 亳州市| 堆龙德庆县| 商丘市| 广宁县| 农安县| 南川市| 措勤县| 勐海县| 温宿县| 广平县| 龙胜| 湟源县| 冀州市| 遂昌县| 鸡东县| 界首市| 泉州市| 额济纳旗| 类乌齐县| 宣恩县| 洞头县| 漳浦县| 崇信县| 灵台县| 上杭县| 长白| 南岸区| 华安县| 连平县| 西充县| 科尔| 松江区| 泸州市|