最近思來想去,眼看着自己就要進某廠遊戲安全團隊實習了,也不能整天的無所事事,所以就尋思着先找點最簡單的遊戲用來練練手。想到之前逆向過一些小遊戲,就把之前分析的掃雷整理了一下啊,寫了個外掛,發了上來。
最近實在是比較忙,快期中考試了,什麼都不會,所以忙着預習去了,本文只寫了關於掃雷逆向的部分,相關的外掛編寫已經寫差不多了,等最近有時間整理出來o(╯□╰)o
工具
分析對象:winmine/掃雷(windows xp版本)
逆向工具:ollydbg,IDA,peid,ResHacker
操作平臺:windows7 旗艦版
分析過程
main函數定位
peid加載winmine發現爲vc編寫的,沒有加過殼,則可以確定加載程序的流程爲:獲取當前版本號,堆初始化,獲取命令行參數,獲取環境變量,分離出命令行參數,全局數據和浮點寄存器初始化所以在main(這裏應該是winmain了)調用前,調用流程爲:
GetVersion()—–>_heap_init()—–>GetCommandLine()—–>_crtGetEnvironmentStrings()—–>_setargv()—–>_setenvp()—–>_cinit()
所以很容易找到winmain的調用地址,跟進之後發現
發現這裏便是在做一些初始化
很快找到:
:010022D5 mov ecx, yBottom
.text:010022DB mov edx, xRight
.text:010022E1 push edi ; lpParam
.text:010022E2 push hInstance ; hInstance
.text:010022E8 add ecx, eax
.text:010022EA push edi ; hMenu
.text:010022EB push edi ; hWndParent
.text:010022EC push ecx ; nHeight
.text:010022ED mov ecx, dword_1005A90
.text:010022F3 add edx, ecx
.text:010022F5 push edx ; nWidth
.text:010022F6 mov edx, Y
.text:010022FC sub edx, eax
.text:010022FE mov eax, X
.text:01002303 push edx ; Y
.text:01002304 sub eax, ecx
.text:01002306 push eax ; X
.text:01002307 push 0CA0000h ; dwStyle
.text:0100230C push esi ; lpWindowName
.text:0100230D push esi ; lpClassName
.text:0100230E push edi ; dwExStyle
.text:0100230F call ds:CreateWindowExW
.text:01002315 cmp eax, edi
.text:01002317 mov hWnd, eax
.text:0100231C jnz short loc_1002325
.text:0100231E push 3E8h
.text:01002323 jmp short loc_1002336
.text:01002325 ; ---------------------------------------------------------------------------
.text:01002325
.text:01002325 loc_1002325: ; CODE XREF: WinMain+12Cj
.text:01002325 push ebx
.text:01002326 call sub_1001950 ;位置
.text:0100232B call sub_1002B14 ; 裝載資源
.text:01002330 test eax, eax
.text:01002332 jnz short loc_1002342
.text:01002334 push 5
.text:01002336
.text:01002336 loc_1002336: ; CODE XREF: WinMain+133j
.text:01002336 call sub_1003950
.text:0100233B
.text:0100233B loc_100233B: ; CODE XREF: WinMain+ABj
.text:0100233B xor eax, eax
.text:0100233D jmp loc_10023C6
.text:01002342 ; ---------------------------------------------------------------------------
.text:01002342
.text:01002342 loc_1002342: ; CODE XREF: WinMain+142j
.text:01002342 push dword_10056C4
.text:01002348 call sub_1003CE5
.text:0100234D call SetMine ; 佈置雷區
.text:01002352 push ebx ; nCmdShow
.text:01002353 push hWnd ; hWnd
.text:01002359 call ds:ShowWindow
.text:0100235F push hWnd ; hWnd
.text:01002365 call ds:UpdateWindow
.text:0100236B mov esi, ds:GetMessageW
.text:01002371 mov dword_1005B38, edi
.text:01002377 jmp short loc_10023A4
分析可疑函數
在createwindow,分別調用了4個函數,然後就showwindow了,那麼這4個函數肯定完成了在掃雷前用戶的選擇,資源的加載,雷區的繪製.
跟進第一個函數sub_1001950,發現主要調用了GetMenuItemRect和MoveWindow函數將窗口待會創建到制定位置,這裏跟我們關心的算法沒有太大關係,所以不做分析。
跟進第二個函數sub_1002B14,發現主要在調用LoadResource進行加載資源
跟進第三個函數sub_1003CE5:
.text:01003CE5 sub_1003CE5 proc near ; CODE XREF: sub_1001BC9:loc_1001D00p
.text:01003CE5 ; WinMain+158p
.text:01003CE5
.text:01003CE5 arg_0 = dword ptr 4
.text:01003CE5
.text:01003CE5 mov eax, [esp+arg_0]
.text:01003CE9 mov dword_10056C4, eax
.text:01003CEE call sub_1001516 ; 菜單選項
.text:01003CF3 mov eax, dword_10056C4
.text:01003CF8 and al, 1
.text:01003CFA neg al
.text:01003CFC sbb eax, eax
.text:01003CFE not eax
.text:01003D00 and eax, hMenu
.text:01003D06 push eax ; hMenu
.text:01003D07 push hWnd ; hWnd
.text:01003D0D call ds:SetMenu
.text:01003D13 push 2
.text:01003D15 call sub_1001950
.text:01003D1A retn 4
.text:01003D1A sub_1003CE5 endp
發現在調用sub_1001516之後調用了SerMenu,之後再次調用了第一個函數sub_1001950,應該就是處理用戶選擇不同難度而對窗口進行的重新佈置吧
跟進sub_1001516
爲了之後分析方便,將sub_1001516改名爲MenuChoose,sub_1001950改名爲SetWindow
/*注:本代碼中CheckMenu爲自己分析後自己起的函數名*/
.text:01001516 MenuChoose proc near ; CODE XREF: sub_1001B49+24p
.text:01001516 ; sub_1003CE5+9p
.text:01001516 xor eax, eax
.text:01001518 cmp word ptr dword_10056A0, ax
.text:0100151F setz al
.text:01001522 push eax
.text:01001523 push 209h ; 521
.text:01001528 call CheckMenu
.text:0100152D xor eax, eax
.text:0100152F cmp word ptr dword_10056A0, 1
.text:01001537 setz al
.text:0100153A push eax
.text:0100153B push 20Ah ; 522
.text:01001540 call CheckMenu
.text:01001545 xor eax, eax
.text:01001547 cmp word ptr dword_10056A0, 2
.text:0100154F setz al
.text:01001552 push eax
.text:01001553 push 20Bh ; 523
.text:01001558 call CheckMenu
.text:0100155D xor eax, eax
.text:0100155F cmp word ptr dword_10056A0, 3
.text:01001567 setz al
.text:0100156A push eax
.text:0100156B push 20Ch ; 524
.text:01001570 call CheckMenu
.text:01001575 push dword_10056C8
.text:0100157B push 211h ; 529
.text:01001580 call CheckMenu
.text:01001585 push Data
.text:0100158B push 20Fh ; 527
.text:01001590 call CheckMenu
.text:01001595 push dword_10056B8
.text:0100159B push 20Eh
.text:010015A0 call CheckMenu
.text:010015A5 retn
.text:010015A5 MenuChoose endp
這裏差不多就應該是通過資源ID選擇了,用ResHacker分析一下得證
然後分析最後一個函數SetMine(原函數名爲sun_xxxxxx,通過分析已經修改函數名)
.text:010036A4
.text:010036A4 loc_10036A4: ; CODE XREF: SetMine+1Cj
.text:010036A4 ; SetMine+24j
.text:010036A4 push 6
.text:010036A6
.text:010036A6 loc_10036A6: ; CODE XREF: SetMine+28j
.text:010036A6 pop ebx
.text:010036A7 mov dword_1005334, eax
.text:010036AC mov dword_1005338, ecx
.text:010036B2 call PaintMine
.text:010036B7 mov eax, dword_10056A4
.text:010036BC mov dword_1005160, edi
.text:010036C2 mov dword_1005330, eax
.text:010036C7
.text:010036C7 loc_10036C7: ; CODE XREF: SetMine+74j
.text:010036C7 ; SetMine+89j
.text:010036C7 push dword_1005334
.text:010036CD call Rand
.text:010036D2 push dword_1005338
.text:010036D8 mov esi, eax
.text:010036DA inc esi
.text:010036DB call Rand
.text:010036E0 inc eax
.text:010036E1 mov ecx, eax
.text:010036E3 shl ecx, 5
.text:010036E6 test byte_1005340[ecx+esi], 80h
.text:010036EE jnz short loc_10036C7
繪製雷區函數分析
發現期中一個函數(PaintMine)便是在繪製雷區
大體F5把握一下大致的意思
int __cdecl PaintMine()
{
signed int v0; // eax@1
int v1; // ecx@3
int v2; // edx@3
int result; // eax@3
int v4; // esi@5
char *v5; // edx@6
v0 = 864;
do
{
--v0;
byte_1005340[v0] = 15;
}
while ( v0 );
v1 = dword_1005334;
v2 = dword_1005338;
result = dword_1005334 + 2;
if ( dword_1005334 != -2 )
{
do
{
--result;
byte_1005340[result] = 16;
*(&byte_1005360[32 * v2] + result) = 16;
}
while ( result );
}
v4 = v2 + 2;
if ( v2 != -2 )
{
v5 = &byte_1005340[32 * v4];
result = (int)((char *)&unk_1005341 + 32 * v4 + v1);
do
{
v5 -= 32;
result -= 32;
--v4;
*v5 = 16;
*(_BYTE *)result = 16;
}
while ( v4 );
}
return result;
}
大體意思就是先將最大可能的雷區內數據全部設置爲0xF,然後在根據用戶選擇菜單中的等級,繪製雷區的邊界
OD動態跟蹤:
01002ED5 /$ B8 60030000 mov eax,0x360 ; 算法如下,最大面積吧
01002EDA |> 48 /dec eax ; 循環0x360次
01002EDB |. C680 40530001>|mov byte ptr ds:[eax+0x1005340],0xF ; 全部設置成0xf
01002EE2 |.^ 75 F6 \jnz Xwinmine.01002EDA
01002EE4 |. 8B0D 34530001 mov ecx,dword ptr ds:[0x1005334] ; 長度
01002EEA |. 8B15 38530001 mov edx,dword ptr ds:[0x1005338] ; 寬度
01002EF0 |. 8D41 02 lea eax,dword ptr ds:[ecx+0x2] ; 長度+2
01002EF3 |. 85C0 test eax,eax
01002EF5 |. 56 push esi
01002EF6 |. 74 19 je Xwinmine.01002F11
01002EF8 |. 8BF2 mov esi,edx
01002EFA |. C1E6 05 shl esi,0x5 ; 長度左移5位
01002EFD |. 8DB6 60530001 lea esi,dword ptr ds:[esi+0x1005360] ; 爲定位到邊界值
01002F03 |> 48 /dec eax
01002F04 |. C680 40530001>|mov byte ptr ds:[eax+0x1005340],0x10 ; 雷區的上邊界設置爲0x10
01002F0B |. C60406 10 |mov byte ptr ds:[esi+eax],0x10 ; 設置下邊界的標誌位0x10
01002F0F |.^ 75 F2 \jnz Xwinmine.01002F03
01002F11 |> 8D72 02 lea esi,dword ptr ds:[edx+0x2]
01002F14 |. 85F6 test esi,esi
01002F16 |. 74 21 je Xwinmine.01002F39
01002F18 |. 8BC6 mov eax,esi
01002F1A |. C1E0 05 shl eax,0x5 ; 寬度移位
01002F1D |. 8D90 40530001 lea edx,dword ptr ds:[eax+0x1005340]
01002F23 |. 8D8408 415300>lea eax,dword ptr ds:[eax+ecx+0x1005341] ; 定位到左邊界
01002F2A |> 83EA 20 /sub edx,0x20 ; 一行的長度
01002F2D |. 83E8 20 |sub eax,0x20
01002F30 |. 4E |dec esi
01002F31 |. C602 10 |mov byte ptr ds:[edx],0x10 ; 設置左右邊界
01002F34 |. C600 10 |mov byte ptr ds:[eax],0x10
01002F37 |.^ 75 F1 \jnz Xwinmine.01002F2A
01002F39 |> 5E pop esi ; winmine.01005AA0
最後在內存中畫成的雷區的樣子:(0x10爲邊界值)
而我們打開遊戲界面如圖,剛好與之對應
生成地雷函數分析
之後回到SetMine函數中,在調用PaintMine之後繪製雷區之後通過調用Rand隨機在雷區中生成雷。
直接F5看一下SetMine
do
{
do
{
v1 = Rand(dword_1005334) + 1;
v2 = Rand(dword_1005338) + 1;
}
while ( *(&byte_1005340[32 * v2] + v1) & 0x80 );
*(&byte_1005340[32 * v2] + v1) |= 0x80u;
--Num;
}
while ( Num );
通過分析,可以明白這裏的v2即是縱座標,v1爲橫座標,然後判斷此處是否有地雷,沒有在設置地雷(0x8F)
OD動態分析證明上述分析:
最後生成完雷就是這個樣子:
按照這個自然就很好掃雷咯
後
關於掃雷 遊戲的核心算法大體就分析這麼多了,因爲主要是爲了下一篇的掃雷外掛的編寫,所以相關的鼠標點擊算法就沒有記錄下來了。
未完待續,大牛勿噴!