Reverzujeme Minesweeper

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: ober
Datum: 10.9.2008
Hodnocení/Hlasovalo: 2/41

Cilem clanku bude ukazat moznosti reverze engineeringu. Nasim ukolem bude upravit dobre znamou hru M$ - Minesweeper a to tak, abychom dokazali zobrazit kde jsou rozmistene miny.

Cilem clanku bude ukazat moznosti reverze engineeringu.
Nasim ukolem bude upravit dobre znamou hru M$ - Minesweeper a to tak abychom dokazali zobrazit kde jsou rozmistene miny.

pouzite nastroje:
- OllyDbg
- MSVC 6.0

reverzovany program:
- minesweeper.exe ver. 5.1

Pridame jednu polozku do menu, po jejimz vybrani prekreslime hraci plochu tak, aby se zobrazilo rozmisteni min.
Otevru minesweeper.exe v OllyDbg, zbezne kouknu na kod a vidim, ze na konci souboru cca od 01004A56 jsou jiz same nuly a tam nekam budeme dopisovat nas potrebny kod.
Jak pridat polozku menu ?
Jedna z moznosti je pouzit ResourceHacker, ale my to udelame ciste, programove, nebudeme zasahovat do resources. K pridani polozky do menu slouzi API f-ce

BOOL InsertMenu(HMENU hMenu,
    UINT uPosition,
    UINT uFlags,
    PTR uIDNewItem,
    LPCTSTR lpNewItem
);

klicova vec je ale nalezt misto, kam volani teto funkce pridat. v Olly vyhledame Search for / name in current module (CTRL+N), tam muzeme najit LoadMenuW, k tomu najdeme References a vidim, ze na 010022AC je call LoadMenuW, to vypada jako vhodne misto,



zde volani prepiseme na call na nas kod, ktery umistime na konci souboru, od adr. 01004A80, zbytek vyplnime NOPy

010022A1 |. E8 DA270000 CALL winmine.01004A80
010022A6 |. 90          NOP
010022A7 |. 90          NOP
010022A8 |. 90          NOP
010022A9 |. 90          NOP
010022AA |. 90          NOP
010022AB |. 90          NOP
010022AC |. 90          NOP
010022AD |. 90          NOP
010022AE |. 90          NOP
010022AF |. 90          NOP
010022B0 |. 90          NOP
010022B1 |. 90          NOP

dopiseme zatim jen volani LoadMenuW, ktere jsme prepsali CALLem na nas kod a NOPy

01004A80 /$ 68 F4010000   PUSH 1F4                                ; /RsrcName = 1F4
01004A85 |. FF35 305B0001 PUSH DWORD PTR DS:[1005B30]             ; |hInst = 01000000
01004A8B |. FF15 D4100001 CALL DWORD PTR DS:[<&USER32.LoadMenuW>] ; \LoadMenuW
01004A91 |. C3 RETN

a zkusime spustit (F9), program se spustil, original menu se naloadovalo, vypada to, ze muzeme pokracovat. Reloadujeme minesweeper.exe (CTRL+F2), zobrazime predchozi patche (CTRL+P) a mezernikem je aktivujeme.
Nyni pridame polozku menu. Je zde ale problem, protoze jak vidime (CTRL+N) tak .exe neimportuje InsertMenu, budeme si tedy jeji adresu muset zjistit pres LoadLibrary/GetProcAddress. InsertMenu je v user32.dll, naloadujeme ji a zjistime adresu InsertMenu, k tomu budu potrebovat dva stringy, ulozim si je od adresy 01004A56, tj. binary edit (CTRL+E) a po zapsani stringu udelam znovu analyze code (CTRL+A)

01004A56 . 49 6E 73 65 72>ASCII "InsertMenuA",0
01004A62 . 75 73 65 72 33>ASCII "user32.dll",0
01004A6D . 53 68 6F 77 20>ASCII "Show mines",0

Adresu LoadLibraryA a GetProcAddress najdu v "names" (CTRL+N) po volani LoadLibrary je v EAX handle na user32.dll, ten potrebujeme jako parametr pro GetProcAddressA

