javascript
Hacking Diablo II之外挂实战教程:去除D2JSP试用版显示的Trial Version信息
前幾天一個網友給我發消息請我幫他個忙。他的問題是,他正在使用的D2JSP是免費試用版,試用版在運行時會在游戲的所有游戲畫面中央顯示一行很大的“Trial Version”字樣(見下圖中的紅圈),很煩人,他想去掉這行字。我想正好用此做個教程解釋前面介紹過的hack工作原理,于是答應幫他看看。
我已經很久沒有開BOT了,最后一次使用D2JSP還是2003年1.09d時期的事。根據我以前的理解,D2JSP和其他hacks一樣,由兩部分組成,d2jsp.exe只是一個loader,負責把d2jsp.dll加載到游戲進程。而d2jsp.dll才是D2JSP的運行引擎-真正發揮作用的東西。因此“Trial Version”字符串肯定在d2jsp.dll中。最開始的想法是“Trial Version”可能會以一個常量字符串的形式保存在d2jsp.dll中,因此最容易想到的做法是用UltraEdit打開d2jsp.dll,尋找“Trial Version”,找到的話把它改為空字符串就OK了。然而打開后發現d2jsp.dll是加了殼的,這才想起自從D2 1.10以來D2JSP開始收費,采取了一些措施防止別人破解,加殼就是其中之一(當然還是被人破了)。在d2jsp.dll文件的前面幾行看到了aspack字串,看來加的是aspack殼。脫殼我很不在行,但是我知道有一些自動脫殼工具,針對aspack的脫殼工具也肯定有,不妨試試,于是到網上抓了個脫殼軟件-AspackDie。當然試驗的結果是失敗了。又想,不管怎樣,d2jsp.dll運行的時候它自己得脫殼吧,那我可以在它自己脫殼后在進程內存中找吧。于是加載D2JSP后用WinHex打開游戲進程,查找Trial Version,然而還是沒找到。看來可能用了即時脫殼技術,或者干脆Trial Version根本不是常量字符串,比如我知道如果在C/C++中這樣寫szVersion就不是常量字符串:
?
char?szVersion[]?=?{'T',?'r',?'i',?'a',?'l',?'?',?'V',?'e',?'r',?'s',?'i',?'o',?'n',?''};然后怎么辦,繼續研究脫殼?不,其實我早就有了更好、更通用的解決方法,但是需要編程。現在看來簡單的思路行不通,還是寫程序算了。換個角度來看,不管字符串是什么,總得調用某個函數來輸出顯示吧,看上圖中的字樣,顯然是調用了Diablo II的內部函數顯示的,因此如果能截獲這個函數過濾掉"Trial Version"字符串就搞定了。而且這種方法不用修改D2JSP程序,對所有D2JSP版本都起作用,因此更通用。問題是它調用了D2X的哪個函數呢?我想起我的d2hackmap中也用到了類似的字符串顯示的功能,它們用的有可能是同一個!于是把d2hackmap的源代碼翻出來看了看,發現了d2win.dll中導出序號為10064(Diablo II中所有dll導出的函數都只有序號沒有名字)的函數做此用途,其函數聲明為:
?
void?__fastcall?D2WIN_DrawText(wchar_t?*str,?int?xpos,?int?ypos,?DWORD?color,?DWORD?unknown);?然后用VC++ attach到游戲進程進行調試,想在D2WIN_DrawText設斷點監控,結果發現調試器剛attach上去D2JSP就會崩潰。看來它做了反調試處理,這也在意料之中。還是直接寫個DLL注入到游戲進程截獲D2WIN_DrawText來觀測得了,字符串可以用Win32 API OutputDebugString輸出然后用DbgView觀測:
?
void?__fastcall?MyD2WIN_DrawText(wchar_t?*str,?int?xpos,?int?ypos,?DWORD?color,?DWORD?unknown)...{
????if?(str)?OutputDebugStringW(str);
}
結果發現“Trial Version”果然是通過D2WIN_DrawText輸出的,見下圖。
現在只要改變MyD2WIN_DrawText()的實現,判斷輸出字符串如果為"Trial Version"則跳過真正的D2WIN_DrawText()代碼就搞定了,代碼見后面的詳細源代碼分析。改變后的顯示結果如下圖,畫面上方的d2jsp v1.2.0字樣還在,Trial Version字樣已經沒了:
至此,給D2JSP打補丁的程序d2jsppatch.dll已經做好了,然而還有一個問題,就是d2jsppatch.dll如何加載呢?用D2JSP開的BOT一般是無人職守自動運行的,因此d2jsppatch.dll最好能夠隨著d2jsp.dll自動加載,否則每次都需要人工加載那就太麻煩了。那怎么做到隨d2jsp.dll自動加載呢?一個辦法是把d2jsp.dll改名為d2jsp2.dll,d2jsppatch.dll改名為d2jsp.dll,這樣d2jsp.exe每次加載d2jsp.dll時其實加載的是d2jsppatch.dll。d2jsppatch.dll加載起來后,在它的啟動代碼里再手工加載d2jsp2.dll(即原來的d2jsp.dll),這樣就達到了自動加載d2jsppatch.dll的目的。這種方法的缺點是它改變了D2JSP原有模塊之間的關系,可能會導致一些兼容性問題,比如d2jsp.exe可能會校驗d2jsp.dll檢查它是否被人改過,或者某個依賴于d2jsp.dll工作的程序可能會用LoadLibrary("d2jsp.dll")/GetProcAddress()獲取d2jsp.dll的到處函數,這時就會失敗。
伴隨d2jsp.dll自動加載的另外一種做法是把d2jsppatch.dll靜態綁定到d2jsp.dll或者d2jsp.dll會用到的某個DLL。由于d2jsp.dll加了殼,DLL文件的IAT(Import Address Table,即導入表)已經被破壞,最好是從d2jsp.dll會用到的DLL下手。我們知道D2JSP支持jscript腳本程序,它肯定會加載jscript腳本引擎js32.dll,所以我們可以把d2jsppatch.dll靜態綁定到js32.dll上去。熟悉Windows編程且喜歡玩API hooking的朋友可能知道微軟的Detours Library,其中附帶了一個程序setdll.exe可以把一個DLL靜態綁定到其他EXE或者DLL。在命令行運行:
?
setdll.exe?-d:d2jsppatch.dll?js32.dll就把d2jsppatch.dll綁定到了js32.dll,如下圖。
以下是對d2jsppatch.dll源代碼的詳細分析,給感興趣的朋友參考。d2jsppatch.dll雖然是針對D2JSP的,但是它包含了一個hack的所有基本組成部分,而且功能很簡單,用它來了解hack的工作原理是很合適的。d2maphack、d2hackmap等hacks的工作原理和它完全一樣,不同的只是實現的功能。
1,d2jsppatch.dll的加載和卸載。DLL入口函數DllMain中一般用來做安裝(InstallPatch)、卸載(RemovePatch)旁路點(detour patch)。安裝、卸載工作可能導致程序崩潰(見以前的文章),放在DllMain中進行最合適,因為Windows保證在DllMain中的代碼執行時進程內的其他線程不會運行,見: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,旁路點的安裝和卸載。安裝旁路點一般就是往旁路點處插入一個跳轉(call或jmp,5字節)指令,讓程序運行到此處時跳轉到自己的patch代碼。要注意程序代碼段的頁面屬性一般都是只讀的,安裝前先要把頁面屬性改成可讀寫,裝完后再恢復原來的頁面屬性。卸載旁路點就是把patch點原先的代碼寫回去,因此在安裝時要做一下備份。
?
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,旁路點的patch代碼。patch代碼根據所要實現的功能的不同而不同,需要具體情況具體分析。一般來說邏輯稍微復雜些的patch代碼會由兩步分組成,一是用匯編寫的入口代碼,對寄存器和棧指針進行精細控制以免破壞原先的數據;二是用C/C++寫的邏輯實現代碼。匯編入口代碼調用C/C++的邏輯實現代碼。在此例中,D2WinDrawTextPatch_ASM為純(嵌入)匯編函數,第一行代碼運行前的寄存器內容和棧布局如注釋所示,D2WinDrawTextPatch有5個參數,由于是__fastcall,因此前兩個參數在ecx和edx中傳遞(VC的規定,C++ Builder可能有所不同),剩下三個從棧中傳入。由于C函數D2WinDrawTextPatch可能破壞ecx和edx里的內容,因此調用前必須保存它們的值,然后調用D2WinDrawTextPatch做真正的字符串過濾,注意D2WinDrawTextPatch也聲明為__fastcall,因此參數str為ecx。根據str是否為"Trial Version",D2WinDrawTextPatch返回TRUE或FALSE(返回值存放在eax中)。D2WinDrawTextPatch_ASM在D2WinDrawTextPatch返回后先恢復ecx和edx的原先內容,接著根據eax的值判斷是否應該執行真正的D2WinDrawText函數來顯示字符串。標號draw_text后面的代碼流程將跳到真正的D2WinDrawText去執行,由于旁路點把它的前兩個指令(共5個字節)改掉了(見下圖,上半部分為patch前的代碼,下半部分為patch后的代碼),在跳轉之前必須先執行這兩條指令。 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,給靜態綁定用的空導出函數。在一般的hack中這一部分是不需要的。setdll.exe在做靜態綁定時需要把d2jsppatch.dll的一個導出函數(隨便一個,有一個就行)插入js32.dll的IAT中。
?
extern?"C"?__declspec(?dllexport?)?VOID?FakedInterface()...{
}
?5,做為一個行為良好、成熟的hack,在安裝旁路點前還應該檢測自己用到的旁路點有沒有已經被其他hack改掉了,否則就會導致其他hack不能正常工作甚至整個游戲崩潰。如果別人已經改掉了,自己要么額外處理要么只好主動退出。那么如果要patch很多點(像maphack、hackmap有上百個),怎么檢查這些點是否被改了呢?一個快速而且有效的做法是對這些點的內容預先計算一個校驗和,運行時每次安裝旁路點前再校驗一遍然后和預先計算的值比較。d2jsppatch.dll的功能很簡單,沒有做這部分處理,具體做法請參考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()
...{
}
總結
以上是生活随笔為你收集整理的Hacking Diablo II之外挂实战教程:去除D2JSP试用版显示的Trial Version信息的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VB计算机中next是什么意思,VB程序
- 下一篇: RAPIDXML 中文手册,根据官方文档