Hacking Diablo II之D2HACKIT技術詳解

趁着聖誕又歇了幾天,沒博,倒是主動被動的看了不少片子。我發現我的觀影口味挺雜,什麼都看,還什麼都能看的津津有味。這些片中,有肥皂劇型的“Ally McBeal”(甜心俏佳人),柯恩兄弟的黑幫型的"Miller's Crossing",悶騷型的“Country Life”,一直想看的“北京樂與路”,還有成龍大叔的“神話”。
前幾天有博友留言希望我多談點兒d2hackit,那我就隨便說說。
D2中的外掛,比較有代表性的大致可以分爲三類:以maphack爲代表的輔助型,以幫助玩家更有效的玩遊戲爲目的;以D2JSP爲代表的BOT型,用於在無人值守的狀態下自動進行遊戲;還有一類就是以d2hackit爲代表的全能型,bot、pickit、dupe、pk hack、packet sniffer、trade hack、drop hack什麼都能做,D2中dupe氾濫有一部分原因要歸功於它,比較有代表性的基於d2hackit的插件外掛有09時期的pickit、PindleBot,和1.10時期的zPickit。
從結果上看,d2hackit無疑是成功的,然而從軟件設計角度看,它當初的部分設計目標並沒有達到。d2hackit的設計意圖,是要實現一個外掛的開發平臺,其他開發者只需用這個平臺提供的基礎設施,不必具備逆向工程,甚至彙編語言的能力就能夠做出外掛。按照d2hackit原作者thohell的設想,d2hackit甚至不必侷限於D2,而是一個通用的外掛開發平臺,可以用於其他遊戲。
降低外掛開發的難度這一點d2hackit是做到了,d2hackit插件氾濫就說明了它的成功。通用平臺這一目標它顯然沒有達到,別說用於其它遊戲,在09補丁上能用的插件,在1.10上都用不了,必須重新編譯甚至修改源代碼。
d2hackit的基本工作原理,跟我以前寫的一篇介紹外掛工作原理的文章裏說的大致相同:d2hackit的加載、卸載,地址(旁路點、遊戲內部函數和全局變量)的定位,以及旁路點被觸發時的處理等幾部份。不同的是d2hackit做爲一個外掛平臺,還需要加載它的插件,並在適當時刻(收、發數據包,進入、退出遊戲等)通知插件。下面逐一分析。
1,d2hackit的加載和卸載。 d2hackit在本質上就是一個windows的DLL,因此任何能在遊戲進程加載、卸載DLL的方法都適用於d2hackit。d2hackit 自帶的loader用的是遠程DLL注入的方法,另外d2hackit本身也可以做爲d2loader的插件在遊戲程序運行時自動加載。
2,地址的定位。d2hackit對地址的定位是比較有特色的,它除了支持基址+偏移量的通常做法,還支持指紋式的內存數據匹配搜索,也就是說它可以通過搜索特定模式的字節序列來定位內存地址。這樣設計的意圖是在遊戲補丁升級時只需更新指紋數據就可以讓d2hackit工作於新版本補丁(當然大家都知道這個目標沒有達到)。指紋模式在配置文件中指定(d2hackit.ini),格式如下:
; Name=Module,patchsize,offset,fingerprint
GamePacketReceivedIntercept
=D2Client.dll,7,13,8B5C2410xxxxxxxxxxxx8D145B8B0495
上面這行指定了接收數據包的旁路點地址的匹配模式:在d2client.dll中搜索8B5C2410xxxxxxxxxxxx8D145B8B0495特徵數據,每xx匹配任意1字節數據。7爲patch長度,13爲距離匹配地址的偏移。
3,旁路點的事件處理。以接收遊戲數據包的處理爲例。在安裝了GamePacketReceivedIntercept旁路點以後,每當收到遊戲內數據包時,d2hackit的相應旁路點處理例程就會先獲取控制權。爲了不破壞原先的寄存器內容,旁路點處理例程必須先保存必要的寄存器然後再做真正的處理。因此處理例程分爲兩部分,用匯編寫的GamePacketReceivedInterceptSTUB函數和C函數GamePacketReceivedIntercept。
DWORD __fastcall GamePacketReceivedIntercept(BYTE* aPacket, DWORD aLength)
{
    
// Pass packet to all clients who wants to snoop
    LinkedItem *li = ClientModules.GetFirstItem();
    
while(li)
    {    
        PCIS pCIS 
= ClientModules.GetCIS(li);
        
if(pCIS && pCIS->OnGamePacketBeforeReceived)
        {
            aLength 
= pCIS->OnGamePacketBeforeReceived(aPacket, aLength);
            
if(!aLength) break;
        }
        li 
= ClientModules.GetNextItem(li);
    }
    
return aLength;
}
上面是d2hackit的收到數據包時的處理代碼,很簡單,就是遍歷加載的插件模塊,挨個調用插件模塊中的OnGamePacketBeforeReceived函數進行進一步的分析。
4,d2hackit插件模塊。d2hackit的插件以d2h爲後綴,其實也是普通的DLL。做爲d2hackit的插件,d2h必須提供一些回調入口點(即DLL的導出函數),供d2hackit在必要的時刻調用。這些入口點有:
BOOL EXPORT OnClientStart();
BOOL EXPORT OnClientStop
();
DWORD EXPORT OnGameTimerTick
();
BOOL EXPORT OnGameCommandLine
(char** argv, int argc);
DWORD EXPORT OnBnetPacketBeforeSent
(BYTE* aPacket, DWORD aLen);
DWORD EXPORT OnBnetPacketBeforeReceived
(BYTE* aPacket, DWORD aLen);
DWORD EXPORT OnRealmPacketBeforeSent
(BYTE* aPacket, DWORD aLen);
DWORD EXPORT OnRealmPacketBeforeReceived
(BYTE* aPacket, DWORD aLen);
DWORD EXPORT OnGamePacketBeforeSent
(BYTE* aPacket, DWORD aLen);
DWORD EXPORT OnGamePacketBeforeReceived
(BYTE* aPacket, DWORD aLen);
LPCSTR EXPORT GetModuleAuthor
();
LPCSTR EXPORT GetModuleWebsite
();
LPCSTR EXPORT GetModuleEmail
();
LPCSTR EXPORT GetModuleDescription
();
DWORD EXPORT GetModuleVersion
();
VOID EXPORT OnGameJoin
(THISGAMESTRUCT* thisgame);
VOID EXPORT OnGameLeave
(THISGAMESTRUCT* thisgame);
通過名字你就能猜出它們在什麼時候會被d2hackit調用,比如說收到數據包時就是OnGamePacketBeforeReceived
d2hackit插件的加載有兩種方法,一種是在d2hackit.ini裏設定隨着d2hackit本身加載時自動加載,另一種可以由玩家在進入遊戲後在聊天輸入框中以特定的命令加載(d2hackit截獲了聊天輸入框的處理)。
5,d2hackit也給插件模塊提供了一組接口(API),供插件模塊使用。比如在OnGamePacketBeforeReceived函數中,插件可以直接修改數據包buffer的數據,但有的時候你需要向玩家顯示分析結果或者向服務器發送一些定製數據包,這時候就得調用d2hackit提供的API。基本的API大概有這些:
DWORD    EXPORT GameSendPacketToServer(LPBYTE buf, DWORD len);
DWORD    EXPORT BnetSendPacketToServer(LPBYTE buf, DWORD len);
DWORD    EXPORT RealmSendPacketToServer(LPBYTE buf, DWORD len);
BOOL    EXPORT GameSendMessageToChat(LPSTR msg);
DWORD    EXPORT GameSendPacketToGame(LPBYTE buf, DWORD len);
BOOL    EXPORT GameInsertPacketToGame(LPBYTE buf, DWORD len);
BOOL    EXPORT GamePrintInfo(LPCSTR buf);
BOOL    EXPORT GamePrintError(LPCSTR buf);
BOOL    EXPORT GamePrintVerbose(LPCSTR buf);
BOOL    EXPORT GamePrintString(LPCSTR buf);
DWORD    EXPORT GameSendPacketToServer(LPBYTE buf, DWORD len);
PTHISGAMESTRUCT EXPORT GetThisgameStruct(
void);
PSERVERINFO EXPORT GetServerInfo(DWORD dwVersion, LPCSTR szModule);
BOOL    EXPORT GameSaveAndExit();

