2011年9月29日星期四

[轉載]用IDA Pro + OD 來分析掃雷


作者:fatalucard

【聲明】小弟菜鳥一隻,潛水多年,酷愛遊戲,更愛逆向,喜歡修改遊戲。壇子里關於掃雷的文章已經很多了,各位前輩都已經分析透徹了。小弟發此文,只是給和我一樣的初級逆向愛好者提供一些思路,看看如何分析一個遊戲,做出修改器,甚至是修改遊戲規則。有不對的地方,各位前輩請指教,多謝。

工具:IDA Pro, OllyDbg, exeScope或者resource hacker

首先,我們知道,對於VC 寫的windows編程來說,一般都是創建窗體,註冊窗體,顯示窗體,更新窗體,再來一個消息處理函數。我們先用IDA Pro來看看,至於為什麼要用IDA,是因為我覺得IDA在某些方面比OD方便,比如函數名,變量名替換之類的,和OD配合使用,效果很好。

打開IDA Pro,自動進入EP,一開始肯定是一些加載器的初始化過程,沒有必要仔細看,一路向下走。

.text:01003E21 public start
.text:01003E21 start proc near
.text:01003E21 push 70h
.text:01003E23 push offset dword_1001390
.text:01003E28 call sub_100400C
.text:01003E2D xor ebx, ebx
.text:01003E2F push ebx ; lpModuleName
.text:01003E30 mov edi, ds:GetModuleHandleA
.text:01003E36 call edi ; GetModuleHandleA
.text:01003E38 cmp word ptr [eax], 5A4Dh ; MZ
.text:01003E3D jnz short loc_1003E5E
.text:01003E3F mov ecx, [eax+3Ch]
.text:01003E42 add ecx, eax
.text:01003E44 cmp dword ptr [ecx], 4550h
.text:01003E4A jnz short loc_1003E5E
.text:01003E4C movzx eax, word ptr [ecx+18h]
.text:01003E50 cmp eax, 10Bh
.text:01003E55 jz short loc_1003E76
.text:01003E57 cmp eax, 20Bh
.text:01003E5C jz short loc_1003E63

當到達這裡的時候:

text:01003F89 loc_1003F89: ; CODE XREF: start+158?j
.text:01003F89 push eax ; hAccTable
.text:01003F8A push esi ; int
.text:01003F8B push ebx ; int
.text:01003F8C push ebx ; lpModuleName
.text:01003F8D call edi ; GetModuleHandleA
.text:01003F8F push eax ; int
.text:01003F90 call sub_10021F0 ;這是咱們的Main函數。
.text:01003F95 mov esi, eax
.text:01003F97 mov [ebp-7Ch], esi
.text:01003F9A cmp [ebp-1Ch], ebx
.text:01003F9D jnz short loc_1003FA6
.text:01003F9F push esi ; int
.text:01003FA0 call ds:exit

我們就發現了main函數,10021F0,為什麼是這個?你看這個函數執行完了,程序就退出了啊。我們可以用IDA給它改名字,叫做Main。不廢話,進入main函數看看。

看! IDA多智能,告訴我們了參數和臨時變量,有WndClass和MSG,大家是不是眼前一亮。

.text:010021F0 Main proc near ; CODE XREF: start+16F?p
.text:010021F0
.text:010021F0 WndClass = WNDCLASSW ptr -4Ch
.text:010021F0 Msg = MSG ptr -24h
.text:010021F0 var_8 = tagINITCOMMONCONTROLSEX ptr -8
.text:010021F0 arg_0 = dword ptr 8
.text:010021F0 hAccTable = dword ptr 14h
.text:010021F0
.text:010021F0 push ebp
.text:010021F1 mov ebp, esp
.text:010021F3 sub esp, 4Ch
.text:010021F6 mov eax, [ebp+arg_0]
.text:010021F9 push ebx

繼續走,往下幾行就到這裡了:

text:0100225A mov [ebp+WndClass.style], edi
.text:0100225D mov [ebp+WndClass.lpfnWndProc], offset WndProccess ;就是她!
.text:01002264 mov [ebp+WndClass.cbClsExtra], edi
.text:01002267 mov [ebp+WndClass.cbWndExtra], edi
.text:0100226A mov [ebp+WndClass.hInstance], ecx
.text:0100226D mov [ebp+WndClass.hIcon], eax
.text:01002270 call ds:LoadCursorW
.text:01002276 push ebx ; int
.text:01002277 mov [ebp+WndClass.hCursor], eax
.text:0100227A call ds:GetStockObject
.text:01002280 mov [ebp+WndClass.hbrBackground], eax
.text:01002283 lea eax, [ebp+WndClass]
.text:01002286 mov esi, offset AppName
.text:0100228B push eax ; lpWndClass
.text:0100228C mov [ebp+WndClass.lpszMenuName], edi
.text:0100228F mov [ebp+WndClass.lpszClassName], esi
.text:01002292 call ds:RegisterClassW

