生活随笔
收集整理的這篇文章主要介紹了
逆向-扫雷算法分析
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
最近思來想去,眼看著自己就要進(jìn)某廠游戲安全團(tuán)隊(duì)實(shí)習(xí)了,也不能整天的無所事事,所以就尋思著先找點(diǎn)最簡單的游戲用來練練手。想到之前逆向過一些小游戲,就把之前分析的掃雷整理了一下啊,寫了個(gè)外掛,發(fā)了上來。
最近實(shí)在是比較忙,快期中考試了,什么都不會,所以忙著預(yù)習(xí)去了,本文只寫了關(guān)于掃雷逆向的部分,相關(guān)的外掛編寫已經(jīng)寫差不多了,等最近有時(shí)間整理出來o(╯□╰)o
工具
分析對象:winmine/掃雷(windows xp版本)
逆向工具:ollydbg,IDA,peid,ResHacker
操作平臺:windows7 旗艦版
分析過程
main函數(shù)定位
peid加載winmine發(fā)現(xiàn)為vc編寫的,沒有加過殼,則可以確定加載程序的流程為:獲取當(dāng)前版本號,堆初始化,獲取命令行參數(shù),獲取環(huán)境變量,分離出命令行參數(shù),全局?jǐn)?shù)據(jù)和浮點(diǎn)寄存器初始化所以在main(這里應(yīng)該是winmain了)調(diào)用前,調(diào)用流程為:
GetVersion()—–>_heap_init()—–>GetCommandLine()—–>_crtGetEnvironmentStrings()—–>_setargv()—–>_setenvp()—–>_cinit()
所以很容易找到winmain的調(diào)用地址,跟進(jìn)之后發(fā)現(xiàn)
發(fā)現(xiàn)這里便是在做一些初始化
很快找到:
:
010022D5
mov ecx, yBottom
.text:010022DB
mov edx, xRight
.text:010022E1 push edi
.text:010022E2 push hInstance
.text:010022E8 add ecx, eax
.text:010022EA
push edi
.text:010022EB
push edi
.text:010022EC
push ecx
.text:010022ED
mov ecx, dword_1005A90
.text:010022F3
add edx, ecx
.text:010022F5
push edx
.text:010022F6
mov edx,
Y
.text:010022FC
sub edx, eax
.text:010022FE
mov eax,
X
.text:01002303 push edx
.text:01002304 sub eax, ecx
.text:01002306 push eax
.text:01002307 push 0CA0000h
.text:0100230C
push esi
.text:0100230D
push esi
.text:0100230E
push edi
.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:
.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:
.text:01002336 call sub_1003950
.text:0100233B
.text:0100233B loc_100233B:
.text:0100233B xor eax, eax
.text:0100233D
jmp loc_10023C6
.text:01002342
.text:01002342
.text:01002342 loc_1002342:
.text:01002342 push dword_10056C4
.text:01002348 call sub_1003CE5
.text:0100234D
call SetMine
.text:01002352 push ebx
.text:01002353 push hWnd
.text:01002359 call ds:ShowWindow
.text:0100235F
push hWnd
.text:01002365 call ds:UpdateWindow
.text:0100236B
mov esi, ds:GetMessageW
.text:01002371 mov dword_1005B38, edi
.text:01002377 jmp short loc_10023A4
分析可疑函數(shù)
在createwindow,分別調(diào)用了4個(gè)函數(shù),然后就showwindow了,那么這4個(gè)函數(shù)肯定完成了在掃雷前用戶的選擇,資源的加載,雷區(qū)的繪制.
跟進(jìn)第一個(gè)函數(shù)sub_1001950,發(fā)現(xiàn)主要調(diào)用了GetMenuItemRect和MoveWindow函數(shù)將窗口待會創(chuàng)建到制定位置,這里跟我們關(guān)心的算法沒有太大關(guān)系,所以不做分析。
跟進(jìn)第二個(gè)函數(shù)sub_1002B14,發(fā)現(xiàn)主要在調(diào)用LoadResource進(jìn)行加載資源
跟進(jìn)第三個(gè)函數(shù)sub_1003CE5:
.text:01003CE5 sub_1003CE5 proc near
.text:01003CE5
.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
.text:01003D07
push hWnd
.text:01003D0D
call ds:SetMenu
.text:01003D13
push 2
.text:01003D15
call sub_1001950
.text:01003D1A retn
4
.text:01003D1A sub_1003CE5 endp
發(fā)現(xiàn)在調(diào)用sub_1001516之后調(diào)用了SerMenu,之后再次調(diào)用了第一個(gè)函數(shù)sub_1001950,應(yīng)該就是處理用戶選擇不同難度而對窗口進(jìn)行的重新布置吧
跟進(jìn)sub_1001516
為了之后分析方便,將sub_1001516改名為MenuChoose,sub_1001950改名為SetWindow
.text:01001516 MenuChoose proc near
.text:01001516
.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
.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
.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
.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
.text:01001570 call CheckMenu
.text:01001575 push dword_10056C8
.text:0100157B
push 211h
.text:01001580 call CheckMenu
.text:01001585 push Data
.text:0100158B
push 20Fh
.text:01001590 call CheckMenu
.text:01001595 push dword_10056B8
.text:0100159B
push 20Eh
.text:010015A0
call CheckMenu
.text:010015A5 retn
.text:010015A5 MenuChoose endp
這里差不多就應(yīng)該是通過資源ID選擇了,用ResHacker分析一下得證
然后分析最后一個(gè)函數(shù)SetMine(原函數(shù)名為sun_xxxxxx,通過分析已經(jīng)修改函數(shù)名)
.text:010036A4
.text:010036A4 loc_10036A4:
.text:010036A4
.text:010036A4
push 6
.text:010036A6
.text:010036A6 loc_10036A6:
.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:
.text:010036C7
.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
繪制雷區(qū)函數(shù)分析
發(fā)現(xiàn)期中一個(gè)函數(shù)(PaintMine)便是在繪制雷區(qū)
大體F5把握一下大致的意思
int __cdecl PaintMine()
{
signed int v0;
int v1;
int v2;
int result;
int v4;
char *v5; 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;
}
大體意思就是先將最大可能的雷區(qū)內(nèi)數(shù)據(jù)全部設(shè)置為0xF,然后在根據(jù)用戶選擇菜單中的等級,繪制雷區(qū)的邊界
OD動態(tài)跟蹤:
01002ED5 /$ B8
60030000 mov eax,
0x360 ; 算法如下,最大面積吧
01002EDA
|> 48 /dec eax ; 循環(huán)0x360次
01002EDB
|. C680 40530001>|mov byte ptr ds:[eax+0x1005340],0xF ; 全部設(shè)置成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 ; 雷區(qū)的上邊界設(shè)置為0x10
01002F0B
|. C60406 10 |mov byte ptr ds:[esi+eax],0x10 ; 設(shè)置下邊界的標(biāo)志位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 ; 設(shè)置左右邊界
01002F34
|. C600 10 |mov byte ptr ds:[eax],0x10
01002F37
|.^ 75 F1 \jnz Xwinmine.01002F2A
01002F39
|> 5E pop esi ; winmine.01005AA0
最后在內(nèi)存中畫成的雷區(qū)的樣子:(0x10為邊界值)
而我們打開游戲界面如圖,剛好與之對應(yīng)
生成地雷函數(shù)分析
之后回到SetMine函數(shù)中,在調(diào)用PaintMine之后繪制雷區(qū)之后通過調(diào)用Rand隨機(jī)在雷區(qū)中生成雷。
直接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;}
while ( Num );
通過分析,可以明白這里的v2即是縱坐標(biāo),v1為橫坐標(biāo),然后判斷此處是否有地雷,沒有在設(shè)置地雷(0x8F)
OD動態(tài)分析證明上述分析:
最后生成完雷就是這個(gè)樣子:
按照這個(gè)自然就很好掃雷咯
后
關(guān)于掃雷 游戲的核心算法大體就分析這么多了,因?yàn)橹饕菫榱讼乱黄膾呃淄鈷斓木帉?#xff0c;所以相關(guān)的鼠標(biāo)點(diǎn)擊算法就沒有記錄下來了。
未完待續(xù),大牛勿噴!
總結(jié)
以上是生活随笔為你收集整理的逆向-扫雷算法分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。