這樣,d2hackit的插件通過處理特定的事件和調用d2hackit提供的API就可以實現它想要的功能,無須彙編和逆向工程知識。由於網絡遊戲的功能實現非常依賴於數據包,因此通過截獲數據包的收發來分析、篡改、僞造數據包可以完成絕大部分外掛的功能。

6,d2hackit的升級。由於d2hackit利用遊戲的特定函數和數據進行工作,每次D2升級補丁d2hackit本身也必須重定位這些地址。做一個可以用於1.11b補丁的d2hackit其實也主要就是這個工作。比起d2hackmap來,d2hackit用的的地址很少(10個),難度小多了。要定位的地址有:
GamePacketReceivedIntercept=D2Client.dll,7,13,8B5C2410xxxxxxxxxxxx8D145B8B0495
GamePacketReceivedIntercept2=D2Client.dll,7,18,85C9894C24xx0F8FxxxxxxxxFF05xxxxxxxx5F5E5D
GamePacketSentIntercept=D2Net.dll,5,0,!10005
GamePlayerInfoIntercept=D2Client.dll,6,10,E8xxxxxxxxE8xxxxxxxx8935xxxxxxxx5EC3
pPlayerInfoStruct=D2Client.dll,0,12,E8xxxxxxxxE8xxxxxxxx8935xxxxxxxx5EC3
GamePrintStringLocation=D2Client.dll,0,0,81ECxxxxxxxx53558BE956578A45xx84C0
LoaderStruct=Game.exe,0,0,1d10abd1
GameKeyDownIntercept=D2Client.dll,5,5,8B770833D2xxxxxxxxxxxxC0668B41043BC6
GameSendPacketToGameLocation=d2net.dll,0,21,81EC0C010000538B1Dxxxxxxxx5556
GameSaveAndExit=D2client.dll,0,0,33c9e8xxxxxxxxc705xxxxxxxxxxxxxxxxe8xxxxxxxxc705
GameSendMessageToChat=bnclient.dll,0,15,C390909090909090909090909090908BD1
pGameInfoStruct=d2client.dll,0,0,#6FAA1C5A