MOV ESI,winmine.01004A62                 ; ASCII "user32.dll"
PUSH ESI                                 ; /FileName => "user32.dll"
CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; \LoadLibraryA
MOV ESI,winmine.01004A56                 ; ASCII "InsertMenuW"
PUSH ESI                                 ; /ProcNameOrOrdinal => "InsertMenuW"
PUSH EAX                                 ; |hModule
CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; \GetProcAddress

protoze po navratu z naseho kodu potrebujeme mit obsah registru takovy, jaky by byl po volani LoadMenuW, nasledujici kod (od 010022B2) s tim pocita, musime jeste udelat upravu a uschovat obsah registru a pred navratem do puvodniho kodu obsah registru obnovit, kod bude vypadat takto:

01004A80 /$ 68 F4010000   PUSH 1F4                                 ; /RsrcName = 1F4
01004A85 |. FF35 305B0001 PUSH DWORD PTR DS:[1005B30]              ; |hInst = NULL
01004A8B |. FF15 D4100001 CALL DWORD PTR DS:[<&USER32.LoadMenuW>]  ; \LoadMenuW
01004A91 |. 56            PUSH ESI
01004A92 |. 53            PUSH EBX
01004A93 |. 52            PUSH EDX
01004A94 |. 51            PUSH ECX
01004A95 |. 50            PUSH EAX
01004A96 |. BE 624A0001   MOV ESI,winmine.01004A62                 ; ASCII "user32.dll"
01004A9B |. 56            PUSH ESI                                 ; /FileName => "user32.dll"
01004A9C |. FF15 9C100001 CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; \LoadLibraryA
01004AA2 |. BE 564A0001   MOV ESI,winmine.01004A56                 ; ASCII "InsertMenuA"
01004AA7 |. 56            PUSH ESI                                 ; /ProcNameOrOrdinal => "InsertMenuA"
01004AA8 |. 50            PUSH EAX                                 ; |hModule
01004AA9 |. FF15 94100001 CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; \GetProcAddress
01004AAF |. BE 6D4A0001   MOV ESI,winmine.01004A6D                 ; ASCII "Show mines"
01004AB4 |. 8B1C24        MOV EBX,DWORD PTR SS:[ESP]               ; do EAX nacteme handle menu
01004AB7 |. 56            PUSH ESI                                 ; menu name
01004AB8 |. 68 52020000   PUSH 252                                 ; menu identifier
01004ABD |. 68 00040000   PUSH 400                                 ; flag MF_BYPOSITION
01004AC2 |. 6A FF         PUSH -1                                  ; menu item position
01004AC4 |. 53            PUSH EBX                                 ; handle to menu
01004AC5 |. FFD0          CALL EAX                                 ; call InsertMenu
01004AC7 |. 58            POP EAX
01004AC8 |. 59            POP ECX
01004AC9 |. 5A            POP EDX
01004ACA |. 5B            POP EBX
01004ACB |. 5E            POP ESI
01004ACC \. C3            RETN

menu ID dame takove, aby nekolidovalo s nejakym ID jiz existujiciho menu, ty zjistime napr. tak, ze otevreme minesweeper.exe ve Visual Studiu jako resources a polozky menu si projdeme - u vsech vidime ID. Flag MF_BYPOSITION a position -1 znamena, ze polozka menu bude pridana na konec

nasledne potrebujeme dopsat kod, ktery se provede po vybrani naseho menu. Vyber menu se aplikaci signalizuje poslanim zpravy WM_COMMAND, kde parametrem je menu ID. Musime tedy nalezt zpracovani WM_COMMAND. Kod neni prilis rozsahly, takze se nam brzo podari najit:

01001DBC |> 0FB745 10     MOVZX EAX,WORD PTR SS:[EBP+10] ; Case 111 (WM_COMMAND) of switch 01001D5B
01001DC0 |. B9 10020000   MOV ECX,210
01001DC5 |. 3BC1          CMP EAX,ECX
01001DC7 |. 0F8F 0F010000 JG winmine.01001EDC
01001DCD |. 0F84 FF000000 JE winmine.01001ED2
01001DD3 |. 3D FE010000   CMP EAX,1FE
01001DD8 |. 0F84 EA000000 JE winmine.01001EC8
01001DDE |. 3BC6          CMP EAX,ESI
01001DE0 |. 0F84 B7000000 JE winmine.01001E9D
01001DE6 |. 3D 08020000   CMP EAX,208
01001DEB |. 0F8E B8030000 JLE winmine.010021A9
01001DF1 |. 3D 0B020000   CMP EAX,20B
01001DF6 |. 7E 61         JLE SHORT winmine.01001E59
01001DF8 |. 3D 0C020000   CMP EAX,20C
01001DFD |. 74 50         JE SHORT winmine.01001E4F
01001DFF |. 3D 0E020000   CMP EAX,20E
01001E04 |. 74 20         JE SHORT winmine.01001E26
01001E06 |. 3D 0F020000   CMP EAX,20F

