javascript
Hacking Diablo II之外挂实战教程:去除D2JSP试用版显示的Trial Version信息
前幾天一個(gè)網(wǎng)友給我發(fā)消息請(qǐng)我?guī)退麄€(gè)忙。他的問(wèn)題是,他正在使用的D2JSP是免費(fèi)試用版,試用版在運(yùn)行時(shí)會(huì)在游戲的所有游戲畫(huà)面中央顯示一行很大的“Trial Version”字樣(見(jiàn)下圖中的紅圈),很煩人,他想去掉這行字。我想正好用此做個(gè)教程解釋前面介紹過(guò)的hack工作原理,于是答應(yīng)幫他看看。
我已經(jīng)很久沒(méi)有開(kāi)BOT了,最后一次使用D2JSP還是2003年1.09d時(shí)期的事。根據(jù)我以前的理解,D2JSP和其他hacks一樣,由兩部分組成,d2jsp.exe只是一個(gè)loader,負(fù)責(zé)把d2jsp.dll加載到游戲進(jìn)程。而d2jsp.dll才是D2JSP的運(yùn)行引擎-真正發(fā)揮作用的東西。因此“Trial Version”字符串肯定在d2jsp.dll中。最開(kāi)始的想法是“Trial Version”可能會(huì)以一個(gè)常量字符串的形式保存在d2jsp.dll中,因此最容易想到的做法是用UltraEdit打開(kāi)d2jsp.dll,尋找“Trial Version”,找到的話把它改為空字符串就OK了。然而打開(kāi)后發(fā)現(xiàn)d2jsp.dll是加了殼的,這才想起自從D2 1.10以來(lái)D2JSP開(kāi)始收費(fèi),采取了一些措施防止別人破解,加殼就是其中之一(當(dāng)然還是被人破了)。在d2jsp.dll文件的前面幾行看到了aspack字串,看來(lái)加的是aspack殼。脫殼我很不在行,但是我知道有一些自動(dòng)脫殼工具,針對(duì)aspack的脫殼工具也肯定有,不妨試試,于是到網(wǎng)上抓了個(gè)脫殼軟件-AspackDie。當(dāng)然試驗(yàn)的結(jié)果是失敗了。又想,不管怎樣,d2jsp.dll運(yùn)行的時(shí)候它自己得脫殼吧,那我可以在它自己脫殼后在進(jìn)程內(nèi)存中找吧。于是加載D2JSP后用WinHex打開(kāi)游戲進(jìn)程,查找Trial Version,然而還是沒(méi)找到??磥?lái)可能用了即時(shí)脫殼技術(shù),或者干脆Trial Version根本不是常量字符串,比如我知道如果在C/C++中這樣寫(xiě)szVersion就不是常量字符串:
?
char?szVersion[]?=?{'T',?'r',?'i',?'a',?'l',?'?',?'V',?'e',?'r',?'s',?'i',?'o',?'n',?''};然后怎么辦,繼續(xù)研究脫殼?不,其實(shí)我早就有了更好、更通用的解決方法,但是需要編程?,F(xiàn)在看來(lái)簡(jiǎn)單的思路行不通,還是寫(xiě)程序算了。換個(gè)角度來(lái)看,不管字符串是什么,總得調(diào)用某個(gè)函數(shù)來(lái)輸出顯示吧,看上圖中的字樣,顯然是調(diào)用了Diablo II的內(nèi)部函數(shù)顯示的,因此如果能截獲這個(gè)函數(shù)過(guò)濾掉"Trial Version"字符串就搞定了。而且這種方法不用修改D2JSP程序,對(duì)所有D2JSP版本都起作用,因此更通用。問(wèn)題是它調(diào)用了D2X的哪個(gè)函數(shù)呢?我想起我的d2hackmap中也用到了類(lèi)似的字符串顯示的功能,它們用的有可能是同一個(gè)!于是把d2hackmap的源代碼翻出來(lái)看了看,發(fā)現(xiàn)了d2win.dll中導(dǎo)出序號(hào)為10064(Diablo II中所有dll導(dǎo)出的函數(shù)都只有序號(hào)沒(méi)有名字)的函數(shù)做此用途,其函數(shù)聲明為:
?
void?__fastcall?D2WIN_DrawText(wchar_t?*str,?int?xpos,?int?ypos,?DWORD?color,?DWORD?unknown);?然后用VC++ attach到游戲進(jìn)程進(jìn)行調(diào)試,想在D2WIN_DrawText設(shè)斷點(diǎn)監(jiān)控,結(jié)果發(fā)現(xiàn)調(diào)試器剛attach上去D2JSP就會(huì)崩潰??磥?lái)它做了反調(diào)試處理,這也在意料之中。還是直接寫(xiě)個(gè)DLL注入到游戲進(jìn)程截獲D2WIN_DrawText來(lái)觀測(cè)得了,字符串可以用Win32 API OutputDebugString輸出然后用DbgView觀測(cè):
?
void?__fastcall?MyD2WIN_DrawText(wchar_t?*str,?int?xpos,?int?ypos,?DWORD?color,?DWORD?unknown)...{
????if?(str)?OutputDebugStringW(str);
}
結(jié)果發(fā)現(xiàn)“Trial Version”果然是通過(guò)D2WIN_DrawText輸出的,見(jiàn)下圖。
現(xiàn)在只要改變MyD2WIN_DrawText()的實(shí)現(xiàn),判斷輸出字符串如果為"Trial Version"則跳過(guò)真正的D2WIN_DrawText()代碼就搞定了,代碼見(jiàn)后面的詳細(xì)源代碼分析。改變后的顯示結(jié)果如下圖,畫(huà)面上方的d2jsp v1.2.0字樣還在,Trial Version字樣已經(jīng)沒(méi)了:
至此,給D2JSP打補(bǔ)丁的程序d2jsppatch.dll已經(jīng)做好了,然而還有一個(gè)問(wèn)題,就是d2jsppatch.dll如何加載呢?用D2JSP開(kāi)的BOT一般是無(wú)人職守自動(dòng)運(yùn)行的,因此d2jsppatch.dll最好能夠隨著d2jsp.dll自動(dòng)加載,否則每次都需要人工加載那就太麻煩了。那怎么做到隨d2jsp.dll自動(dòng)加載呢?一個(gè)辦法是把d2jsp.dll改名為d2jsp2.dll,d2jsppatch.dll改名為d2jsp.dll,這樣d2jsp.exe每次加載d2jsp.dll時(shí)其實(shí)加載的是d2jsppatch.dll。d2jsppatch.dll加載起來(lái)后,在它的啟動(dòng)代碼里再手工加載d2jsp2.dll(即原來(lái)的d2jsp.dll),這樣就達(dá)到了自動(dòng)加載d2jsppatch.dll的目的。這種方法的缺點(diǎn)是它改變了D2JSP原有模塊之間的關(guān)系,可能會(huì)導(dǎo)致一些兼容性問(wèn)題,比如d2jsp.exe可能會(huì)校驗(yàn)d2jsp.dll檢查它是否被人改過(guò),或者某個(gè)依賴于d2jsp.dll工作的程序可能會(huì)用LoadLibrary("d2jsp.dll")/GetProcAddress()獲取d2jsp.dll的到處函數(shù),這時(shí)就會(huì)失敗。
伴隨d2jsp.dll自動(dòng)加載的另外一種做法是把d2jsppatch.dll靜態(tài)綁定到d2jsp.dll或者d2jsp.dll會(huì)用到的某個(gè)DLL。由于d2jsp.dll加了殼,DLL文件的IAT(Import Address Table,即導(dǎo)入表)已經(jīng)被破壞,最好是從d2jsp.dll會(huì)用到的DLL下手。我們知道D2JSP支持jscript腳本程序,它肯定會(huì)加載jscript腳本引擎js32.dll,所以我們可以把d2jsppatch.dll靜態(tài)綁定到j(luò)s32.dll上去。熟悉Windows編程且喜歡玩API hooking的朋友可能知道微軟的Detours Library,其中附帶了一個(gè)程序setdll.exe可以把一個(gè)DLL靜態(tài)綁定到其他EXE或者DLL。在命令行運(yùn)行:
?
setdll.exe?-d:d2jsppatch.dll?js32.dll就把d2jsppatch.dll綁定到了js32.dll,如下圖。
以下是對(duì)d2jsppatch.dll源代碼的詳細(xì)分析,給感興趣的朋友參考。d2jsppatch.dll雖然是針對(duì)D2JSP的,但是它包含了一個(gè)hack的所有基本組成部分,而且功能很簡(jiǎn)單,用它來(lái)了解hack的工作原理是很合適的。d2maphack、d2hackmap等hacks的工作原理和它完全一樣,不同的只是實(shí)現(xiàn)的功能。
1,d2jsppatch.dll的加載和卸載。DLL入口函數(shù)DllMain中一般用來(lái)做安裝(InstallPatch)、卸載(RemovePatch)旁路點(diǎn)(detour patch)。安裝、卸載工作可能導(dǎo)致程序崩潰(見(jiàn)以前的文章),放在DllMain中進(jìn)行最合適,因?yàn)閃indows保證在DllMain中的代碼執(zhí)行時(shí)進(jìn)程內(nèi)的其他線程不會(huì)運(yùn)行,見(jiàn):http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx
?
BOOL?APIENTRY?DllMain(HANDLE?hModule,?DWORD?dwReason,?LPVOID?lpReserved)...{
????if?(DLL_PROCESS_ATTACH?==?dwReason)
????...{
????????DisableThreadLibraryCalls((HMODULE)hModule);
????????return?InstallPatch();
????}
????else?if(DLL_PROCESS_DETACH?==?dwReason)
????...{
????????RemovePatch();
????}
????return?TRUE;
}
?2,旁路點(diǎn)的安裝和卸載。安裝旁路點(diǎn)一般就是往旁路點(diǎn)處插入一個(gè)跳轉(zhuǎn)(call或jmp,5字節(jié))指令,讓程序運(yùn)行到此處時(shí)跳轉(zhuǎn)到自己的patch代碼。要注意程序代碼段的頁(yè)面屬性一般都是只讀的,安裝前先要把頁(yè)面屬性改成可讀寫(xiě),裝完后再恢復(fù)原來(lái)的頁(yè)面屬性。卸載旁路點(diǎn)就是把patch點(diǎn)原先的代碼寫(xiě)回去,因此在安裝時(shí)要做一下備份。
?
void?WriteLocalBYTES(void*?pAddress,?void?*buf,?int?len)...{
????DWORD?oldprot?=?0,?dummy;
????VirtualProtect(pAddress,?len,?PAGE_EXECUTE_READWRITE,?&oldprot);
????WriteProcessMemory(GetCurrentProcess(),?pAddress,?buf,?len,?&dummy);
????VirtualProtect(pAddress,?len,?oldprot,?&dummy);
}
void?PatchCALL(DWORD?pOldCode,?DWORD?pNewCode,?DWORD?len)
...{
????BYTE?buf1[5];
????buf1[0]?=?0xe8;?//?inst?call
????*(DWORD?*)(buf1+1)?=?pNewCode-(pOldCode+5);
????WriteLocalBYTES((void*)pOldCode,?buf1,?len);
}
FARPROC?g_pDrawText;
BYTE?g_oldcode[5];
BOOL?InstallPatch()
...{
????HMODULE?hD2Win?=?GetModuleHandle("d2win.dll");
????if?(!hD2Win)?hD2Win?=?LoadLibrary("d2win.dll");
????if?(hD2Win)
????...{
????????g_pDrawText?=?GetProcAddress(hD2Win,?(LPCSTR)10064);
????????if?(g_pDrawText)
????????...{
????????????memcpy(g_oldcode,?g_pDrawText,?5);
????????????PatchCALL((DWORD)g_pDrawText,?(DWORD)D2WinDrawTextPatch_ASM,?5);
????????????return?TRUE;
????????}
????}
????return?FALSE;
}
void?RemovePatch()
...{
????if?(g_pDrawText)
????????WriteLocalBYTES(g_pDrawText,?g_oldcode,?5);
}
3,旁路點(diǎn)的patch代碼。patch代碼根據(jù)所要實(shí)現(xiàn)的功能的不同而不同,需要具體情況具體分析。一般來(lái)說(shuō)邏輯稍微復(fù)雜些的patch代碼會(huì)由兩步分組成,一是用匯編寫(xiě)的入口代碼,對(duì)寄存器和棧指針進(jìn)行精細(xì)控制以免破壞原先的數(shù)據(jù);二是用C/C++寫(xiě)的邏輯實(shí)現(xiàn)代碼。匯編入口代碼調(diào)用C/C++的邏輯實(shí)現(xiàn)代碼。在此例中,D2WinDrawTextPatch_ASM為純(嵌入)匯編函數(shù),第一行代碼運(yùn)行前的寄存器內(nèi)容和棧布局如注釋所示,D2WinDrawTextPatch有5個(gè)參數(shù),由于是__fastcall,因此前兩個(gè)參數(shù)在ecx和edx中傳遞(VC的規(guī)定,C++ Builder可能有所不同),剩下三個(gè)從棧中傳入。由于C函數(shù)D2WinDrawTextPatch可能破壞ecx和edx里的內(nèi)容,因此調(diào)用前必須保存它們的值,然后調(diào)用D2WinDrawTextPatch做真正的字符串過(guò)濾,注意D2WinDrawTextPatch也聲明為_(kāi)_fastcall,因此參數(shù)str為ecx。根據(jù)str是否為"Trial Version",D2WinDrawTextPatch返回TRUE或FALSE(返回值存放在eax中)。D2WinDrawTextPatch_ASM在D2WinDrawTextPatch返回后先恢復(fù)ecx和edx的原先內(nèi)容,接著根據(jù)eax的值判斷是否應(yīng)該執(zhí)行真正的D2WinDrawText函數(shù)來(lái)顯示字符串。標(biāo)號(hào)draw_text后面的代碼流程將跳到真正的D2WinDrawText去執(zhí)行,由于旁路點(diǎn)把它的前兩個(gè)指令(共5個(gè)字節(jié))改掉了(見(jiàn)下圖,上半部分為patch前的代碼,下半部分為patch后的代碼),在跳轉(zhuǎn)之前必須先執(zhí)行這兩條指令。 BOOL?__fastcall?D2WinDrawTextPatch(wchar_t?*str)
...{
????return?str?&&?(*str?==?L'T'?&&?wcscmp(str,?L"Trial?Version")?==?0);
}
/**//*
*?[esp+0x10]?=?arg?5?-?unknown
*?[esp+0x0c]?=?arg?4?-?col
*?[esp+0x08]?=?arg?3?-?ypos
*?[esp+0x04]?=?return?address?of?caller?of?D2WIN_DrawText,?e.g.,?from?D2JSP
*?[esp+0x00]?=?return?address?of?D2WIN_DrawText
*?edx?=?arg?2,?xpos
*?ecx?=?arg?1,?text?string
*/
void?__declspec(naked)?D2WinDrawTextPatch_ASM()
...{
????__asm
????...{
????????push?edx;?//?arg?2?-?xpos
????????push?ecx;?//?arg?1?-?str
????????call?D2WinDrawTextPatch;
????????pop?ecx;
????????pop?edx;
????????test?eax,?eax;
????????jz?draw_text;
????????pop?eax;?//?discard?return?address?of?caller?of?D2WIN_DrawText
????????ret?0x0c;?//?discard?three?arguments
draw_text:
????????pop?eax;?//?return?address?of?D2WIN_DrawText
????????//?original?code,?overwritten?by?detour?patch
????????push?ebx;
????????mov?ebx,?dword?ptr?[esp+0x10];
????????jmp?eax;
????}
}
4,給靜態(tài)綁定用的空導(dǎo)出函數(shù)。在一般的hack中這一部分是不需要的。setdll.exe在做靜態(tài)綁定時(shí)需要把d2jsppatch.dll的一個(gè)導(dǎo)出函數(shù)(隨便一個(gè),有一個(gè)就行)插入js32.dll的IAT中。
?
extern?"C"?__declspec(?dllexport?)?VOID?FakedInterface()...{
}
?5,做為一個(gè)行為良好、成熟的hack,在安裝旁路點(diǎn)前還應(yīng)該檢測(cè)自己用到的旁路點(diǎn)有沒(méi)有已經(jīng)被其他hack改掉了,否則就會(huì)導(dǎo)致其他hack不能正常工作甚至整個(gè)游戲崩潰。如果別人已經(jīng)改掉了,自己要么額外處理要么只好主動(dòng)退出。那么如果要patch很多點(diǎn)(像maphack、hackmap有上百個(gè)),怎么檢查這些點(diǎn)是否被改了呢?一個(gè)快速而且有效的做法是對(duì)這些點(diǎn)的內(nèi)容預(yù)先計(jì)算一個(gè)校驗(yàn)和,運(yùn)行時(shí)每次安裝旁路點(diǎn)前再校驗(yàn)一遍然后和預(yù)先計(jì)算的值比較。d2jsppatch.dll的功能很簡(jiǎn)單,沒(méi)有做這部分處理,具體做法請(qǐng)參考d2hackmap 2.24的源代碼,也可以到這里下載:http://newd2event.net/index.php?id=hacks/Sting_Hackmap。
附:完整的d2jsppatch.dll源代碼,只有短短的100行不到。
#define?WIN32_LEAN_AND_MEAN#include?<windows.h>
//?helper?stuff
void?WriteLocalBYTES(void*?pAddress,?void?*buf,?int?len)
...{
????DWORD?oldprot?=?0,?dummy;
????VirtualProtect(pAddress,?len,?PAGE_EXECUTE_READWRITE,?&oldprot);
????WriteProcessMemory(GetCurrentProcess(),?pAddress,?buf,?len,?&dummy);
????VirtualProtect(pAddress,?len,?oldprot,?&dummy);
}
void?PatchCALL(DWORD?pOldCode,?DWORD?pNewCode,?DWORD?len)
...{
????BYTE?buf1[5];
????buf1[0]?=?0xe8;?//?inst?call
????*(DWORD?*)(buf1+1)?=?pNewCode-(pOldCode+5);
????WriteLocalBYTES((void*)pOldCode,?buf1,?len);
}
//?patch?stuff
//BOOL?__fastcall?D2DrawTextPatch(wchar_t?*str,?int?xpos,?int?ypos,?DWORD?col,?DWORD?unknown)
BOOL?__fastcall?D2WinDrawTextPatch(wchar_t?*str)
...{
????return?str?&&?(*str?==?L'T'?&&?wcscmp(str,?L"Trial?Version")?==?0);
}
/**//*
*?[esp+0x10]?=?arg?5?-?unknown
*?[esp+0x0c]?=?arg?4?-?col
*?[esp+0x08]?=?arg?3?-?ypos
*?[esp+0x04]?=?return?address?of?caller?of?D2WIN_DrawText,?e.g.,?from?D2JSP
*?[esp+0x00]?=?return?address?of?D2WIN_DrawText
*?edx?=?arg?2,?xpos
*?ecx?=?arg?1,?text?string
*/
void?__declspec(naked)?D2WinDrawTextPatch_ASM()
...{
????__asm
????...{
????????push?edx;?//?arg?2?-?xpos
????????push?ecx;?//?arg?1?-?str
????????call?D2WinDrawTextPatch;
????????pop?ecx;
????????pop?edx;
????????test?eax,?eax;
????????jz?draw_text;
????????pop?eax;?//?discard?return?address?of?caller?of?D2WIN_DrawText
????????ret?0x0c;?//?discard?three?arguments
draw_text:
????????pop?eax;?//?return?address?of?D2WIN_DrawText
????????//?original?code,?overwritten?by?detour?patch
????????push?ebx;
????????mov?ebx,?[esp+0x10];
????????jmp?eax;
????}
}
FARPROC?g_pDrawText;
BYTE?g_oldcode[5];
BOOL?InstallPatch()
...{
????HMODULE?hD2Win?=?GetModuleHandle("d2win.dll");
????if?(!hD2Win)?hD2Win?=?LoadLibrary("d2win.dll");
????if?(hD2Win)
????...{
????????g_pDrawText?=?GetProcAddress(hD2Win,?(LPCSTR)10064);
????????if?(g_pDrawText)
????????...{
????????????memcpy(g_oldcode,?g_pDrawText,?5);
????????????PatchCALL((DWORD)g_pDrawText,?(DWORD)D2WinDrawTextPatch_ASM,?5);
????????????return?TRUE;
????????}
????}
????return?FALSE;
}
void?RemovePatch()
...{
????if?(g_pDrawText)
????????WriteLocalBYTES(g_pDrawText,?g_oldcode,?5);
}
BOOL?APIENTRY?DllMain(HANDLE?hModule,?DWORD?dwReason,?LPVOID?lpReserved)
...{
????if?(DLL_PROCESS_ATTACH?==?dwReason)
????...{
????????DisableThreadLibraryCalls((HMODULE)hModule);
????????return?InstallPatch();
????}
????else?if(DLL_PROCESS_DETACH?==?dwReason)
????...{
????????RemovePatch();
????}
????return?TRUE;
}
//?faked?exported?function?used?to?bind?with?js32.dll
extern?"C"?__declspec(?dllexport?)?VOID?FakedInterface()
...{
}
總結(jié)
以上是生活随笔為你收集整理的Hacking Diablo II之外挂实战教程:去除D2JSP试用版显示的Trial Version信息的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: VB计算机中next是什么意思,VB程序
- 下一篇: RAPIDXML 中文手册,根据官方文档