遊戲外掛教程(轉)

林堯彬發表於2020-04-04
 

由於本人是遊戲愛好者,又是懶人,所以就有了寫外掛的想法。

有同樣愛好的人 可以 參考一下 主要是呼叫API的一些東西。

以下教程來自網上,本人通過一下教程,編寫了魔獸爭霸3,輔助工具(主用功能顯血、改鍵)

謝謝 O(∩_∩)O~

 

遊戲外掛的編寫原理(一) 
    一、 前言

  所謂遊戲外掛,其實是一種遊戲外輔程式,它可以協助玩家自動產生遊戲動作、修
改遊戲網路資料包以及修改遊

戲記憶體資料等,以實現玩家用最少的時間和金錢去完成功力升級和過關斬將。雖然,現
在對遊戲外掛程式的“合法”

身份眾說紛紜,在這裡我不想對此發表任何個人意見,讓時間去說明一切吧。

  不管遊戲外掛程式是不是“合法”身份,但是它卻是具有一定的技術含量的,在這
些小小程式中使用了許多高階

技術,如攔截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)


五、封包技術

  通過對動作模擬技術的介紹,我們對遊戲外掛有了一定程度上的認識,也學會了使
用動作模擬技術來實現簡單的

動作模擬型遊戲外掛的製作。這種動作模擬型遊戲外掛有一定的侷限性,它僅僅只能解
決使用計算機代替人力完成那

麼有規律、繁瑣而無聊的遊戲動作。但是,隨著網路遊戲的盛行和複雜度的增加,很多
遊戲要求將客戶端動作資訊及

時反饋回伺服器,通過伺服器對這些動作資訊進行有效認證後,再向客戶端傳送下一步
遊戲動作資訊,這樣動作模擬

技術將失去原有的效應。為了更好地“外掛”這些遊戲,遊戲外掛程式也進行了升級換
代,它們將以前針對遊戲使用者

介面層的模擬推進到資料通訊層,通過封包技術在客戶端擋截遊戲伺服器傳送來的遊戲
控制資料包,分析資料包並修

改資料包;同時還需按照遊戲資料包結構建立資料包,再模擬客戶端傳送給遊戲服務
器,這個過程其實就是一個封包

的過程。

  封包的技術是實現第二類遊戲外掛的最核心的技術。封包技術涉及的知識很廣泛,
實現方法也很多,如擋截

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].Vir
tualAddress == 0)

return NULL;
    //定位第一個IAT位置。
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)

(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Virtu
alAddress));
    //根據輸入庫名稱迴圈檢查所有的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函式的操作,看看

資訊框是不是有所變化。同樣,如此程式正在接收網路資料包,就可以實現封包功能了。

  六、結束語

  除了以上介紹的幾種遊戲外掛程式常用的技術以外,在一些外掛程式中還使用了遊戲資料修改技術、遊戲加速技術等。在這篇文

章裡,就不逐一介紹了。

轉載於:https://www.cnblogs.com/lsgsanxiao/p/4502779.html

相關文章