zde je videt testovani, ktera polozka menu byla vybrana (napr. CMP EAX, 20E - "Sound") U rozsahlejsiho kodu by bylo treba najit adresu WndProc jinak. Funkce RegisterClass ma jako parametr pointer na strukturu WNDCLASS, ktera ma jednu z polozek adresu WndProc.
V obsluze WM_COMMAND presmerujeme kod na nasi funkci

01001DBC E8 0D2D0000 CALL winmine.01004ACE
01001DC1 90          NOP
01001DC2 90          NOP
01001DC3 90          NOP
01001DC4 90          NOP

a od adresy 01004ACE napiseme funci pro obsluhu naseho menu nejdrive dame kod, ktery jsme smazali diky prepsani na call

01004ACE 0FB745 10   MOVZX EAX,WORD PTR SS:[EBP+10]
01004AD2 B9 10020000 MOV ECX,210

potom otestujeme, zda je vybrana nase polozka menu, ktere jsme dali ID 252, jinak dame retn na puvodni kod

01004AD7 . 3D 52020000 CMP EAX,252
01004ADC . 74 01       JE SHORT winmine.01004ADF
01004ADE . C3          RETN

ted potrebujeme vyhledat, kde je ulozena informace o hracim poli, tj. kde je informace o rozmisteni min. Nastavime si breakpointy do mista, kde se zpracovava message WM_LBUTTONUP, WM_LBUTTONDOWN spustime, klikneme mysi na nejake policko v minach a krokujeme, sledujeme kde se co cte z pameti a tak zjistime, ze velmi podezrele to vypada od adresy 01005340, budeme tedy sledovat tuto oblast. Pri debugovani zjistime ze hodnoty maji tento vyznam:
10 - okraj,
0F - neprozkoumano,
8A - naslapnuta mina,
8F - skryta mina, atd.,

nas zajima 8F a 8A, trochu experimentujeme s velikosti hraci plochy a zjistime, ze maximalni rozmer je 24x30 a je ulozena od 01005340 az do 0100567F pri maximalni velikosti. Ted vime co potrebujeme a muzeme psat kod. Nahradime vsechny 8F za 8A a dame prekreslit hraci pole volanim InvalidateRect

01004ADF |> 60            PUSHAD
01004AE0 |. B8 7F560001   MOV EAX,winmine.0100567F
01004AE5 |> 8B08          /MOV ECX,DWORD PTR DS:[EAX]
01004AE7 |. 80F9 8F       |CMP CL,8F
01004AEA |. 75 03         |JNZ SHORT winmine.01004AEF
01004AEC |. C600 8A       |MOV BYTE PTR DS:[EAX],8A
01004AEF |> 48            |DEC EAX
01004AF0 |. 3D 40530001   |CMP EAX,winmine.01005340
01004AF5 |.^75 EE         \JNZ SHORT winmine.01004AE5
01004AF7 |. 6A 01         PUSH 1                                   ; /Erase = TRUE
01004AF9 |. 6A 00         PUSH 0                                   ; |pRect = NULL
01004AFB |. 6A 00         PUSH 0                                   ; |hWnd = NULL
01004AFD |. FF15 10110001 CALL DWORD PTR DS:[<&USER32.InvalidateRe>; \InvalidateRect
01004B03 |. 61            POPAD
01004B04 \. C3            RETN

zkusime spustit, a jestli tam nejsou chyby tak muzeme videt neco takoveho:



Ted uz zbyva napsat programek, ktery potrebne zmeny - patch aplikuje do minesweeper.exe, ale to uz by mel kazdy zvladnout, napr. Pomoci sqeleho DUP2.
Jeste podekovani ZaiRoN-ovi, jehoz textem jsem se inspiroval.