一年一度的重裝系統“大工程”又浩浩盪盪的開工了。
整理去年一年的工具及資料,今天起陸續放一點以前給客戶做的游戲輔助的手記。(一年多了,客戶應該不會介意吧)
廢話不多說,今天是第一篇。
《龍翔密傳》分析手記
選怪
突破口:
CE搜索變化值,不停選怪取消怪,定位到如下代碼:
00413b5e - 89 be b0 00 00 00 - mov [esi+000000b0],edi
稍微回溯一下
00435BDC /E9 4B090000 jmp 0043652C 00435BE1 |8B0D 08536300 mov ecx, dword ptr [635308] 00435BE7 |8B11 mov edx, dword ptr [ecx] 00435BE9 |8B46 0C mov eax, dword ptr [esi+C] 00435BEC |8B52 34 mov edx, dword ptr [edx+34] 00435BEF |6A 00 push 0 ; 固定參數 00435BF1 |50 push eax ; 怪物ID 00435BF2 |FFD2 call edx ; 選怪CALL edx = 0048ac00
該CALL就是選怪CALL。
該CALL 可以直接調用實現選怪 _asm { push 0 // 固定參數 push id // 怪物ID call 0x0048ac00 }
怪物列表
突破口:
根據選怪的反匯編代碼,跟進00435BF2這個CALL發現了另一個CALL:
00413AA9 |. 53 push ebx ; 怪物ID 00413AAA |. FFD2 call edx ; 根據怪物ID,取對象基址 再跟進此CALL發現另一個CALL: 00412CA5 |. 50 push eax ; 怪物ID指針 00412CA6 |. 8D4C24 14 lea ecx, dword ptr [esp+14] 00412CAA |. 51 push ecx ; 上層CALL的返回地址 00412CAB |. E8 30260000 call 004152E0 ; 獲得怪物列表 ////// 00412CAB |. E8 30260000 call 004152E0 ; 獲得怪物列表
這個CALL很關鍵了,是遍歷怪物列表的關鍵,以下是該CALL的分析:
004152E0 /$ 55 push ebp 004152E1 |. 8BEC mov ebp, esp 004152E3 |. 8B4F 18 mov ecx, dword ptr [edi+18] ; 超級像怪物列表(ecx = [01c398b8 + 18] = 01c45390) 004152E6 |. 8B41 04 mov eax, dword ptr [ecx+4] ; eax = [[[635210]+90+18]+4] = 20904618 004152E9 |. 83EC 10 sub esp, 10 004152EC |. 8078 15 00 cmp byte ptr [eax+15], 0 004152F0 |. 53 push ebx 004152F1 |. 56 push esi 004152F2 |. 8BF1 mov esi, ecx 004152F4 |. 75 1E jnz short 00415314 004152F6 |. 8B4D 0C mov ecx, dword ptr [ebp+C] ; 怪物ID 004152F9 |. 8B09 mov ecx, dword ptr [ecx] 004152FB |. EB 03 jmp short 00415300 004152FD | 8D49 00 lea ecx, dword ptr [ecx] 00415300 |> 3948 0C /cmp dword ptr [eax+C], ecx 00415303 |. 7D 05 |jge short 0041530A 00415305 |. 8B40 08 |mov eax, dword ptr [eax+8] 00415308 |. EB 04 |jmp short 0041530E 0041530A |> 8BF0 |mov esi, eax ; esi 將作為本CALL 的返回值 0041530C |. 8B00 |mov eax, dword ptr [eax] 0041530E |> 8078 15 00 |cmp byte ptr [eax+15], 0 00415312 |.^ 74 EC \je short 00415300
經過查看00415300 到00415312代碼所訪問的內存模型,判斷怪物列表為一顆二叉樹. 上面首先獲得eax,即怪物二叉樹的根節點. eax = [[[[635210]+90+18]+4] 然后從00415300 到00415312是重點,這里遍歷二叉樹找出對應ID的對象. 左樹是小([eax]),右樹是大([eax+8]).[eax+4]是父節點.[eax+c]是ID. [eax+15]有點特別,是一個bool類型,通過反匯編代碼可以看出,此變量為整個遍歷的結束條件. 所以,大膽推測[eax+15]標示了該節點的前一個節點是否是選中狀態.如果為選中狀態則為true.
通過調用此CALL的上層CALL : 00412CEF |> \8B47 10 mov eax, dword ptr [edi+10] ; 可以看出[eax+10]就是怪物對象地址 所以可以聲明一個數據結構如下:
typedef struct _NPC { NPC* LeftSmall; NPC* Parent; NPC* RightBig; int id; DATA* pData; }NPC;
根據反匯編代碼004152EC到00415312寫出大致的模擬算法如下:
NPC* GetAddressByID(int nID) { NPC* a = A.Head; NPC* temp = NULL; NPC* find = NULL; if(a->selected == false) { int id = nID; do { if(a->id < id)// 如果當前ID小於給定的ID,則往大的方向遍歷 { temp = a->RigthBig; } else { find = a; a = a->LeftSmall; } }while(a->selecte != false); } return find; }
下面是根據分析資料實現遍歷怪物列表的算法:(前序遞歸)
CArray<DATA*,DATA*&> NPCArray; void PreOrder(NPC* Tree) { if(Tree->id != 0) { NPCArray.Add(Tree->pData); PreOrder(Tree->LeftSmall); PreOrder(Tree->RightBig); } }
注意用此根節點遍歷出來包括了怪物對象,還包括其他一些無用的對象,需要過濾.
這里過濾的方式是物品的話,最大血量及當前血量都是0,可以以此作為判斷是否是物品的根據.肯定二叉樹葉子節點有一個變量是標明該對象的類型的,是背包還是怪物還是地上的物品等等.目前沒有分析了.暫時用最大血量跟當前血量做判斷.
打怪
突破口:
OD在GameClient模塊搜索"skill"查找到Send_Skill... 順藤摸瓜,回溯幾層CALL后,發現一個switch分支,當為7的分支的時候就是打怪
00423317 |. E8 14200000 call 00425330 0042331C |. 5F pop edi 0042331D |. 5E pop esi 0042331E |. 8BE5 mov esp, ebp 00423320 |. 5D pop ebp 00423321 |. C3 retn 00423322 |> 56 push esi ; esi = 上層ECX (DWORD [esi] 恆定為5e6e68); Case 7 of switch 0042327E 00423323 |. E8 D81C0000 call 00425000 ; **************真正的打怪CALL 距離不夠會自動靠近一步 00423328 |. 5F pop edi 00423329 |. 5E pop esi 0042332A |. 8BE5 mov esp, ebp 0042332C |. 5D pop ebp 0042332D |. C3 retn
直接CALL 00425000 就會對當前選定的怪進行攻擊.
esi = 上層ECX。
以下是上層代碼:
00418F0F |. 8B8B CC010000 mov ecx, dword ptr [ebx+1CC] ; ecx = [上層ecx + 1cc] 上層ECX = 1EB35128 00418F15 |. 85C9 test ecx, ecx 00418F17 |. 74 06 je short 00418F1F 00418F19 |. 8B01 mov eax, dword ptr [ecx] 00418F1B |. 8B10 mov edx, dword ptr [eax] 00418F1D |. FFD2 call edx ; edx = 00423210,內含真正的打怪CALL
該層的ECX又等於[該層上層ECX + 1cc],經過實測該層上層ECX恆定為1EB35128(此值非常眼熟,挺像怪物列表里的一個值,有待考證)
經過以上分析,打怪CALL的調用代碼如下:
_asm { mov eax,[1EB35128+1CC] push eax call 0x00425000 }
上面的ECX = 1EB35128,此值非恆定,經過跟蹤發現此為一個NEW出來的地址.
尋找過程:
發現DWORD [esi]恆定為56e6e8,直接搜索常數,發現有兩個引用的地方,一個是構造函數,一個是析構函數,用構造函數來突破.構造函數附近代碼如下:
0041FC91 |. E8 08771300 call <jmp.&MSVCR90.operator new> ; !!!!!!!!!!!!!這里NEW了一個對象,打怪CALL需要用到 0041FC96 |. 83C4 04 add esp, 4 0041FC99 8945 F0 mov dword ptr [ebp-10], eax 0041FC9C C645 FC 01 mov byte ptr [ebp-4], 1 0041FCA0 3BC7 cmp eax, edi 0041FCA2 |. 74 09 je short 0041FCAD 0041FCA4 |. 56 push esi 0041FCA5 |. 50 push eax 0041FCA6 |. E8 D5330000 call 00423080
OK,既然是NEW出來的,那就給它做個內存補丁:
在下面找一空白處(關鍵是見縫插針,不能覆蓋原來的代碼)
填寫如下代碼:
0041FD31 A3 0AFD4100 mov dword ptr [41FD0A], eax 0041FD36 8945 F0 mov dword ptr [ebp-10], eax 0041FD39 E9 15010000 jmp 0041FE53 0041FE53 C645 FC 01 mov byte ptr [ebp-4], 1 0041FE57 ^ E9 44FEFFFF jmp 0041FCA0
修改完畢后,原來的代碼變成了這樣:
0041FC91 |. E8 08771300 call <jmp.&MSVCR90.operator new> ; !!!!!!!!!!!!!這里NEW了一個對象,打怪CALL需要用到 0041FC96 |. 83C4 04 add esp, 4 0041FC99 E9 93000000 jmp 0041FD31 0041FC9E 90 nop 0041FC9F 90 nop 0041FCA0 |. 3BC7 cmp eax, edi 0041FCA2 |. 74 09 je short 0041FCAD 0041FCA4 |. 56 push esi 0041FCA5 |. 50 push eax 0041FCA6 |. E8 D5330000 call 00423080 ; 此CALL里eax填充56E6E8 0041FCAB |. EB 02 jmp short 0041FCAF
注意:因為新增的代碼是在.text區段,而該區段是不能寫的,所以在測試的時候用PE Explorer修改該區段為可寫.否則
004236D9 A3 D8364200 mov dword ptr [4236D8], eax這句代碼將出錯.用代碼實現的時候注意修改內存屬性。
(更新)在第一個測試版本中發現該CALL因為會判斷怪的距離,不夠的話會自動靠近.經過實測,當此CALL尚未執行完畢(即正在自動靠近怪物的時候),又調用此CALL的時候會讓游戲崩潰,由此判定此CALL不是線程安全的函數,需要調用者確保線程安全.所以這個CALL太雞肋!繼續找個完美的CALL。。。
004235B5 |> \8B42 08 mov eax, dword ptr [edx+8] ; Case 3 of switch 00423555 004235B8 |. 8B4A 0C mov ecx, dword ptr [edx+C] 004235BB |. 8B52 10 mov edx, dword ptr [edx+10] 004235BE |. 52 push edx ; edx = 1eb怪物ID 004235BF |. 51 push ecx ; 1 004235C0 |. 50 push eax ; 0 004235C1 |. E8 9A120000 call 00424860 ;
之前的CALL實際上是消息循環自動調用的,檢查某個標記存在則自動調用之前找到的打怪CALL.而這里這個CALL可以理解為正是設定標志的.設定后可以讓消息循環自動調用.
注意!!!!!!!!!!!調用此CALL,需要內部的ESI賦值
以下是上個CALL的ESI賦值
0048B85A |. /74 54 je short 0048B8B0 0048B85C |. |8B0D 10526300 mov ecx, dword ptr [635210] 0048B862 |. |8B51 50 mov edx, dword ptr [ecx+50] 0048B865 |. |8B8A CC010000 mov ecx, dword ptr [edx+1CC] ; 為打怪CALL,提供的ESI 0048B86B |. |C74424 50 020>mov dword ptr [esp+50], 2 0048B873 |. |BA 03000000 mov edx, 3 0048B878 |. |66:895424 08 mov word ptr [esp+8], dx 0048B87D |. |8B10 mov edx, dword ptr [eax]
打怪代碼實現:
_asm { mov ecx,[635210] mov edx,[ecx+50] mov esi,[edx+1cc] push 0x6d//怪物ID push 1 push 0 call 00424860 }
怪物對象:
+14 ID范圍的最大值
+48 怪物ID
+11C X坐標
+120 Y坐標
名字:[[+158]+8]+8+4
突破口:CE搜名字,找到對應的怪后,下內存訪問斷點
血量:
當前:[[[+158]+8]+80]
最大:[[[+158]+8]+80+8]
004191A2 |. 8B86 58010000 mov eax, dword ptr [esi+158] ; 讀名字的關鍵開始 004191A8 |. 8B40 08 mov eax, dword ptr [eax+8] 004191AB |. 83C0 08 add eax, 8 004191AE |. 8378 18 10 cmp dword ptr [eax+18], 10 004191B2 |. 72 05 jb short 004191B9 004191B4 |. 8B40 04 mov eax, dword ptr [eax+4] 004191B7 |. EB 03 jmp short 004191BC 004191B9 |> 83C0 04 add eax, 4 004191BC |> 8D50 01 lea edx, dword ptr [eax+1] 004191BF |. 90 nop 004191C0 |> 8A08 /mov cl, byte ptr [eax] 004191C2 |. 40 |inc eax 004191C3 |. 84C9 |test cl, cl 004191C5 |.^ 75 F9 \jnz short 004191C0
當前選中對象的分析:
00436848 |. 8B4424 10 mov eax, dword ptr [esp+10] 0043684C |. 8BF0 mov esi, eax 0043684E |. 75 0F jnz short 0043685F 00436850 |. C780 E8000000>mov dword ptr [eax+E8], 3 0043685A |. E9 8E010000 jmp 004369ED 0043685F |> C780 E8000000>mov dword ptr [eax+E8], 2 00436869 |. D903 fld dword ptr [ebx] 0043686B |. D998 A0000000 fstp dword ptr [eax+A0] ; 當前選中的物品ID 00436871 |. D943 08 fld dword ptr [ebx+8] 00436874 |. C780 A8000000>mov dword ptr [eax+A8], 1 0043687E |. D998 A4000000 fstp dword ptr [eax+A4] ; 當前選中的怪ID eax = 01c36c80 恆定 00436884 |. E9 64010000 jmp 004369ED 00436889 |> 8B17 mov edx, dword ptr [edi] ; Case 3 of switch 004367E7 0043688B |. 8B42 18 mov eax, dword ptr [edx+18] 0043688E |. 8BCF mov ecx, edi 00436890 |. FFD0 call eax
此段代碼會不停在浮點寄存器中壓棧出棧,eax = 01c36c80正是當前所選對象的基址.
eax = 01c36c80恆定,為確保以后游戲升級后能順利查找到該地址,下面是eax的特征碼
首先eax = [esp] + 10
查看函數開頭,實際是ebx的壓棧,繼續回溯ebx
則得到下列代碼
0051FF10 |. 395D C0 cmp dword ptr [ebp-40], ebx 0051FF13 |. 0F85 87020000 jnz 005201A0 0051FF19 |. 8B0D 34526300 mov ecx, dword ptr [635234] 0051FF1F |. 8B1D 1C526300 mov ebx, dword ptr [63521C] ; 當前所選對象基地址 0051FF25 |. 8B01 mov eax, dword ptr [ecx] 0051FF27 |. 8B33 mov esi, dword ptr [ebx] ; GameClie.005E7EFC 0051FF29 |. 8B50 40 mov edx, dword ptr [eax+40] 0051FF2C |. 83C6 4C add esi, 4C 0051FF2F |. FFD2 call edx
如上所示:[63521C]即為eax的基地址
+A0 當前選中物品的ID
+A4 當前選中怪的ID
物品列表
突破口
CE監控 改寫 地址 01c36c80 + A0的代碼:
0043686b - d9 98 a0 00 00 00 - fstp dword ptr [eax+000000a0]// 排除掉了 0042fbf5 - 89 47 08 - mov [edi+08],eax 0041ff3e - c7 40 08 00 00 00 00 - mov [eax+08],00000000// 排除掉了
0042fbf5 - 89 47 08 - mov [edi+08],eax是關鍵
一路回溯,發現遍歷物品列表的函數 跟 遍歷怪物的函數是一樣的.
注意用此根節點遍歷出來包括了怪物對象,還包括其他一些無用的對象,需要過濾.
這里過濾的方式是物品的話,最大血量及當前血量都是0,可以以此作為判斷是否是物品的根據.肯定二叉樹葉子節點有一個變量是標明該對象的類型的,是背包還是怪物還是地上的物品等等.目前沒有分析了.
技能
004235EC |. 53 push ebx ; -1 004235ED |. 8B5C24 18 mov ebx, dword ptr [esp+18] 004235F1 |. 53 push ebx ; -1 004235F2 |. 8B5C24 18 mov ebx, dword ptr [esp+18] 004235F6 |. 53 push ebx ; -1 004235F7 |. 57 push edi ; 怪物ID 004235F8 |. 52 push edx ; -1 004235F9 |. 51 push ecx ; 2 004235FA |. 50 push eax ; 0x16 技能代碼 004235FB |. 56 push esi ; esi = 01c28BE8 恆定 004235FC |. E8 0F0B0000 call 00424110 ; 這個就是技能CALL