RegisterClassW,哈哈,我們這下子就知道消息處理函數了。先繼續往下走,過一會兒我們看看這個消息處理函數。

.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

嗯,創建窗體了。接著走,看返回的EAX不為空的話就跳。跳到這裡:

text:01002325 loc_1002325: ; CODE XREF: Main+12C?j
.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:01002342 loc_1002342: ; CODE XREF: Main+142?j
.text:01002342 push dword_10056C4
.text:01002348 call sub_1003CE5
.text:0100234D call sub_100367A
.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


發現4個函數,我們要進去看看,因為憑咱們玩掃雷的經驗來看,雷的產生是在窗體繪製之前就做好了,也就是showWindow之前,會有函數來布雷,所以,這四個函數,sub_1001950, sub_1002B14, sub_1003CE5,sub_100367A應該有一個或者多個來處理布雷。一個一個看看。

先看sub_1001950,函數太長,就不都貼了,貼一部分:
....
....
.text:01001978 mov edi, ds:GetMenuItemRect
.text:0100197E mov dword_1005B88, eax
.text:01001983 jnz short loc_10019DB
.text:01001985 mov edx, dword_1005B34
.text:0100198B add edx, eax
.text:0100198D mov eax, hMenu
.text:01001992 cmp eax, ebp
.text:01001994 mov dword_1005B88, edx
.text:0100199A jz short loc_10019DB
.text:0100199C lea edx, [esp+40h+rcItem]
.text:010019A0 push edx ; lprcItem
.text:010019A1 push ebp ; uItem
.text:010019A2 push eax ; hMenu
.text:010019A3 push ecx ; hWnd
.text:010019A4 call edi ; GetMenuItemRect
.text:010019A6 test eax, eax
.text:010019A8 jz short loc_10019DB
.text:010019AA lea eax, [esp+40h+var_20]
.text:010019AE push eax ; lprcItem
.text:010019AF push ebx ; uItem
.text:010019B0 push hMenu ; hMenu
.text:010019B6 push hWnd ; hWnd
.text:010019BC call edi ; GetMenuItemRect
....
....

從函數上看,是處理菜單那一塊的東西,(如果錯了,請各位指正,我當時一看函數不像,就沒仔細看)。這個函數不像,那就下一個sub_1002B14:

.text:01002B14 sub_1002B14 proc near ; CODE XREF: Main+13B?p
.text:01002B14 call sub_1002414
.text:01002B19 test eax, eax
.text:01002B1B jnz short loc_1002B1E
.text:01002B1D retn

她居然call了另外一個函數,sub_1002414。進入sub_1002414看了就知道,是處理resource的,就不貼了,很明顯的。
下一個函數,sub_1003CE5:

.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 ; Assign a new menu to the specified window
.text:01003D13 push 2
.text:01003D15 call sub_1001950
.text:01003D1A retn 4

注意到一個SetMenu,應該是設置菜單的,其中,call了一個函數sub_1001516。不放心的話,就進去看看。

sub_1001516:
.text:01001516 sub_1001516 proc near ; CODE XREF: sub_1001B49+24?p
.text:01001516 ; sub_1003CE5+9?p
.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 sub_1003CC4
.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 sub_1003CC4
.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 sub_1003CC4
.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 sub_1003CC4
.text:01001575 push dword_10056C8
.text:0100157B push 211h ;529
.text:01001580 call sub_1003CC4
.text:01001585 push Data
.text:0100158B push 20Fh ;527
.text:01001590 call sub_1003CC4
.text:01001595 push dword_10056B8
.text:0100159B push 20Eh ;526
.text:010015A0 call sub_1003CC4
.text:010015A5 retn
.text:010015A5 sub_1001516 endp

發現每次都是壓入一個數,然後call sub_1003CC4。因為她的父函數是處理菜單的,因此,我們懷疑壓入的數是控件ID,用reource hacker看看。
果不其然:

500 MENU
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
{
POPUP "&Game"
{
  MENUITEM "&New\tF2", 510
  MENUITEM SEPARATOR
  MENUITEM "&Beginner", 521
  MENUITEM "&Intermediate", 522
  MENUITEM "&Expert", 523
  MENUITEM "&Custom...", 524
  MENUITEM SEPARATOR
  MENUITEM "&Marks (?)", 527
  MENUITEM "Co&lor", 529
  MENUITEM "&Sound", 526
  MENUITEM SEPARATOR
  MENUITEM "Best &Times...", 528
  MENUITEM SEPARATOR
  MENUITEM "E&xit", 512
}
POPUP "&Help"
{
  MENUITEM "&Contents\tF1", 590
  MENUITEM "&Search for Help on...", 591
  MENUITEM "Using &Help", 592
  MENUITEM SEPARATOR
  MENUITEM "&About Minesweeper...", 593
}
}