第二個要做的工作是修改旁路點的入口代碼,因爲不同的d2補丁具體代碼可能有變化,對寄存器的利用也有所不同。具體的說就是要改這幾個函數:
void    GamePacketReceivedInterceptSTUB();
void    GamePacketReceivedIntercept2STUB();
void    BnetPacketReceivedSaveSTUB();
void    BnetPacketReceivedInterceptSTUB();
void    GamePacketSentInterceptSTUB();
void    BnetPacketSentInterceptSTUB();
void    GamePlayerInfoInterceptSTUB();

第三步是修正兩個遊戲內部的數據結構:
typedef struct playerinfostruct_t_110
{
    BYTE        UnitType;        
// +0x00
    DWORD        CharacterClass;    // +0x04
    DWORD        unknown1;        // +0x08
    DWORD        PlayerID;        // +0x0c
    DWORD        PlayerLocation;    // +0x10 (defines location somehow in or out of town and maybe other locations (0x05=in town, 0x01=out of town)
    LPCSTR        PlayerName;        // +0x14 pointer to LPSZ player name
    BYTE        Act;            // +0x18
    BYTE        unknown2[0x73];    // +0x19
    WORD        PlayerPositionX;// +0x8c
    WORD        PlayerPositionY;// +0x8e
    
}
 PLAYERINFOSTRUCT_110, *PPLAYERINFOSTRUCT_110;

typedef 
struct gamestruct_t_110
{
    
char    Unknown1[0x1a];
    
char    GameName[0x10];
    
char    Unknown2[0x08];
    
char    ServerIP[0x10];
    
char    Unknown3[0x46];
    
char    AccountName[0x10];
    
char    Unknown4[0x20];
    
char    CharacterName[0x10];
    
char    Unknown5[0x08];
    
char    RealmName[0x10];
    
char    Unknown6[0x147];
    
char    RealmName2[0x10];
    
char    Unknown7[0x08];
    
char    GamePassword[0x10];

}
 GAMESTRUCT_110, *PGAMESTRUCT_110;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章