網(wǎng)絡(luò)游戲盛行催生了各種外掛,讓商家頭痛不已,那么外掛是怎么設(shè)計的呢?看看本文就知道了
一、 前言
所謂游戲外掛,其實是一種游戲外輔程序,它可以協(xié)助玩家自動產(chǎn)生游戲動作、修改游戲網(wǎng)絡(luò)數(shù)據(jù)包以及修改游戲內(nèi)存數(shù)據(jù)等,以實現(xiàn)玩家用最少的時間和金錢去完成功力升級和過關(guān)斬將。雖然,現(xiàn)在對游戲外掛程序的“合法”身份眾說紛紜,在這里我不想對此發(fā)表任何個人意見,讓時間去說明一切吧。
隨著網(wǎng)絡(luò)游戲的時代的來臨,游戲外掛在原有的功能之上進行了新的發(fā)展,它變得更加多種多樣,功能更加強大,操作更加簡單,以至有些游戲的外掛已經(jīng)成為一個體系,比如《石器時代》,外掛品種達到了幾十種,自動戰(zhàn)斗、自動行走、自動練級、自動補血、加速、不遇敵、原地遇敵、快速增加經(jīng)驗值、按鍵精靈……幾乎無所不包。
游戲外掛的設(shè)計主要是針對于某個游戲開發(fā)的,我們可以根據(jù)它針對的游戲的類型可大致可將外掛分為兩種大類。
一類是將游戲中大量繁瑣和無聊的攻擊動作使用外掛自動完成,以幫助玩家輕松搞定攻擊對象并可以快速的增加玩家的經(jīng)驗值。比如在《龍族》中有一種工作的設(shè)定,玩家的工作等級越高,就可以駕馭越好的裝備。但是增加工作等級卻不是一件有趣的事情,毋寧說是重復(fù)枯燥的機械勞動。如果你想做法師用的杖,首先需要做基本工作--?砍樹。砍樹的方法很簡單,在一棵大樹前不停的點鼠標(biāo)就可以了,每10000的經(jīng)驗升一級。這就意味著玩家要在大樹前不停的點擊鼠標(biāo),這種無聊的事情通過"按鍵精靈"就可以解決。外掛的"按鍵精靈"功能可以讓玩家擺脫無趣的點擊鼠標(biāo)的工作。
另一類是由外掛程序產(chǎn)生欺騙性的網(wǎng)絡(luò)游戲封包,并將這些封包發(fā)送到網(wǎng)絡(luò)游戲服務(wù)器,利用這些虛假信息欺騙服務(wù)器進行游戲數(shù)值的修改,達到修改角色能力數(shù)值的目的。這類外掛程序針對性很強,一般在設(shè)計時都是針對某個游戲某個版本來做的,因為每個網(wǎng)絡(luò)游戲服務(wù)器與客戶端交流的數(shù)據(jù)包各不相同,外掛程序必須要對欺騙的網(wǎng)絡(luò)游戲服務(wù)器的數(shù)據(jù)包進行分析,才能產(chǎn)生服務(wù)器識別的數(shù)據(jù)包。這類外掛程序也是當(dāng)前最流利的一類游戲外掛程序。
另外,現(xiàn)在很多外掛程序功能強大,不僅實現(xiàn)了自動動作代理和封包功能,而且還提供了對網(wǎng)絡(luò)游戲的客戶端程序的數(shù)據(jù)進行修改,以達到欺騙網(wǎng)絡(luò)游戲服務(wù)器的目的。我相信,隨著網(wǎng)絡(luò)游戲商家的反外掛技術(shù)的進展,游戲外掛將會產(chǎn)生更多更優(yōu)秀的技術(shù),讓我們期待著看場技術(shù)大戰(zhàn)吧......
三、外掛技術(shù)綜述
可以將開發(fā)游戲外掛程序的過程大體上劃分為兩個部分:
前期部分工作是對外掛的主體游戲進行分析,不同類型的外掛分析主體游戲的內(nèi)容也不相同。如外掛為上述談到的外掛類型中的第一類時,其分析過程常是針對游戲的場景中的攻擊對象的位置和分布情況進行分析,以實現(xiàn)外掛自動進行攻擊以及位置移動。如外掛為外掛類型中的第二類時,其分析過程常是針對游戲服務(wù)器與客戶端之間通訊包數(shù)據(jù)的結(jié)構(gòu)、內(nèi)容以及加密算法的分析。因網(wǎng)絡(luò)游戲公司一般都不會公布其游戲產(chǎn)品的通訊包數(shù)據(jù)的結(jié)構(gòu)、內(nèi)容和加密算法的信息,所以對于開發(fā)第二類外掛成功的關(guān)鍵在于是否能正確分析游戲包數(shù)據(jù)的結(jié)構(gòu)、內(nèi)容以及加密算法,雖然可以使用一些工具輔助分析,但是這還是一種堅苦而復(fù)雜的工作。
后期部分工作主要是根據(jù)前期對游戲的分析結(jié)果,使用大量的程序開發(fā)技術(shù)編寫外掛程序以實現(xiàn)對游戲的控制或修改。如外掛程序為第一類外掛時,通常會使用到鼠標(biāo)模擬技術(shù)來實現(xiàn)游戲角色的自動位置移動,使用鍵盤模擬技術(shù)來實現(xiàn)游戲角色的自動攻擊。如外掛程序為第二類外掛時,通常會使用到擋截Sock和擋截API函數(shù)技術(shù),以擋截游戲服務(wù)器傳來的網(wǎng)絡(luò)數(shù)據(jù)包并將數(shù)據(jù)包修改后封包后傳給游戲服務(wù)器。另外,還有許多外掛使用對游戲客戶端程序內(nèi)存數(shù)據(jù)修改技術(shù)以及游戲加速技術(shù)。
本文主要是針對開發(fā)游戲外掛程序后期使用的程序開發(fā)技術(shù)進行探討,重點介紹的如下幾種在游戲外掛中常使用的程序開發(fā)技術(shù):
● 動作模擬技術(shù):主要包括鍵盤模擬技術(shù)和鼠標(biāo)模擬技術(shù)。
● 封包技術(shù):主要包括擋截Sock技術(shù)和擋截API技術(shù)。
四、動作模擬技術(shù)
我們在前面介紹過,幾乎所有的游戲都有大量繁瑣和無聊的攻擊動作以增加玩家的功力,還有那些數(shù)不完的迷宮,這些好像已經(jīng)成為了角色游戲的代名詞。現(xiàn)在,外掛可以幫助玩家從這些繁瑣而無聊的工作中擺脫出來,專注于游戲情節(jié)的進展。外掛程序為了實現(xiàn)自動角色位置移動和自動攻擊等功能,需要使用到鍵盤模擬技術(shù)和鼠標(biāo)模擬技術(shù)。下面我們將重點介紹這些技術(shù)并編寫一個簡單的實例幫助讀者理解動作模擬技術(shù)的實現(xiàn)過程。
1. 鼠標(biāo)模擬技術(shù)
幾乎所有的游戲中都使用了鼠標(biāo)來改變角色的位置和方向,玩家僅用一個小小的鼠標(biāo),就可以使角色暢游天下。那么,我們?nèi)绾螌崿F(xiàn)在沒有玩家的參與下角色也可以自動行走呢。其實實現(xiàn)這個并不難,僅僅幾個Windows API函數(shù)就可以搞定,讓我們先來認(rèn)識認(rèn)識這些API函數(shù)。
(1) 模擬鼠標(biāo)動作API函數(shù)mouse_event,它可以實現(xiàn)模擬鼠標(biāo)按下和放開等動作。
VOID mouse_event(
DWORD dwFlags, // 鼠標(biāo)動作標(biāo)識。
DWORD dx, // 鼠標(biāo)水平方向位置。
DWORD dy, // 鼠標(biāo)垂直方向位置。
DWORD dwData, // 鼠標(biāo)輪子轉(zhuǎn)動的數(shù)量。
DWORD dwExtraInfo // 一個關(guān)聯(lián)鼠標(biāo)動作輔加信息。
);
其中,dwFlags表示了各種各樣的鼠標(biāo)動作和點擊活動,它的常用取值如下:
MOUSEEVENTF_MOVE 表示模擬鼠標(biāo)移動事件。
MOUSEEVENTF_LEFTDOWN 表示模擬按下鼠標(biāo)左鍵。
MOUSEEVENTF_LEFTUP 表示模擬放開鼠標(biāo)左鍵。
MOUSEEVENTF_RIGHTDOWN 表示模擬按下鼠標(biāo)右鍵。
MOUSEEVENTF_RIGHTUP 表示模擬放開鼠標(biāo)右鍵。
MOUSEEVENTF_MIDDLEDOWN 表示模擬按下鼠標(biāo)中鍵。
MOUSEEVENTF_MIDDLEUP 表示模擬放開鼠標(biāo)中鍵。
(2)、設(shè)置和獲取當(dāng)前鼠標(biāo)位置的API函數(shù)。獲取當(dāng)前鼠標(biāo)位置使用GetCursorPos()函數(shù),設(shè)置當(dāng)前鼠標(biāo)位置使用SetCursorPos()函數(shù)。
BOOL GetCursorPos(
LPPOINT lpPoint // 返回鼠標(biāo)的當(dāng)前位置。
);
BOOL SetCursorPos(
int X, // 鼠標(biāo)的水平方向位置。
int Y //鼠標(biāo)的垂直方向位置。
);
通常游戲角色的行走都是通過鼠標(biāo)移動至目的地,然后按一下鼠標(biāo)的按鈕就搞定了。下面我們使用上面介紹的API函數(shù)來模擬角色行走過程。
CPoint oldPoint,newPoint;
GetCursorPos(&oldPoint); //保存當(dāng)前鼠標(biāo)位置。
newPoint.x = oldPoint.x+40;
newPoint.y = oldPoint.y+10;
SetCursorPos(newPoint.x,newPoint.y); //設(shè)置目的地位置。
mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//模擬按下鼠標(biāo)右鍵。
mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//模擬放開鼠標(biāo)右鍵。
2. 鍵盤模擬技術(shù)
在很多游戲中,不僅提供了鼠標(biāo)的操作,而且還提供了鍵盤的操作,在對攻擊對象進行攻擊時還可以使用快捷鍵。為了使這些攻擊過程能夠自動進行,外掛程序需要使用鍵盤模擬技術(shù)。像鼠標(biāo)模擬技術(shù)一樣,Windows API也提供了一系列API函數(shù)來完成對鍵盤動作的模擬。
模擬鍵盤動作API函數(shù)keydb_event,它可以模擬對鍵盤上的某個或某些鍵進行按下或放開的動作。
VOID keybd_event(
BYTE bVk, // 虛擬鍵值。
BYTE bScan, // 硬件掃描碼。
DWORD dwFlags, // 動作標(biāo)識。
DWORD dwExtraInfo // 與鍵盤動作關(guān)聯(lián)的輔加信息。
);
其中,bVk表示虛擬鍵值,其實它是一個BYTE類型值的宏,其取值范圍為1-254。有關(guān)虛擬鍵值表請在MSDN上使用關(guān)鍵字“Virtual-Key Codes”查找相關(guān)資料。bScan表示當(dāng)鍵盤上某鍵被按下和放開時,鍵盤系統(tǒng)硬件產(chǎn)生的掃描碼,我們可以MapVirtualKey()函數(shù)在虛擬鍵值與掃描碼之間進行轉(zhuǎn)換。dwFlags表示各種各樣的鍵盤動作,它有兩種取值:
KEYEVENTF_EXTENDEDKEY和KEYEVENTF_KEYUP。
下面我們使用一段代碼實現(xiàn)在游戲中按下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. 激活外掛
上面介紹的鼠標(biāo)和鍵盤模擬技術(shù)實現(xiàn)了對游戲角色的動作部分的模擬,但要想外掛能工作于游戲之上,還需要將其與游戲的場景窗口聯(lián)系起來或者使用一個激活鍵,就象按鍵精靈的那個激活鍵一樣。我們可以用GetWindow函數(shù)來枚舉窗口,也可以用Findwindow函數(shù)來查找特定的窗口。另外還有一個FindWindowEx函數(shù)可以找到窗口的子窗口,當(dāng)游戲切換場景的時候我們可以用FindWindowEx來確定一些當(dāng)前窗口的特征,從而判斷是否還在這個場景,方法很多了,比如可以GetWindowInfo來確定一些東西,比如當(dāng)查找不到某個按鈕的時候就說明游戲場景已經(jīng)切換了等等辦法。當(dāng)使用激活鍵進行關(guān)聯(lián),需要使用Hook技術(shù)開發(fā)一個全局鍵盤鉤子,在這里就不具體介紹全局鉤子的開發(fā)過程了,在后面的實例中我們將會使用到全局鉤子,到時將學(xué)習(xí)到全局鉤子的相關(guān)知識。
4. 實例實現(xiàn)
通過上面的學(xué)習(xí),我們已經(jīng)基本具備了編寫動作式游戲外掛的能力了。下面我們將創(chuàng)建一個畫筆程序外掛,它實現(xiàn)自動移動畫筆字光標(biāo)的位置并寫下一個紅色的“R”字。以這個實例為基礎(chǔ),加入相應(yīng)的游戲動作規(guī)則,就可以實現(xiàn)一個完整的游戲外掛。這里作者不想使用某個游戲作為例子來開發(fā)外掛(因沒有游戲商家的授權(quán)啊!),如讀者感興趣的話可以找一個游戲試試,最好僅做測試技術(shù)用。
首先,我們需要編寫一個全局鉤子,使用它來激活外掛,激活鍵為F10。創(chuàng)建全局鉤子步驟如下:
(1).選擇MFC AppWizard(DLL)創(chuàng)建項目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文件中加入共享數(shù)據(jù)段,代碼如下:
//Shared data section
#pragma data_seg("sharedata")
HHOOK glhHook=NULL; //鉤子句柄。
HINSTANCE glhInstance=NULL; //DLL實例句柄。
#pragma data_seg()
(5).在ActiveKey.def文件中設(shè)置共享數(shù)據(jù)段屬性,代碼如下:
SETCTIONS
shareddata READ WRITE SHARED
(6).在ActiveKey.cpp文件中加入CkeyHook類的實現(xiàn)代碼和鉤子函數(shù)代碼:
//鍵盤鉤子處理函數(shù)。
extern "C" LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if( nCode >= 0 )
{
if( wParam == 0X79 )//當(dāng)按下F10鍵時,激活外掛。
{
//外掛實現(xiàn)代碼。
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);//模擬按下鼠標(biāo)左鍵。
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//模擬放開鼠標(biāo)左鍵。
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);//設(shè)置鍵盤鉤子。
return glhHook;
}
//卸載全局鉤子。
BOOL CKeyHook::Stop()
{
BOOL bResult = TRUE;
if( glhHook )
bResult = UnhookWindowsHookEx(glhHook);//卸載鍵盤鉤子。
return bResult;
}
(7).修改DllMain函數(shù),代碼如下:
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
//如果使用lpReserved參數(shù)則刪除下面這行
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("NOtePadHOOK.DLL Initializing!\n");
//擴展DLL僅初始化一次
if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance))
return 0;
new CDynLinkLibrary(ActiveKeyDLL);
//把DLL加入動態(tài)MFC類庫中
glhInstance = hInstance;
//插入保存DLL實例句柄
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("NotePadHOOK.DLL Terminating!\n");
//終止這個鏈接庫前調(diào)用它
AfxTermExtensionModule(ActiveKeyDLL);
}
return 1;
}
(8).編譯項目ActiveKey,生成ActiveKey.DLL和ActiveKey.lib。
接著,我們還需要創(chuàng)建一個外殼程序?qū)⑷帚^子安裝了Windows系統(tǒng)中,這個外殼程序編寫步驟如下:
(1).創(chuàng)建一個對話框模式的應(yīng)用程序,項目名為Simulate。
(2).在主對話框中加入一個按鈕,使用ClassWizard為其創(chuàng)建CLICK事件。
(3).將ActiveKey項目Debug目錄下的ActiveKey.DLL和ActiveKey.lib拷貝到Simulate項目目錄下。
(4).從“工程”菜單中選擇“設(shè)置”,彈出Project Setting對話框,選擇Link標(biāo)簽,在“對象/庫模塊”中輸入ActiveKey.lib。
(5).將ActiveKey項目中的ActiveKey.h頭文件加入到Simulate項目中,并在Stdafx.h中加入#include ActiveKey.h。
(6).在按鈕單擊事件函數(shù)輸入如下代碼:
void CSimulateDlg::OnButton1()
{
// TODO: Add your control notification handler code here
if( !bSetup )
{
m_hook.Start();//激活全局鉤子。
}
else
{
m_hook.Stop();//撤消全局鉤子。
}
bSetup = !bSetup;
}
(7).編譯項目,并運行程序,單擊按鈕激活外掛。
(8).啟動畫筆程序,選擇文本工具并將筆的顏色設(shè)置為紅色,將鼠標(biāo)放在任意位置后,按F10鍵,畫筆程序自動移動鼠標(biāo)并寫下一個紅色的大寫R。圖一展示了按F10鍵前的畫筆程序的狀態(tài),圖二展示了按F10鍵后的畫筆程序的狀態(tài)。
圖一:按F10前狀態(tài)(001.jpg)
圖二:按F10后狀態(tài)(002.jpg)
五、封包技術(shù)
通過對動作模擬技術(shù)的介紹,我們對游戲外掛有了一定程度上的認(rèn)識,也學(xué)會了使用動作模擬技術(shù)來實現(xiàn)簡單的動作模擬型游戲外掛的制作。這種動作模擬型游戲外掛有一定的局限性,它僅僅只能解決使用計算機代替人力完成那么有規(guī)律、繁瑣而無聊的游戲動作。但是,隨著網(wǎng)絡(luò)游戲的盛行和復(fù)雜度的增加,很多游戲要求將客戶端動作信息及時反饋回服務(wù)器,通過服務(wù)器對這些動作信息進行有效認(rèn)證后,再向客戶端發(fā)送下一步游戲動作信息,這樣動作模擬技術(shù)將失去原有的效應(yīng)。為了更好地“外掛”這些游戲,游戲外掛程序也進行了升級換代,它們將以前針對游戲用戶界面層的模擬推進到數(shù)據(jù)通訊層,通過封包技術(shù)在客戶端擋截游戲服務(wù)器發(fā)送來的游戲控制數(shù)據(jù)包,分析數(shù)據(jù)包并修改數(shù)據(jù)包;同時還需按照游戲數(shù)據(jù)包結(jié)構(gòu)創(chuàng)建數(shù)據(jù)包,再模擬客戶端發(fā)送給游戲服務(wù)器,這個過程其實就是一個封包的過程。
封包的技術(shù)是實現(xiàn)第二類游戲外掛的最核心的技術(shù)。封包技術(shù)涉及的知識很廣泛,實現(xiàn)方法也很多,如擋截WinSock、擋截API函數(shù)、擋截消息、VxD驅(qū)動程序等。在此我們也不可能在此文中將所有的封包技術(shù)都進行詳細介紹,故選擇兩種在游戲外掛程序中最常用的兩種方法:擋截WinSock和擋截API函數(shù)。
1. 擋截WinSock
眾所周知,Winsock是Windows網(wǎng)絡(luò)編程接口,它工作于Windows應(yīng)用層,它提供與底層傳輸協(xié)議無關(guān)的高層數(shù)據(jù)傳輸編程接口。在Windows系統(tǒng)中,使用WinSock接口為應(yīng)用程序提供基于TCP/IP協(xié)議的網(wǎng)絡(luò)訪問服務(wù),這些服務(wù)是由Wsock32.DLL動態(tài)鏈接庫提供的函數(shù)庫來完成的。
由上說明可知,任何Windows基于TCP/IP的應(yīng)用程序都必須通過WinSock接口訪問網(wǎng)絡(luò),當(dāng)然網(wǎng)絡(luò)游戲程序也不例外。
由此我們可以想象一下,如果我們可以控制WinSock接口的話,那么控制游戲客戶端程序與服務(wù)器之間的數(shù)據(jù)包也將易如反掌。按著這個思路,下面的工作就是如何完成控制WinSock接口了。由上面的介紹可知,WinSock接口其實是由一個動態(tài)鏈接庫提供的一系列函數(shù),由這些函數(shù)實現(xiàn)對網(wǎng)絡(luò)的訪問。有了這層的認(rèn)識,問題就好辦多了,我們可以制作一個類似的動態(tài)鏈接庫來代替原WinSock接口庫,在其中實現(xiàn)WinSock32.dll中實現(xiàn)的所有函數(shù),并保證所有函數(shù)的參數(shù)個數(shù)和順序、返回值類型都應(yīng)與原庫相同。在這個自制作的動態(tài)庫中,可以對我們感興趣的函數(shù)(如發(fā)送、接收等函數(shù))進行擋截,放入外掛控制代碼,最后還繼續(xù)調(diào)用原WinSock庫中提供的相應(yīng)功能函數(shù),這樣就可以實現(xiàn)對網(wǎng)絡(luò)數(shù)據(jù)包的擋截、修改和發(fā)送等封包功能。
下面重點介紹創(chuàng)建擋截WinSock外掛程序的基本步驟:
(1) 創(chuàng)建DLL項目,選擇Win32 Dynamic-Link Library,再選擇An empty DLL project。
(2) 新建文件wsock32.h,按如下步驟輸入代碼:
① 加入相關(guān)變量聲明:
HMODULE hModule=NULL; //模塊句柄
char buffer[1000]; //緩沖區(qū)
FARPROC proc; //函數(shù)入口指針
② 定義指向原WinSock庫中的所有函數(shù)地址的指針變量,因WinSock庫共提供70多個函數(shù),限于篇幅,在此就只選擇幾個常用的函數(shù)列出,有關(guān)這些庫函數(shù)的說明可參考MSDN相關(guān)內(nèi)容。
//定義指向原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文件,按如下步驟輸入代碼:
① 加入相關(guān)頭文件聲明:
#include
#include
#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庫已復(fù)制為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ù)的最后一步都調(diào)用原WinSock庫的同名函數(shù)。部分輸出函數(shù)定義代碼如下:
//庫輸出函數(shù)定義。
//WinSock初始化函數(shù)。
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
{
//調(diào)用原WinSock庫初始化函數(shù)
return WSAStartup1(wVersionRequired,lpWSAData);
}
//WinSock結(jié)束清除函數(shù)。
int PASCAL FAR WSACleanup(void)
{
return WSACleanup1(); //調(diào)用原WinSock庫結(jié)束清除函數(shù)。
}
//創(chuàng)建Socket函數(shù)。
SOCKET PASCAL FAR socket (int af, int type, int protocol)
{
//調(diào)用原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的內(nèi)容進行修改,以實現(xiàn)欺騙服務(wù)器。
外掛代碼......
//調(diào)用原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)
{
//在此可以擋截到服務(wù)器端發(fā)送到客戶端的數(shù)據(jù)包,先將其保存到buffer中。
strcpy(buffer,buf);
//對buffer數(shù)據(jù)包數(shù)據(jù)進行分析后,對其按照玩家的指令進行相關(guān)修改。
外掛代碼......
//最后調(diào)用原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)、從“工程”菜單中選擇“設(shè)置”,彈出Project Setting對話框,選擇Link標(biāo)簽,在“對象/庫模塊”中輸入Ws2_32.lib。
(6)、編譯項目,產(chǎn)生wsock32.dll庫文件。
(7)、將系統(tǒng)目錄下原wsock32.dll庫文件拷貝到被外掛程序的目錄下,并將其改名為wsock.001;再將上面產(chǎn)生的wsock32.dll文件同樣拷貝到被外掛程序的目錄下。重新啟動游戲程序,此時游戲程序?qū)⑾燃虞d我們自己制作的wsock32.dll文件,再通過該庫文件間接調(diào)用原WinSock接口函數(shù)來實現(xiàn)訪問網(wǎng)絡(luò)。上面我們僅僅介紹了擋載WinSock的實現(xiàn)過程,至于如何加入外掛控制代碼,還需要外掛開發(fā)人員對游戲數(shù)據(jù)包結(jié)構(gòu)、內(nèi)容、加密算法等方面的仔細分析(這個過程將是一個艱辛的過程),再生成外掛控制代碼。關(guān)于數(shù)據(jù)包分析方法和技巧,不是本文講解的范圍,如您感興趣可以到網(wǎng)上查查相關(guān)資料。
(3)、注入外掛代碼進入被掛游戲進程中
完成了定位和修改程序中調(diào)用API函數(shù)代碼后,我們就可以隨意設(shè)計自定義的API函數(shù)的替代函數(shù)了。做完這一切后,還需要將這些代碼注入到被外掛游戲程序進程內(nèi)存空間中,不然游戲進程根本不會訪問到替代函數(shù)代碼。注入方法有很多,如利用全局鉤子注入、利用注冊表注入擋截User32庫中的API函數(shù)、利用CreateRemoteThread注入(僅限于NT/2000)、利用BHO注入等。因為我們在動作模擬技術(shù)一節(jié)已經(jīng)接觸過全局鉤子,我相信聰明的讀者已經(jīng)完全掌握了全局鉤子的制作過程,所以我們在后面的實例中,將繼續(xù)利用這個全局鉤子。至于其它幾種注入方法,如果感興趣可參閱MSDN有關(guān)內(nèi)容。
有了以上理論基礎(chǔ),我們下面就開始制作一個擋截MessageBoxA和recv函數(shù)的實例,在開發(fā)游戲外掛程序 時,可以此實例為框架,加入相應(yīng)的替代函數(shù)和處理代碼即可。此實例的開發(fā)過程如下:
(1) 打開前面創(chuàng)建的ActiveKey項目。
(2) 在ActiveKey.h文件中加入HOOKAPI結(jié)構(gòu),此結(jié)構(gòu)用來存儲被擋截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標(biāo)志,否則返回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項并修改其內(nèi)容為替代函數(shù)地址。代碼如下:
extern "C" __declspec(dllexport)
HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi)
//其中,hModule為進程模塊句柄;szImportMod為輸入庫名稱;pHookAPI為HOOKAPI結(jié)構(gòu)指針。
{
//定位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項內(nèi)容為替代函數(shù)地址。
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//恢復(fù)修改頁保護屬性。
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect,
&dwOldProtect);
}
}
pOrigThunk++;
pRealThunk++;
}
SetLastError(ERROR_SUCCESS); //設(shè)置錯誤為ERROR_SUCCESS,表示成功。
return TRUE;
}
(4) 定義替代函數(shù),此實例中只給MessageBoxA和recv兩個API進行擋截。代碼如下:
static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
//過濾掉原MessageBoxA的正文和標(biāo)題內(nèi)容,只顯示如下內(nèi)容, 。
return MessageBox(hWnd, "Hook API OK!", "Hook API", uType);
}
static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags )
{
//此處可以擋截游戲服務(wù)器發(fā)送來的網(wǎng)絡(luò)數(shù)據(jù)包,可以加入分析和處理數(shù)據(jù)代碼。
return recv(s,buf,len,flags);
}
(5) 在KeyboardProc函數(shù)中加入激活擋截API代碼,在if( wParam == 0X79 )語句中后面加入如下else if語句:
......
//當(dāng)激活F11鍵時,啟動擋截API函數(shù)功能。
else if( wParam == 0x7A )
{
HOOKAPI api[2];
api[0].szFunc ="MessageBoxA";//設(shè)置被擋截函數(shù)的名稱。
api[0].pNewProc = (PROC)MessageBoxA1;//設(shè)置替代函數(shù)的地址。
api[1].szFunc ="recv";//設(shè)置被擋截函數(shù)的名稱。
api[1].pNewProc = (PROC)recv1; //設(shè)置替代函數(shù)的地址。
//設(shè)置擋截User32.dll庫中的MessageBoxA函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"User32.dll",&api[0]);
//設(shè)置擋截Wsock32.dll庫中的recv函數(shù)。
HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&api[1]);
}
......
(6) 在ActiveKey.cpp中加入頭文件聲明 "#include "wsock32.h"。 從“工程”菜單中選擇“設(shè)置”,彈出Project Setting對話框,選擇Link標(biāo)簽,在“對象/庫模塊”中輸入Ws2_32..lib。
(7) 重新編譯ActiveKey項目,產(chǎn)生ActiveKey.dll文件,將其拷貝到Simulate.exe目錄下。運行Simulate.exe并啟動全局鉤子。激活任意應(yīng)用程序,按F11鍵后,運行此程序中可能調(diào)用MessageBoxA函數(shù)的操作,看看信息框是不是有所變化。同樣,如此程序正在接收網(wǎng)絡(luò)數(shù)據(jù)包,就可以實現(xiàn)封包功能了。
六、結(jié)束語
除了以上介紹的幾種游戲外掛程序常用的技術(shù)以外,在一些外掛程序中還使用了游戲數(shù)據(jù)修改技術(shù)、游戲加速技術(shù)等。在這篇文章里,就不逐一介紹了。
網(wǎng)絡(luò)游戲外掛核心封包揭密
[文章導(dǎo)讀]
網(wǎng)絡(luò)游戲的封包技術(shù)是大多數(shù)編程愛好者都比較關(guān)注的關(guān)注的問題之一,在這里就讓我們一起研究一下這一個問題吧,網(wǎng)絡(luò)游戲的封包技術(shù)是大多數(shù)編程愛好者都比較關(guān)注的關(guān)注的問題之一,在這里就讓我們一起研究一下這一個問題吧。
別看這是封包這一問題,但是涉及的技術(shù)范圍很廣范,實現(xiàn)的方式也很多(比如說APIHOOK,VXD,Winsock2都可以實現(xiàn)),在這里我們不可能每種技術(shù)和方法都涉及,所以我在這里以Winsock2技術(shù)作詳細講解,就算作拋磚引玉。
由于大多數(shù)讀者對封包類編程不是很了解,我在這里就簡單介紹一下相關(guān)知識:
APIHooK:
由于Windows的把內(nèi)核提供的功能都封裝到API里面,所以大家要實現(xiàn)功能就必須通過API,換句話說就是我們要想捕獲數(shù)據(jù)封包,就必須先要得知道并且捕獲這個API,從API里面得到封包信息。
VXD:
直接通過控制VXD驅(qū)動程序來實現(xiàn)封包信息的捕獲,不過VXD只能用于win9X。
winsock2:
winsock是Windows網(wǎng)絡(luò)編程接口,winsock工作在應(yīng)用層,它提供與底層傳輸協(xié)議無關(guān)的高層數(shù)據(jù)傳輸編程接口,winsock2是winsock2.0提供的服務(wù)提供者接口,但只能在win2000下用。
好了,我們開始進入winsock2封包式編程吧。
在封包編程里面我準(zhǔn)備分兩個步驟對大家進行講解:1、封包的捕獲,2、封包的發(fā)送。
首先我們要實現(xiàn)的是封包的捕獲:
Delphi的封裝的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我們要對winsock2在Delphi里面做一個接口,才可以使用winsock2。
1、如何做winsock2的接口?
1)我們要先定義winsock2.0所用得到的類型,在這里我們以WSA_DATA類型做示范,大家可以舉一仿三的來實現(xiàn)winsock2其他類型的封裝。
我們要知道WSA_DATA類型會被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家會發(fā)現(xiàn)WSData是引用參數(shù),在傳入?yún)?shù)時傳的是變量的地址,所以我們對WSA_DATA做以下封裝:
2)我們要從WS2_32.DLL引入winsock2的函數(shù),在此我們也是以WSAStartup為例做函數(shù)引入:
通過以上方法,我們便可以對winsock2做接口,下面我們就可以用winsock2做封包捕獲了,不過首先要有一塊網(wǎng)卡。因為涉及到正在運作的網(wǎng)絡(luò)游戲安全問題,所以我們在這里以IP數(shù)據(jù)包為例做封包捕獲,如果下面的某些數(shù)據(jù)類型您不是很清楚,請您查閱MSDN:
1)我們要起動WSA,這時個要用到的WSAStartup函數(shù),用法如下:
2)使用socket函數(shù)得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:
在程序里m_hSocket為socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均為常量。
3)定義SOCK_ADDR類型,跟據(jù)我們的網(wǎng)卡IP給Sock_ADDR類型附值,然后我們使用bind函數(shù)來綁定我們的網(wǎng)卡,Bind函數(shù)用法如下:
4)用WSAIoctl來注冊WSA的輸入輸出組件,其用法如下:
5)下面做死循環(huán),在死循環(huán)塊里,來實現(xiàn)數(shù)據(jù)的接收。但是徇環(huán)中間要用Sleep()做延時,不然程序會出錯。
6)在循環(huán)塊里,用recv函數(shù)來接收數(shù)據(jù),recv函數(shù)用法如下:
7)在buffer里就是我們接收回來的數(shù)據(jù)了,如果我們想要知道數(shù)據(jù)是什么地方發(fā)來的,那么,我們要定義一定IP包結(jié)構(gòu),用CopyMemory()把IP信息從buffer里面讀出來就可以了,不過讀出來的是十六進制的數(shù)據(jù)需要轉(zhuǎn)換一下。
看了封包捕獲的全過程序,對你是不是有點起發(fā),然而在這里要告訴大家的是封包的獲得是很容易的,但是許多游戲的封包都是加密的,如果你想搞清楚所得到的是什么內(nèi)容還需要自己進行封包解密。
四種網(wǎng)絡(luò)游戲外掛的設(shè)計方法
[文章導(dǎo)讀]
在幾年前我看到別人玩網(wǎng)絡(luò)游戲用上了外掛,做為程序員的我心里實在是不爽在幾年前我看到別人玩網(wǎng)絡(luò)游戲用上了外掛,做為程序員的我心里實在是不爽,想搞清楚這到底是怎么回事。就拿了一些來研究,小有心得,拿出來與大家共享,外掛無非就是分幾種罷了(依制作難度):
1、動作式,所謂動作式,就是指用API發(fā)命令給窗口或API控制鼠標(biāo)、鍵盤等,使游戲里的人物進行流動或者攻擊,最早以前的“石器”外掛就是這種方式。(這種外掛完全是垃圾,TMD,只要會一點點API的人都知道該怎么做,不過這種外掛也是入門級的好東東,雖然不能提高你的戰(zhàn)斗力,但是可以提高你的士氣)
2、本地修改式,這種外掛跟傳統(tǒng)上的一些游戲修改器沒有兩樣,做這種外掛在編程只需要對內(nèi)存地址有一點認(rèn)識并且掌握API就可以實現(xiàn),“精靈”的外掛這是這種方式寫成的,它的難點在于找到那些地址碼,找地址一般地要借助于別人的工具,有的游戲還有雙碼校驗,正正找起來會比較困難。(這種外掛,比上一種有一點點難度,但是這種外掛做起來能夠用,也是有一定難度的啦~~,這種外掛可以很快提升你對內(nèi)存地址的理解及應(yīng)用,是你編程技術(shù)提高的好東東)
3、木馬式,這種外掛的目的是幫外掛制作者偷到用戶的密碼(TMD,“爛”就一個字,不過要知已知彼所以還是要談一下啦~~),做這種外掛有一定的難度,需要HOOK或鍵盤監(jiān)視技術(shù)做底子,才可以完成,它的原理是先首截了用戶的帳號或密碼,然后發(fā)到指定郵箱。(我以前寫過這樣的東東,但是從來沒有用過,我知道這種東東很不道德,所以以后千萬別用呀!)
4、加速式,這種外掛可以加快游戲的速度……(對不起大家,這種東東我沒有實際做過,所以不能妄自評,慚愧) 這幾種外掛之中,前三種可以用VB,Delphi等語言比較好實現(xiàn),后兩種則要用VC等底層支持比較好的編程工具才好實現(xiàn)。
動作式外掛
首先,先來談一下動作式的外掛,這也是我第一次寫外掛時做的最簡單的一種。
記得還在“石器”時代的時候,我看到別人掛著一種軟件(外掛)人物就可以四外游走(當(dāng)時我還不知道外掛怎么回事),于是找了這種軟件過來研究(拿來后才聽別人說這叫外掛),發(fā)現(xiàn)這種東東其實實現(xiàn)起來并不難,仔佃看其實人物的行走無非就是鼠標(biāo)在不同的地方點來點去而已,看后就有實現(xiàn)這功能的沖動,隨后跑到MSDN上看了一些資料,發(fā)現(xiàn)這種實現(xiàn)這幾個功能,只需要幾個簡單的API函數(shù)就可以搞定:
1、首先我們要知道現(xiàn)在鼠標(biāo)的位置(為了好還原現(xiàn)在鼠標(biāo)的位置)所以我們就要用到API函數(shù)GetCursorPos,它的使用方法如下:
2、我們把鼠標(biāo)的位置移到要到人物走到的地方,我們就要用到SetCursorPos函數(shù)來移動鼠標(biāo)位置,它的使用方法如下:
3、模擬鼠標(biāo)發(fā)出按下和放開的動作,我們要用到mouse_event函數(shù)來實現(xiàn),具休使用方法用下:
在它的dwFlags處,可用的事件很多如移動MOUSEEVENTF_MOVE,左鍵按下MOUSEEVENTF_LEFTDOWN,左鍵放開MOUSEEVENTF_LEFTUP,具體的東東還是查一下MSDN吧~~~~~
好了,有了前面的知識,我們就可以來看看人物移走是怎么實現(xiàn)的了:
看了以上的代碼,是不是覺得人物的游走很簡單啦~~,舉一仿三,還有好多好東東可以用這個技巧實現(xiàn)(我早就說過,TMD,這是垃圾外掛的做法,相信了吧~~~),接下來,再看看游戲里面自動攻擊的做法吧(必需游戲中攻擊支持快捷鍵的),道理還是一樣的,只是用的API不同罷了~~~,這回我們要用到的是keybd_event函數(shù),其用法如下:
我們還要知道掃描碼不可以直接使用,要用函數(shù)MapVirtualKey把鍵值轉(zhuǎn)成掃描碼,MapVirtualKey的具體使用方法如下:
好了,比說此快接鍵是CTRL+A,接下來讓我們看看實際代碼是怎么寫的:
首先模擬按下了CTRL鍵,再模擬按下A鍵,再模擬放開A鍵,最后放開CTRL鍵,這就是一個模擬按快捷鍵的周期。
(看到這里,差不多對簡易外掛有了一定的了解了吧~~~~做一個試試?如果你舉一仿三還能有更好的東東出來,這就要看你的領(lǐng)悟能力了~~,不過不要高興太早這只是才開始,以后還有更復(fù)雜的東東等著你呢~~)
本地修改式外掛
現(xiàn)在我們來看看,比動作式外掛更進一步的外掛——本地修改式外掛的整個制作過程進行一個詳細的分解。
具我所知,本地修改式外掛最典型的應(yīng)用就是在“精靈”游戲上面,因為我在近一年前(“精靈”還在測試階段),我所在的公司里有很多同事玩“精靈”,于是我看了一下游戲的數(shù)據(jù)處理方式,發(fā)現(xiàn)它所發(fā)送到服務(wù)器上的信息是存在于內(nèi)存當(dāng)中(我看后第一個感受是:修改這種游戲和修改單機版的游戲沒有多大分別,換句話說就是在他向服務(wù)器提交信息之前修改了內(nèi)存地址就可以了),當(dāng)時我找到了地址于是修改了內(nèi)存地址,果然,按我的想法修改了地址,讓系統(tǒng)自動提交后,果然成功了~~~~~,后來“精靈”又改成了雙地址校檢,內(nèi)存校檢等等,在這里我就不廢話了~~~~,OK,我們就來看看這類外掛是如何制作的:
在做外掛之前我們要對Windows的內(nèi)存有個具體的認(rèn)識,而在這里我們所指的內(nèi)存是指系統(tǒng)的內(nèi)存偏移量,也就是相對內(nèi)存,而我們所要對其進行修改,那么我們要對幾個Windows API進行了解,OK,跟著例子讓我們看清楚這種外掛的制作和API的應(yīng)用(為了保證網(wǎng)絡(luò)游戲的正常運行,我就不把找內(nèi)存地址的方法詳細解說了):
1、首先我們要用FindWindow,知道游戲窗口的句柄,因為我們要通過它來得知游戲的運行后所在進程的ID,下面就是FindWindow的用法:
2、我們GetWindowThreadProcessId來得到游戲窗口相對應(yīng)進程的進程ID,函數(shù)用法如下:
DWORD GetWindowThreadProcessId(
HWND hWnd, // handle of window
LPDWORD lpdwProcessId // address of variable for process identifier
);
3、得到游戲進程ID后,接下來的事是要以最高權(quán)限打開進程,所用到的函數(shù)OpenProcess的具體使用方法如下:
在dwDesiredAccess之處就是設(shè)存取方式的地方,它可設(shè)的權(quán)限很多,我們在這里使用只要使用PROCESS_ALL_ACCESS 來打開進程就可以,其他的方式我們可以查一下MSDN。
4、打開進程后,我們就可以用函數(shù)對存內(nèi)進行操作,在這里我們只要用到WriteProcessMemory來對內(nèi)存地址寫入數(shù)據(jù)即可(其他的操作方式比如說:ReadProcessMemory等,我在這里就不一一介紹了),我們看一下WriteProcessMemory的用法:
5、下面用CloseHandle關(guān)閉進程句柄就完成了。
這就是這類游戲外掛的程序?qū)崿F(xiàn)部份的方法,好了,有了此方法,我們就有了理性的認(rèn)識,我們看看實際例子,提升一下我們的感性認(rèn)識吧,下面就是XX游戲的外掛代碼,我們照上面的方法對應(yīng)去研究一下吧:
這個游戲是用了多地址對所要提交的數(shù)據(jù)進行了校驗,所以說這類游戲外掛制作并不是很難,最難的是要找到這些地址。
木馬式外掛
木馬式外掛,可能大多像木馬吧,是幫助做外掛的人偷取別人游戲的帳號及密碼的東東。因為網(wǎng)絡(luò)上有此類外掛的存在,所以今天不得不說一下(我個人是非常討厭這類外掛的,請看過本文的朋友不要到處亂用此技術(shù),謝謝合作)。要做此類外掛的程序?qū)崿F(xiàn)方法很多(比如HOOK,鍵盤監(jiān)視等技術(shù)),因為HOOK技術(shù)對程序員的技術(shù)要求比較高并且在實際應(yīng)用上需要多帶一個動態(tài)鏈接庫,所以在文中我會以鍵盤監(jiān)視技術(shù)來實現(xiàn)此類木馬的制作。鍵盤監(jiān)視技術(shù)只需要一個.exe文件就能實現(xiàn)做到后臺鍵盤監(jiān)視,這個程序用這種技術(shù)來實現(xiàn)比較適合。
在做程序之前我們必需要了解一下程序的思路:
1、我們首先知道你想記錄游戲的登錄窗口名稱。
2、判斷登錄窗口是否出現(xiàn)。
3、如果登錄窗口出現(xiàn),就記錄鍵盤。
4、當(dāng)窗口關(guān)閉時,把記錄信息,通過郵件發(fā)送到程序設(shè)計者的郵箱。
第一點我就不具體分析了,因為你們比我還要了解你們玩的是什么游戲,登錄窗口名稱是什么。從第二點開始,我們就開始這類外掛的程序?qū)崿F(xiàn)之旅:
那么我們要怎么樣判斷登錄窗口雖否出現(xiàn)呢?其實這個很簡單,我們用FindWindow函數(shù)就可以很輕松的實現(xiàn)了:
實際程序?qū)崿F(xiàn)中,我們要找到'xx'窗口,就用FindWindow(nil,'xx')如果當(dāng)返回值大于0時表示窗口已經(jīng)出現(xiàn),那么我們就可以對鍵盤信息進行記錄了。
先首我們用SetWindowsHookEx設(shè)置監(jiān)視日志,而該函數(shù)的用法如下:
在這里要說明的是在我們程序當(dāng)中我們要對HOOKPROC這里我們要通過寫一個函數(shù),來實現(xiàn)而HINSTANCE這里我們直接用本程序的HINSTANCE就可以了,具體實現(xiàn)方法為:
而HOOKPROC里的函數(shù)就要復(fù)雜一點點:
以上就是記錄鍵盤的整個過程,簡單吧,如果記錄完可不要忘記釋放呀,UnHookWindowsHookEx(hHook),而hHOOK,就是創(chuàng)建setwindowshookex后所返回的句柄。
我們已經(jīng)得到了鍵盤的記錄,那么現(xiàn)在最后只要把記錄的這些信息發(fā)送回來,我們就大功造成了。其他發(fā)送這塊并不是很難,只要把記錄從文本文件里邊讀出來,用DELPHI自帶的電子郵件組件發(fā)一下就萬事OK了。代碼如下:
這個程序全部功能已經(jīng)實現(xiàn),編編試試。
加速型外掛
原本我一直以為加速外掛是針對某個游戲而寫的,后來發(fā)現(xiàn)我這種概念是不對的,所謂加速外掛其實是修改時鐘頻率達到加速的目的。
以前DOS時代玩過編程的人就會馬上想到,這很簡單嘛不就是直接修改一下8253寄存器嘛,這在以前DOS時代可能可以行得通,但是windows則不然。windows是一個32位的操作系統(tǒng),并不是你想改哪就改哪的(微軟的東東就是如此霸氣,說不給你改就不給你改),但要改也不是不可能,我們可以通過兩種方法來實現(xiàn):第一是寫一個硬件驅(qū)動來完成,第二是用Ring0來實現(xiàn)(這種方法是CIH的作者陳盈豪首用的,它的原理是修改一下IDE表->創(chuàng)建一個中斷門->進入Ring0->調(diào)用中斷修改向量,但是沒有辦法只能用ASM匯編來實現(xiàn)這一切*_*,做為高級語言使用者慘啦!),用第一種方法用點麻煩,所以我們在這里就用第二種方法實現(xiàn)吧~~~
在實現(xiàn)之前我們來理一下思路吧:
1、我們首先要寫一個過程在這個過程里嵌入?yún)R編語言來實現(xiàn)修改IDE表、創(chuàng)建中斷門,修改向量等工作
2、調(diào)用這個過程來實現(xiàn)加速功能
好了,現(xiàn)在思路有了,我們就邊看代碼邊講解吧:
首先我們建立一個過程,這個過程就是本程序的核心部份:
最核心的東西已經(jīng)寫完了,大部份讀者是知其然不知其所以然吧,呵呵,不過不知其所以然也然。下面我們就試著用一下這個過程來做一個類似于“變速齒輪”的一個東東吧!
先加一個窗口,在窗口上放上一個trackbar控件把其Max設(shè)為20,Min設(shè)為1,把Position設(shè)為10,在這個控件的Change事件里寫上:
因為windows默認(rèn)的值為$1742,所以我們把1742做為基數(shù),又因為值越小越快,反之越慢的原理,所以寫了這樣一個公式,好了,這就是“變速齒輪”的一個Delphi+ASM版了(只適用于win9X),呵呵,試一下吧,這對你幫助會很大的,呵呵。
在win2000里,我們不可能實現(xiàn)在直接對端口進行操作,Ring0也失了效,有的人就會想到,我們可以寫驅(qū)動程序來完成呀,但在這里我告訴你,windows2000的驅(qū)動不是一個VxD就能實現(xiàn)的,像我這樣的低手是寫不出windows所用的驅(qū)動WDM的,沒辦法,我只有借助外力實現(xiàn)了,ProtTalk就是一個很好的設(shè)備驅(qū)動,他很方便的來實現(xiàn)對低層端口的操作,從而實現(xiàn)加速外掛。
1、我們首先要下一個PortTalk驅(qū)動,他的官方網(wǎng)站是http://www.beyondlogic.org
2、我們要把里面的prottalk.sys拷貝出來。
3、建立一個Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下個pas 文件自己看吧)
4、實現(xiàn)加速外掛。
下面就講一下這程序的實現(xiàn)方法吧,如果說用ProtTalk來操作端口就容易多了,比win98下用ring權(quán)限操作方便。
1、新建一個工程,把剛剛下的接口文件和Protalk.sys一起拷到工程文件保存的文件夾下。
2、我們在我們新建的工程加入我們的接口文件
3、我們建立一個過程
4、先加一個窗口,在窗口上放上一個trackbar控件把其Max設(shè)為20,Min設(shè)為1,把Position設(shè)為10,在這個控件的Change事件里寫上:
就這么容易。
在內(nèi)存中修改數(shù)據(jù)的網(wǎng)游外掛
[文章導(dǎo)讀]
現(xiàn)在很多游戲都是把一些信息存入內(nèi)存單元的,那么我們只需要修改具體內(nèi)存值就能修改游戲中的屬性,很多網(wǎng)絡(luò)游戲也不外于此。
曾幾何時,一些網(wǎng)絡(luò)游戲也是可以用內(nèi)存外掛進行修改的,后來被發(fā)現(xiàn)后,這些游戲就把單一內(nèi)存地址改成多內(nèi)存地址校驗,加大了修改難度,不過仍然可以通過內(nèi)存分析器可以破解的。諸如“FPE”這樣的軟件便提供了一定的內(nèi)存分析功能。
“FPE”是基于內(nèi)存外掛的佼佼者,是家喻戶曉的游戲修改軟件。很多同類的軟件都是模仿“FPE”而得到玩家的認(rèn)可。而“FPE”實現(xiàn)的技術(shù)到現(xiàn)在都沒有公開,很多人只能夠通過猜測“FPE”的實現(xiàn)方法,實現(xiàn)同類外掛。筆者也曾經(jīng)模仿過“FPE”實現(xiàn)相應(yīng)的功能,如“內(nèi)存修改”、“內(nèi)存查詢”等技術(shù)。稍后會對此技術(shù)進行剖析。
既然要做內(nèi)存外掛,那么就必須對Windows的內(nèi)存機制有所了解。計算機的內(nèi)存(RAM)總是不夠用的,在操作系統(tǒng)中內(nèi)存就有物理內(nèi)存和虛擬內(nèi)存之分,因為程序創(chuàng)建放入物理內(nèi)存的地址都是在變化的,所以在得到游戲?qū)傩詴r并不能夠直接訪問物理內(nèi)存地址。在v86模式下,段寄存器使用方法與實模式相同,那么可以通過段寄存器的值左移4位加上地址偏移量就可以得到線性地址,而程序創(chuàng)建時在線性地址的中保留4MB-2GB的一段地址,游戲中屬性便放于此。在windows中把虛擬內(nèi)存塊稱之為頁,而每頁為4KB,在訪問內(nèi)存時讀取游戲?qū)傩詴r,為了不破壞數(shù)據(jù)完整性的快速瀏覽內(nèi)存地址值,最好一次訪問一頁。
在操作進程內(nèi)存時,不需要再使用匯編語言,Windows中提供了一些訪問進程內(nèi)存空間的API,便可以直接對進程內(nèi)存進行操作。但初學(xué)者一般掌握不了這一項技術(shù),為了使初學(xué)者也能夠?qū)?nèi)存進行操作,做出基于內(nèi)存控制的外掛,筆者把一些內(nèi)存操作及一些內(nèi)存操作邏輯進行了封裝,以控件形式提供給初學(xué)者。控件名為:MpMemCtl。
初學(xué)者在使用此控件時,要先安裝外掛引擎控件包(在此后的每篇文章中外掛引擎控件包僅提供與該文章相應(yīng)的控制控件),具體控件安裝方式,請參閱《Delphi指南》,由于篇幅所限,恕不能詳細提供。
在引擎安裝完成后,便可以在Delphi中的組件欄內(nèi),找到[MP GameControls]控件組,其中可以找到[MpMemCtl]控件。初學(xué)者可以使用此控件可以對內(nèi)存進行控制。
一、 得到進程句柄
需要操作游戲內(nèi)存,那么首先必須確認(rèn)要操作的游戲,而游戲程序在運行時所產(chǎn)生的每一個進程都有一個唯一的句柄。
使用控件得到句柄有三種方法:
1、 通過控件打開程序得到句柄。
在控件中,提供了startProgram方法,通過該方法,可以打開程序得到進程句柄,并且可以返回進程信息。
該方法提供了兩個參數(shù),第一個參數(shù)為要打開的程序路徑,第二個參數(shù)為打開程序后所創(chuàng)建進程的進程信息。使用這個方法在得到進程信息的同時,并給控件的ProcHandle(進程句柄)屬性進行了附值,這時可以使用控件直接對內(nèi)存進程讀寫操作。其應(yīng)用實例如下:
2、通過控件根據(jù)程序名稱得到句柄。
在控件中,對系統(tǒng)運行進程也有了相應(yīng)的描述,控件提供了兩個方法,用于根據(jù)程序名稱得到相應(yīng)的進程句柄。getProcIDs()可以得到系統(tǒng)現(xiàn)在所運行的所有程序的名稱列表。getProcID()可以通過所運行程序名稱,得到相應(yīng)進程的句柄。
其應(yīng)用實例如下:
首先可以通過getProcIDs()并把參數(shù)列表返回ComboBox1.Items里:
接著可以通過getProcID()得到相應(yīng)的進程句柄,并給控件的ProcHandle(進程句柄)屬性進行了附值,這時可以使用控件直接對內(nèi)存進程讀寫操作。
3、通過控件根據(jù)窗口名稱得到句柄。
在控件中,控件提供了兩個方法,用于根據(jù)窗口名稱得到相應(yīng)的進程句柄。可以通過getALLWindow()得到所有在進程中運行的窗口。getWinProcHandle()可以通過相應(yīng)的窗口名稱,得到相應(yīng)的進程的句柄。
其應(yīng)用實例如下:
首先可以通過getALLWindow ()并把參數(shù)列表返回ComboBox1.Items里:
接著可以通過getWinProcHandle ()得到相應(yīng)的進程句柄,并給控件的ProcHandle(進程句柄)屬性進行了附值,這時可以使用控件直接對內(nèi)存進程讀寫操作。
二、使游戲暫停
在程序中,為了便于更好的得到游戲的當(dāng)前屬性。在控件中提供了游戲暫停方法。只需要調(diào)用該方法,游戲便可以自由的暫停或啟動。該方法為:pauseProc()
控制類型只能夠傳入?yún)?shù)0或1,0代表使游戲暫停,1代表取消暫停。其應(yīng)用實例如下:
三、讀寫內(nèi)存值
游戲?qū)傩云鋵嵓拇嬖趦?nèi)存地址值里,游戲中要了解或修改游戲?qū)傩裕梢酝ㄟ^對內(nèi)存地值的讀出或?qū)懭胪瓿伞?br>
通過控件,要讀寫內(nèi)存地址值很容易。可以通過調(diào)用控件提供的getAddressvalue()及setAddressvalue()兩個方法即可,在使用方法之前,要確認(rèn)的是要給ProcHandle屬性進行附值,因為對內(nèi)存的操作必須基于進程。給ProcHandle屬性附值的方法,在上文中已經(jīng)介紹。無論是對內(nèi)存值進行讀還是進行寫,都要明確所要操作的內(nèi)存地址。
要注意的是,傳入內(nèi)存地址時,內(nèi)存地址必須為Pointer型。其應(yīng)用實例如下:
讀取地址值(如果“主角”等級所存放的地址為4549632):
這時avalue變量里的值為內(nèi)存地址[4549632]的值。
寫入地址值:
通過該方法可以把要修改的內(nèi)存地址值改為87,即把“主角”等級改為87。
四、內(nèi)存地址值分析
在游戲中要想要到游戲?qū)傩源娣诺膬?nèi)存地址,那么就對相應(yīng)內(nèi)存地址進行內(nèi)存分析,經(jīng)過分析以后才可得到游戲?qū)傩源娣诺娜舜娴刂贰?br>
控件提供兩種基于內(nèi)存地址的分析方法。一種是按精確地址值進行搜索分析,另一種是按內(nèi)存變化增減量進行搜索分析。
1、 如果很明確的知道當(dāng)前想要修改的地址值,那么就用精確地址值進行搜索分析
在游戲中,需要修改人物的經(jīng)驗值,那么首先要從游戲畫面上獲得經(jīng)驗值信息,如游戲人物當(dāng)前經(jīng)驗值為9800,需要把經(jīng)驗值調(diào)高,那么這時候就需要對人物經(jīng)驗值在內(nèi)存中搜索得到相應(yīng)的內(nèi)存地址,當(dāng)然很可能在內(nèi)存中地址值為9800的很多,第一次很可能搜索出若干個地址值為9800的地址。等待經(jīng)驗值再有所變化,如從9800變?yōu)榱?0000時,再次進行搜索,那么從剛剛所搜索到的地址中,便可以進一步獲得范圍更少的內(nèi)存地址,以此類推,那么最后可得到經(jīng)驗值具體存放的地址。
如要用控件來實現(xiàn)內(nèi)存值精確搜索,其實方法很簡單,只需要調(diào)用該控件的Search()方法即可。但是在搜索之前要確認(rèn)搜索的范圍,正如前文中所說:“而程序創(chuàng)建時在線性地址的中保留4MB-2GB的一段地址”,所以要搜索的地址應(yīng)該是4MB-2GB之間,所以要把控件的MaxAddress屬性設(shè)為2GB,把控件的MinAddress屬性設(shè)為4MB。還有一個需要確認(rèn)的是需要搜索的值,那么應(yīng)該把Searchvalue屬性設(shè)置為當(dāng)前搜索的值。如果需要顯示搜索進度那么可以把ShowGauge屬性掛上一個相應(yīng)的TGauge控件(該控件為進度條控件)。
在搜索分析時為了提高搜索效率、實現(xiàn)業(yè)務(wù)邏輯,那么需要傳入一個參數(shù),從而確認(rèn)是否是第一次進行內(nèi)存。其應(yīng)用實例如下:
2、 如果不明確當(dāng)前想要修改的地址值,只知道想要修改的值變大或變小,那么就按內(nèi)存變化增減量進行搜索分析。
如有些游戲的人物血值不顯示出來,但要對人物血值進行修改,那么只有借助于內(nèi)存量增減變化而進行搜索分析出該人物血值存放的地址。如果人物被怪物打了一下,那么人物血值就會減少,那么這時候就用減量進行搜索分析,如果人物吃了“血”人物血值就會增加,那么這時候就用增量進行搜索分析。經(jīng)過不斷搜索,最后會把范圍放血值的內(nèi)存地址給搜索出來。
如要用控件來實現(xiàn)內(nèi)存值精確搜索,其實方法很簡單,只需要調(diào)用該控件的compare()方法即可。MaxAddress、MinAddress屬性設(shè)置上面章節(jié)中有詳細介紹,在此不再重提。在此分析中不需要再指定Searchvalue屬性。如果需要顯示搜索進度那么可以把ShowGauge屬性掛上一個相應(yīng)的TGauge控件。
在搜索分析時為了提高搜索效率、實現(xiàn)業(yè)務(wù)邏輯,那么需要傳入一個參數(shù),從而確認(rèn)是否是第一次進行內(nèi)存。搜索分析類型有兩種:如果參數(shù)值為0,那么就代表增量搜索。如果參數(shù)值為1,那么就代表減量搜索。其應(yīng)用實例如下:
五、得到內(nèi)存地址值
在控件中,提供獲得分析后內(nèi)存地址列表的方法,只需要調(diào)用getAddressList()方法,便可以獲得分析過程中或分析結(jié)果地址列表。但如果使用的是按內(nèi)存變化增減量進行搜索分析的方法,那么第一次可能會搜索出來很多的地址,致使返回速度過長,那么建議使用getAddressCount()方法確定返回列表為一定長度后才給予返回。
其應(yīng)用實例如下:
通過以上五個步驟,便可以整合成一個功能比較完備的,基于內(nèi)存控制方法的游戲外掛。有了“FPE”的關(guān)鍵部份功能。利用此工具,通過一些方法,不僅僅可以分析出來游戲?qū)傩詥蝺?nèi)存地址,而且可以分析出一部份多內(nèi)存游戲?qū)傩源娣诺刂贰?/font>