逆向-掃雷算法分析

最近思來想去,眼看着自己就要進某廠遊戲安全團隊實習了,也不能整天的無所事事,所以就尋思着先找點最簡單的遊戲用來練練手。想到之前逆向過一些小遊戲,就把之前分析的掃雷整理了一下啊,寫了個外掛,發了上來。


最近實在是比較忙,快期中考試了,什麼都不會,所以忙着預習去了,本文只寫了關於掃雷逆向的部分,相關的外掛編寫已經寫差不多了,等最近有時間整理出來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動態分析證明上述分析:
這裏寫圖片描述

最後生成完雷就是這個樣子:
這裏寫圖片描述
按照這個自然就很好掃雷咯
這裏寫圖片描述

關於掃雷 遊戲的核心算法大體就分析這麼多了,因爲主要是爲了下一篇的掃雷外掛的編寫,所以相關的鼠標點擊算法就沒有記錄下來了。
未完待續,大牛勿噴!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章