是處理用戶選的是初級,中級,高級之類的。所以,sub_1003CE5也不是。就剩下sub_100367A了:
sub_100367A函數很長,慢慢分析吧:

.text:0100367A sub_100367A proc near ; CODE XREF: sub_100140C+CA?p
.text:0100367A ; sub_1001B49+33?j ...
.text:0100367A mov eax, dword_10056AC
.text:0100367F mov ecx, uValue
.text:01003685 push ebx
.text:01003686 push esi
.text:01003687 push edi
.text:01003688 xor edi, edi
.text:0100368A cmp eax, dword_1005334
.text:01003690 mov dword_1005164, edi
.text:01003696 jnz short loc_10036A4
.text:01003698 cmp ecx, dword_1005338
.text:0100369E jnz short loc_10036A4
.text:010036A0 push 4
.text:010036A2 jmp short loc_10036A6

發現最終會進入loc_10036A6,走著:

.text:010036A6 loc_10036A6: ; CODE XREF: sub_100367A+28?j
.text:010036A6 pop ebx
.text:010036A7 mov dword_1005334, eax
.text:010036AC mov dword_1005338, ecx
.text:010036B2 call sub_1002ED5
.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: sub_100367A+74?j
.text:010036C7 ; sub_100367A+89?j
.text:010036C7 push dword_1005334
.text:010036CD call RandomReminder
.text:010036D2 push dword_1005338
.text:010036D8 mov esi, eax
.text:010036DA inc esi
.text:010036DB call RandomReminder
.text:010036E0 inc eax
.text:010036E1 mov ecx, eax
.text:010036E3 shl ecx, 5
.text:010036E6 test byte ptr dword_1005340[ecx+esi], 80h
.text:010036EE jnz short loc_10036C7
.text:010036F0 shl eax, 5
.text:010036F3 lea eax, dword_1005340[eax+esi]
.text:010036FA or byte ptr [eax], 80h
.text:010036FD dec dword_1005330
.text:01003703 jnz short loc_10036C7
.text:01003705 mov ecx, dword_1005338
.text:0100370B imul ecx, dword_1005334
.text:01003712 mov eax, dword_10056A4
.text:01003717 sub ecx, eax
.text:01003719 push edi
.text:0100371A mov dword_100579C, edi
.text:01003720 mov dword_1005330, eax
.text:01003725 mov dword_1005194, eax
.text:0100372A mov dword_10057A4, edi
.text:01003730 mov dword_10057A0, ecx
.text:01003736 mov dword_1005000, 1
.text:01003740 call sub_100346A
.text:01003745 push ebx
.text:01003746 call sub_1001950
.text:0100374B pop edi
.text:0100374C pop esi
.text:0100374D pop ebx
.text:0100374E retn
.text:0100374E sub_100367A endp

在這一段中,我們發現了Rand()函數,就在01003940,我已經將她改名成了RandomReminder。進入發現就是隨機產生一個數,然後除以參數,返回餘數,這個應該就是隨機布雷,而且發現調用2次,很容易想到是分別產生X和Y坐標的。兩次調用的參數分別放在dword_1005334和dword_1005338中,懷疑是雷區的大小,進入OD,選擇不同難度,然後斷此函數看看參數,發現,果不其然,就是雷區大​​小。可以給dword_1005334和dword_1005338改名了,以後在遇到這兩個參數就好識別了。接著走,發現這段話:

.text:010036E6 test byte ptr dword_1005340[ecx+esi], 80h
.text:010036EE jnz short loc_10036C7
.text:010036F0 shl eax, 5
.text:010036F3 lea eax, dword_1005340[eax+esi]
.text:010036FA or byte ptr [eax], 80h

哈哈,dword_1005340[ecx+esi],雷區地址。是這個樣子的,dword_1005340[Y*32 + X]; dword_1005330很明顯是雷數。

OK,到這裡就找到雷區了,比較麻煩,還是各位前輩的直接下斷Rand()簡單,但是我比較笨笨,一開始怕萬一不是用Rand()怎麼辦呢,於是就自己分析看看。先寫到這裡,下次主要靠OD分析按鈕事件。謝謝。
原文:http://bbs.pediy.com/showthread.php?t=138855

没有评论:

发表评论