魔獸爭霸三 單機遊戲修改器 系列三
繼續接著上一篇
上一篇得到了Hero Point Ptr +0x78, +0x7C是坐標
6F4742F0 /$ 83EC 14 sub esp, 14 ; from -> Fill Point Buffer(2) ECX=HeroNode+1646F4742F3 |. 8B51 0C mov edx, dword ptr [ecx+C]6F4742F6 |. 8B49 08 mov ecx, dword ptr [ecx+8]6F4742F9 |. 56 push esi6F4742FA |. E8 31B7BCFF call 6F03FA306F4742FF |. 8BF0 mov esi, eax6F474301 |. 8D4424 14 lea eax, dword ptr [esp+14]6F474305 |. 50 push eax6F474306 |. 8BCE mov ecx, esi6F474308 |. E8 53310200 call 6F4974606F47430D |. 50 push eax6F47430E |. 8D4C24 0C lea ecx, dword ptr [esp+C] ; Buffer6F474312 |. 51 push ecx6F474313 |. 8BCE mov ecx, esi ; Hero Point Ptr6F474315 |. E8 966DF1FF call 6F38B0B0 ; Fill Buffer6F47431A |. 8B4424 0C mov eax, dword ptr [esp+C] ; Get X6F47431E |. 8BD0 mov edx, eax6F474320 |. 81E2 0000807F and edx, 7F8000006F474326 |. F7DA neg edx6F474328 |. 1BD2 sbb edx, edx6F47432A |. 81E2 00008002 and edx, 28000006F474330 |. 03D0 add edx, eax6F474332 |. A1 6873AB6F mov eax, dword ptr [6FAB7368]6F474337 |. 83C0 70 add eax, 706F47433A |. 895424 04 mov dword ptr [esp+4], edx6F47433E |. 50 push eax6F47433F |. 8D5424 08 lea edx, dword ptr [esp+8]6F474343 |. 8D4C24 18 lea ecx, dword ptr [esp+18]6F474347 |. E8 B4AC2700 call 6F6EF000 ; Convert Y6F47434C |. 8B4424 08 mov eax, dword ptr [esp+8]6F474350 |. 8B15 6873AB6F mov edx, dword ptr [6FAB7368]6F474356 |. 8BC8 mov ecx, eax6F474358 |. 81E1 0000807F and ecx, 7F8000006F47435E |. F7D9 neg ecx6F474360 |. 1BC9 sbb ecx, ecx6F474362 |. 81E1 00008002 and ecx, 28000006F474368 |. 03C8 add ecx, eax6F47436A |. 83C2 6C add edx, 6C6F47436D |. 894C24 08 mov dword ptr [esp+8], ecx6F474371 |. 52 push edx6F474372 |. 8D5424 0C lea edx, dword ptr [esp+C]6F474376 |. 8D4C24 14 lea ecx, dword ptr [esp+14]6F47437A |. E8 81AC2700 call 6F6EF000 ; Convert X6F47437F |. 8B35 70E4AA6F mov esi, dword ptr [6FAAE470]6F474385 |. 8B4424 1C mov eax, dword ptr [esp+1C]6F474389 |. 8B4C24 10 mov ecx, dword ptr [esp+10]6F47438D |. 8B5424 14 mov edx, dword ptr [esp+14]6F474391 |. 8908 mov dword ptr [eax], ecx ; Write Convert X6F474393 |. 8950 04 mov dword ptr [eax+4], edx ; Write Convert Y6F474396 |. 8970 08 mov dword ptr [eax+8], esi6F474399 |. 5E pop esi6F47439A |. 83C4 14 add esp, 146F47439D . C2 0400 retn 4
然後我們用IDA看下0x6F6EF000的代碼看下如何轉換的。
unsigned int *__fastcall sub_6F6EF000(unsigned int *Buffer, signed int *a2, unsigned int *a3){ signed int v3; // edi unsigned int v4; // ebx unsigned int v5; // ebp int v6; // edx int v7; // ecx signed int v8; // eax unsigned int *result; // eax int v10; // esi unsigned int v11; // edi int v12; // eax int v13; // edx int v14; // esi unsigned int *v15; // [esp+10h] [ebp-4h] v3 = *a2; v4 = *a2 & 0x7F800000; v15 = Buffer; if ( !v4 ) goto LABEL_6; v5 = *a3 & 0x7F800000; if ( !v5 ) goto LABEL_10; v6 = ((v3 >> 31) ^ 2 * (v3 & 0x7FFFFF | 0x800000)) - (v3 >> 31); v7 = v5 - v4; v8 = ((*a3 >> 31) ^ 2 * (*a3 & 0x7FFFFF | 0x800000)) - (*a3 >> 31); if ( (v5 - v4) > 0 ) { if ( v7 >= 0xB800000 ) { Buffer = v15;LABEL_6: *Buffer = *a3; return Buffer; } v4 = *a3 & 0x7F800000; v6 >>= v7 >> 23; goto LABEL_12; } if ( v7 <= 0xF4800000 ) { Buffer = v15;LABEL_10: *Buffer = v3; return Buffer; } v8 >>= (v4 - v5) >> 23;LABEL_12: v10 = v8 + v6; if ( v8 + v6 ) { v11 = v10 & 0x80000000; if ( v10 < 0 ) v10 = -v10; v12 = sub_6F6EEC00(v10); v13 = 8 - v12; if ( 8 - v12 < 0 ) v14 = v10 << (v12 - 8); else v14 = v10 >> (8 - v12); result = v15; *v15 = v11 | v14 & 0x7FFFFF | (v4 + ((v13 - 1) << 23)); } else { result = v15; *v15 = 0; } return result;}
老實說, 挺麻煩的。 如果要從轉換過的X逆推回"真實X", 我也不太想解這個演算法。
那我們換一種思路好了。 我們從"真實坐標X"下寫入斷點。 然後可能會有一個坐標轉換的函數供我們調用也說不定呢? 是吧。
走動一下, 就可以斷下來了。
函數大概是這樣的。 我們從函數頭部下一個條件斷點。
要注意的是, 此處的ECX應該是那個坐標指針, 而非英雄的對象指針。 就是那個+0x78=坐標那個指針。
我們現在的坐標是
Point(80.47450,149.3223)
英雄走動一下就會斷下來了。慢慢單步往下走你會發現
返回一個0.2177261寫入到局部變數緩衝區裡面。
這個是-0.2119922
這裡寫入的是149.5400. 舊的坐標是149.3223。 用計算器算一下就知道
所以這些函數是計算出下一步要移動的距離。 計算出下一步的坐標。 寫入該坐標到 Point Ptr +0x78, +0x7C.
用IDA看一下0x6F6EEE20 這個函數你就會發現……
unsigned int *__fastcall sub_6F6EEE20(unsigned int *a1, int *a2, _DWORD *a3){ int v3; // eax int v4; // esi int v5; // edx unsigned int v6; // esi int v7; // edi int v8; // ebx int v9; // eax unsigned int v10; // edx unsigned int *result; // eax v3 = *a2; v4 = *a2 ^ *a3; v5 = *a2 & 0x7FFFFF; v6 = v4 & 0x80000000; v7 = v3 & 0x7F800000; v8 = *a3 & 0x7F800000; v9 = *a3 & 0x7FFFFF; if ( v5 && v9 ) { v10 = ((v5 | 0xFF800000) << 8) * ((v9 | 0xFF800000) << 8) >> 32; *a1 = ~((v8 + v7 - 0x40000000) >> 0x1F) & (v6 | (v8 + v7 - 0x3F800000 + (v10 >> 0x1F << 23)) | (v10 >> ((v10 & 0x80000000) != 0) >> 7) & 0x7FFFFF); result = a1; } else if ( v7 && v8 ) { *a1 = ~((v8 + v7 - 0x40000000) >> 0x1F) & (v6 | v5 | v9 | (v8 + v7 - 0x3F800000)); result = a1; } else { *a1 = 0; result = a1; } return result;}
這個函數也很坑爹。 但是大概可以看出。 a1(Buffer) 都是依賴a2(EDX) 和 a3(PUSH EDI)的值來進行各種計算得出的。
a2=
a3=
但是都沒我們想要的坐標轉換計算。 所以在這裡繼續逆向也不會有太大的結果, 最多你就能看懂他是怎麼計算坐標距離的而已。 那我們就把目光放到上幾層函數調用。
6F4A73B0 /$ 83EC 08 sub esp, 86F4A73B3 |. A1 70E4AA6F mov eax, dword ptr [6FAAE470]6F4A73B8 |. 56 push esi6F4A73B9 |. 894424 04 mov dword ptr [esp+4], eax6F4A73BD |. 894424 08 mov dword ptr [esp+8], eax6F4A73C1 |. 8D4424 04 lea eax, dword ptr [esp+4]6F4A73C5 |. 50 push eax6F4A73C6 |. 8BF1 mov esi, ecx6F4A73C8 |. E8 03FCFFFF call 6F4A6FD0 ; 1 -> to Write Point6F4A73CD |. 8B4C24 14 mov ecx, dword ptr [esp+14]6F4A73D1 |. 8B5424 10 mov edx, dword ptr [esp+10]6F4A73D5 |. 51 push ecx6F4A73D6 |. 52 push edx6F4A73D7 |. 8BCE mov ecx, esi6F4A73D9 |. E8 F2FCFFFF call 6F4A70D06F4A73DE |. 5E pop esi6F4A73DF |. 83C4 08 add esp, 86F4A73E2 . C2 0800 retn 8
6F493A40 /$ 83EC 0C sub esp, 0C6F493A43 |. 53 push ebx6F493A44 |. 8B5C24 14 mov ebx, dword ptr [esp+14]6F493A48 |. 55 push ebp6F493A49 |. 56 push esi6F493A4A |. 8BF1 mov esi, ecx6F493A4C |. 57 push edi6F493A4D |. 8DBE 80000000 lea edi, dword ptr [esi+80]6F493A53 |. 8D47 04 lea eax, dword ptr [edi+4]6F493A56 |. 50 push eax6F493A57 |. 8D53 04 lea edx, dword ptr [ebx+4]6F493A5A |. 8D4C24 24 lea ecx, dword ptr [esp+24]6F493A5E |. E8 8DB42500 call 6F6EEEF06F493A63 |. 57 push edi6F493A64 |. 8BD3 mov edx, ebx6F493A66 |. 8D4C24 14 lea ecx, dword ptr [esp+14]6F493A6A |. 8BE8 mov ebp, eax6F493A6C |. E8 7FB42500 call 6F6EEEF06F493A71 |. 8B08 mov ecx, dword ptr [eax]6F493A73 |. 8B4424 24 mov eax, dword ptr [esp+24]6F493A77 |. 894C24 14 mov dword ptr [esp+14], ecx6F493A7B |. 8B55 00 mov edx, dword ptr [ebp]6F493A7E |. 50 push eax6F493A7F |. 8D4C24 18 lea ecx, dword ptr [esp+18]6F493A83 |. 51 push ecx6F493A84 |. 8BCE mov ecx, esi6F493A86 |. 895424 20 mov dword ptr [esp+20], edx6F493A8A |. E8 21390100 call 6F4A73B0 ; 2 -> to Write Point6F493A8F |. 5F pop edi ; 23E027A46F493A90 |. 5E pop esi6F493A91 |. 5D pop ebp6F493A92 |. 5B pop ebx6F493A93 |. 83C4 0C add esp, 0C6F493A96 . C2 0800 retn 8
繼續往上的代碼我就不貼了。 總而言之, 逆向過程非常坎坷。
我就是想找滑鼠點擊時候的地圖坐標 轉換成 英雄人物坐標。
大概過程應該是 滑鼠點擊地圖的時候 會有一個地圖坐標。 然後轉換成 人物坐標。 獲取當前人物起點和終點, 計算下一步往哪邊走…… 但是我翻了幾下發現各種計算非常麻煩。 代碼就不貼了, 雖然找到了關鍵點, 但是遊戲轉換機制非常麻煩!!! 超級麻煩!!! 我就不貼這個過程了, 因為會很長很長。 就連我自己雖然逆向完他的坐標轉換機制, 我自己都不用。
所以我嘗試換了一種思路。 就從"閃現"技能下手。下一個斷點在函數頭部。
斷下來後返回上一層調用
6F4A7380 /$ 8B4424 04 mov eax, dword ptr [esp+4]6F4A7384 |. 56 push esi6F4A7385 |. 50 push eax6F4A7386 |. 8BF1 mov esi, ecx6F4A7388 >|. E8 43FCFFFF call 6F4A6FD0 ; Flash1 -> Write Point6F4A738D |. 837C24 0C 00 cmp dword ptr [esp+C], 06F4A7392 |. 74 0D je short 6F4A73A16F4A7394 |. 8B16 mov edx, dword ptr [esi]6F4A7396 |. 8B52 54 mov edx, dword ptr [edx+54]6F4A7399 |. 8D46 78 lea eax, dword ptr [esi+78]6F4A739C |. 50 push eax6F4A739D |. 8BCE mov ecx, esi6F4A739F |. FFD2 call edx6F4A73A1 |> 5E pop esi6F4A73A2 . C2 0800 retn 8
然後一直返回。 直到……
6F2AC2D5 |. E8 76481300 call 6F3E0B506F2AC2DA |> 8B4424 10 mov eax, dword ptr [esp+10]6F2AC2DE |. 8B4C24 14 mov ecx, dword ptr [esp+14]6F2AC2E2 |. 8B15 70E4AA6F mov edx, dword ptr [6FAAE470]6F2AC2E8 |. 894424 18 mov dword ptr [esp+18], eax6F2AC2EC |. 33C0 xor eax, eax6F2AC2EE |. 394424 44 cmp dword ptr [esp+44], eax6F2AC2F2 |. 894C24 1C mov dword ptr [esp+1C], ecx6F2AC2F6 |. 0F94C0 sete al6F2AC2F9 |. 895424 20 mov dword ptr [esp+20], edx6F2AC2FD |. 8B16 mov edx, dword ptr [esi]6F2AC2FF |. 8D4C24 18 lea ecx, dword ptr [esp+18]6F2AC303 |. 50 push eax ; Alway is 16F2AC304 |. 8B82 B8000000 mov eax, dword ptr [edx+B8]6F2AC30A |. 51 push ecx ; Point Buffer6F2AC30B |. 8BCE mov ecx, esi ; ECX = Hero Object Addr6F2AC30D |. FFD0 call eax ; -> EAX=ECX+1646F2AC30F |. 8BC8 mov ecx, eax6F2AC311 |. E8 3A7F1C00 call 6F474250 ; Flash CALL6F2AC316 |. 8B4C24 10 mov ecx, dword ptr [esp+10]6F2AC31A |. 8B5424 14 mov edx, dword ptr [esp+14]6F2AC31E |. 5F pop edi6F2AC31F |. 890B mov dword ptr [ebx], ecx6F2AC321 |. 5E pop esi6F2AC322 |. 8955 00 mov dword ptr [ebp], edx6F2AC325 |. 5D pop ebp6F2AC326 |. 5B pop ebx6F2AC327 |. 83C4 14 add esp, 146F2AC32A . C2 2C00 retn 2C
這個函數是比較不錯的。
Buffer = {X,Y,Z}PUSH 1PUSH BufferMOV ECX, Hero Object AddrCALL [ECX+0xB8] // return ECX+0x164MOV ECX, Hero Object addrCALL 6F474250
關鍵的閃現函數找到了。 因為這個函數, 需要坐標, 也需要英雄對象指針。 所以是完美函數原型。至於裡面怎麼把坐標轉換成真實坐標我們就不care了。 我們直接調用函數即可。
然後接下來就是找Hook的地方了。 我們在寫入"真實坐標"那裡下寫入斷點後。 一直返回到....
6F205C5A |. E8 71BE2600 call 6F471AD06F205C5F |. 56 push esi6F205C60 |. 8BCF mov ecx, edi6F205C62 |. E8 29BF2600 call 6F471B906F205C67 |. 8BCF mov ecx, edi6F205C69 |. E8 A2D80700 call 6F283510 ; 6-> Write Point6F205C6E |. F746 20 00080>test dword ptr [esi+20], 8006F205C75 |. 74 0E je short 6F205C856F205C77 |. 8166 20 FFF7F>and dword ptr [esi+20], FFFFF7FF6F205C7E |. 8BCF mov ecx, edi6F205C80 |. E8 DBCE0700 call 6F282B606F205C85 |> 8366 20 FB and dword ptr [esi+20], FFFFFFFB6F205C89 |. 5F pop edi6F205C8A |. 5E pop esi6F205C8B . C3 retn
然後看函數頭部那裡。
6F205B80 /$ 56 push esi6F205B81 |. 8BF1 mov esi, ecx6F205B83 |. F646 20 04 test byte ptr [esi+20], 46F205B87 |. 57 push edi6F205B88 |. 74 05 je short 6F205B8F6F205B8A |. E8 81D3FFFF call 6F202F106F205B8F |> 8B7E 30 mov edi, dword ptr [esi+30] ; Hook ..............[ESI+30] = Hero Object Ptr6F205B92 |. 85FF test edi, edi6F205B94 |. 75 09 jnz short 6F205B9F ; ESP+1C8= Target Map X, ESP+1CC=Target Map Y6F205B96 |. 8BCE mov ecx, esi6F205B98 |. E8 F3CC2600 call 6F4728906F205B9D |. 8BF8 mov edi, eax6F205B9F |> 6A 02 push 26F205BA1 |. 8BCF mov ecx, edi6F205BA3 |. E8 B8990600 call 6F26F5606F205BA8 |. 8BCE mov ecx, esi
這裡就是Hook的地方。 只要Hook這裡。對比 [ESI+0x30] = Hero Object Addr. 即可調用"瞬移"。
順便提一句, 這個ESI是技能指針。 譬如說我是用
這個ESI, 就是技能對象。 也就是技能對象+0x30就是 施法者或者 技能受益者之類的吧。
這個技能在下一篇會說到技能冷卻,減少耗藍。
然後我把我自己的"自定義瞬移"設計成, 按下F2, 然後滑鼠右擊地圖。 即可"全圖閃現"。
大概代碼實現如下
首先Hook Win32API的 PeekMessageA. 得到按鍵消息。
MyTools::CLdrHeader LdrHeader;LdrHeader.InlineHook(::GetProcAddress(::GetModuleHandleW(L"user32.dll"), "PeekMessageA"), PeekMessage_, reinterpret_cast<void **>(&_OldPeekMessagePtr));BOOL g_PushF2 = FALSE;using PeekMessageAPtr = BOOL(WINAPI*)(_Out_ LPMSG lpMsg, _In_opt_ HWND hWnd, _In_ UINT wMsgFilterMin, _In_ UINT wMsgFilterMax, _In_ UINT wRemoveMsg);PeekMessageAPtr _OldPeekMessagePtr = nullptr;BOOL WINAPI PeekMessage_(_Out_ LPMSG lpMsg, _In_opt_ HWND hWnd, _In_ UINT wMsgFilterMin, _In_ UINT wMsgFilterMax, _In_ UINT wRemoveMsg){ BOOL bRetCode = _OldPeekMessagePtr(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); if (lpMsg->wParam == VK_F2) { g_PushF2 = lpMsg->message == WM_KEYDOWN ? TRUE : FALSE; } return bRetCode;}
然後用機器碼去搜索整塊內存…… 因為這個是在Game.dll裡面。 因為載入地址隨機化的問題。 不能用固定的內存地址。
static MyTools::MYHOOK_CONTENT HookContent;HookContent.uNopCount = 0x0;HookContent.dwFunAddr = reinterpret_cast<DWORD>(HookPoint);dwHookPointAddr = HookContent.dwHookAddr = MyTools::CLSearchBase::FindAddr("7405E881D3FFFF", 0x46D5B88 - 0x46D5B8F, 0, L"Game.dll");if (HookContent.dwHookAddr == 0){ LOG_C_E(L"UnExist HookAddr"); return;}
然後是Hook的代碼
BOOL PlayerFlash(_In_ DWORD dwHookPointHeroObjectAddr, _In_ float X, _In_ float Y, _In_ float Z){ if (dwHookPointHeroObjectAddr != dwHeroObjectAddr || !g_PushF2 || (DWORD)X == 0 || (DWORD)Y == 0) { return FALSE; } static DWORD dwCALL = MyTools::CLSearchBase::FindCALL("FFD08BC8E83A7F1C00", 0x477C30D - 0x477C311, (DWORD)::GetModuleHandleW(L"Game.dll"), 1, 0, L"Game.dll"); LOG_C_D(L"Flash [%.2f,%.2f,%.2f]", X, Y, Z); struct FlashBufferContent { float fX; float fY; float fZ; }; static CHAR Buffer[1024]; memset(Buffer, 0, sizeof(Buffer)); ((FlashBufferContent*)Buffer)->fX = static_cast<float>(X); ((FlashBufferContent*)Buffer)->fY = static_cast<float>(Y); ((FlashBufferContent*)Buffer)->fZ = static_cast<float>(Z); __asm { PUSHAD; PUSH 1; LEA EAX, Buffer; PUSH EAX; MOV ECX, dwHeroObjectAddr; ADd ECX, 0x164; MOV EAX, dwCALL; CALL EAX; POPAD; } return TRUE;}static DWORD dwHookPointESI = 0;static DWORD dwHookPointAddr = 0;static DWORD dwHookPointX = 0;static DWORD dwHookPointY = 0;static DWORD dwHookPointZ = 0;__declspec(naked) void HookPoint(){ __asm { MOV dwHookPointESI, ESI; LEA EAX, DWORD PTR DS : [ESP + 0x1C8]; MOV dwHookPointX, EAX; LEA EAX, DWORD PTR DS : [ESP + 0x1CC]; MOV dwHookPointY, EAX; LEA EAX, DWORD PTR DS : [ESP + 0x1D0]; MOV dwHookPointZ, EAX; } __asm PUSHAD; PlayerFlash(ReadDWORD(dwHookPointESI + 0x30), MyTools::CCharacter::ReadFloat(dwHookPointX), MyTools::CCharacter::ReadFloat(dwHookPointY), MyTools::CCharacter::ReadFloat(dwHookPointZ)); __asm { POPAD; MOV EDI, dwHookPointAddr; ADD EDI, 0x5; PUSH EDI; MOV EDI, DWORD PTR DS : [ESI + 0x30]; TEST EDI, EDI; RETN; }}
效果就是
代碼在
VideoCardGuy/War3Cheat還沒完全整理完……
推薦閱讀:
※偽·從零開始學Python - 1.3 Python Shell的基本使用
※2進位的一個主要優點是什麼?
※一起寫一個解釋器(1)---一些廢話
※Matplotlib中控制子圖的間距
※Linux 下有哪些特別方便的自動化工具?