Minesweeper reversing
Imbrogliamo con stile

Data

by ZaiRoN

 

2o-11-2oo3

UIC's Home Page

Published by Quequero

"Se devo andare dove piove merda, voglio sapere da che parte soffia il vento"

Robert Redford - Spy Game

Bella zai, proprio simpatica come idea!

"Hai un coniglio in tasca o sei contento di vedermi!" 

Joanna Cassidy - Chi ha incastrato Roger Rabbit

....

E-mail: zairon(at)woodmann(dot)net
Nick, UIN, canale IRC/EFnet frequentato: 
ZaiRoN su irc: #crack-it
ZaiRoN su efnet: #cracking4newbies

....

Difficoltà

( )NewBies (Z)Intermedio ( )Avanzato ( )Master

 
Un altro adding functionality project. 

Minesweeper reversing
Imbrogliamo con stile
Written by ZaiRoN

Introduzione

Oggi modificheremo uno dei miei giochi M$ preferiti: MineSweeper (conosciuto anche come Campo minato o Prato fiorito). L'idea non è originale; in rete trovate molti cheat e un interessante Minesweeper reader ma nessuno (che io sappia...) è andato direttamente a reversare il gioco. Il nostro scopo sarà quello di aggiungere una nuova modalità al gioco che, se abilitata, vi permetterà di scoprire le bombe al semplice passaggio del mouse. 
La versione del gioco che userò è quella che trovate insieme alla versione inglese di XP. Perché non quella in italiano? Perché, perché... sono fatti miei! Scherzi a parte, questo progetto è stato tema di discussione all'RCE forum dove la maggior parte delle persone usano questa versione. Comunque sia, non vi preoccupate perché le varie versioni di MineSweeper sono tra loro molto simili, non credo troverete difficoltà ad adattare questo tutorial al vostro specifico file.

Tools usati

- Debugger (Ollydbg)
- Disassemblatore (Ida)
- Editor di risorse (Resource hacker)
- Spy++ (opzionale)
- Compilatore c e asm (lcc, masm)

URL o FTP del programma

Nel file allegato trovate il gioco, il loader e tutti i sorgenti: allegato

Essay

Preambolo
Questa volta useremo una DLL per implementare la nuova caratteristica del gioco per cui il file .exe verrà modificato in minima parte, lo stretto necessario per chiamare le funzioni della DLL e poco altro. Questo non sarà soltanto un semplice esercizio di reversing ma anche un esercizio di programmazione perché dovremo scrivere una dll, lavorare (in minima parte) con la GDI e infine scrivere un piccolo loader che patch-erà il programma a runtime.
 
Il tutorial sarà articolato in questo modo: prima di tutto andremo a cercare i punti del programma originale che dovranno essere modificati in jump al nuovo codice (codice che conterrà le nostre piccole modifiche); fatto questo scriveremo il codice da aggiungere a Minesweeper.exe e infine implementeremo la dll e il loader.
 
Aggiungiamo un nuovo menu item
Dovremmo essere in grado di passare dal modo *normale* al modo *furbacchione* (e viceversa) per cui la miglior cosa da fare è quella di utilizzare un nuovo menu item. Aggiungeremo questo nuovo item, con label 'Special mode', dentro il menu 'Game', sotto l'item 'New'.
Per far ciò potremmo usare un qualsiasi resource editor ma dato che dobbiamo patchare il file in memora ci conviene pensare in modo diverso. Come posso aggiungere un nuovo item ad un menu esistente? Possiamo usare la funzione InsertMenuItem. Vedremo dopo come usare questa funzione, per il momento ci basterà sapere che questa funzione verrà chiamata nella fase di inizializzazione del Minesweeper, esattamente dopo che il menu è stato caricato (dopo perché la funzione necessita dell'handle del menu...).
Adesso dobbiamo trovare il punto dove inserire il jump alla nostra cava (vedremo dopo quale cava scegliere). Per caricare il menu, il programma usa la funzione LoadMenu; sfruttiamo questa funzione mettendoci un bel breakpoint e vediamo cosa succede:
 
:010014B9 push 1F4h <------- lpMenuName, identificatore del menu da caricare
:010014BE push hInstance <-- hInstance
:010014C4 call ds:LoadMenuW
:010014CA push 1F5h <------- lpTableName
:010014CF mov hMenu, eax
:010014D4 push hInstance <-- hInstance
:010014DA call ds:LoadAcceleratorsW
:010014E0 mov [ebp+hAccTable], eax
:010014E3 call sub_10023DB
:010014E8 mov eax, dword_10052A4
:010014ED mov ecx, yBottom
 
Bene, quando il menu sarà caricato potremmo tranquillamente inserire il nostro item. Metteremo il jump al codice che chiamerà InsertMenuItem direttamente a 10014E8. Ho scelto questo indirizzo semplicemente perché ha lo stesso numero di bytes della nuova istruzione di salto...
 
Troviamo il punto in cui il programma controlla se un menu item è stato click-ato
Questo passo è utile perché dobbiamo interagire con questo nuovo item; se l'item 'Special mode' è checkato il programma mostrerà le bombe, altrimenti il gioco rimarrà inalterato.
Per risolvere questo secondo punto dobbiamo prima di tutto individuare la window procedure, la funzione che riceve e gestisce tutti i messaggi mandati al programma; ci sono molti modi per risolvere questo secondo punto, vediamone un paio.
- Possiamo usare Spy++, una piccola applicazione che vi mostra molte informazioni relative ai processi attivi sul vostro pc.
Lanciate il gioco e subito a ruota Spy++. Nella lista dei processi attivi di Spy++ cerchiamo il processo Minesweeper e facciamoci un bel doppio click sopra; vi apparirà una nuova dialog contenente un bel pò di informazioni relative al processo, una di queste informazioni è l'indirizzo a partire dal quale si trova la window procedure. 
- Un altro modo per trovare la window procedure è quello di sfruttare Ida
Caricate il file in Ida e premete ctrl-p, una nuova dialog apparirà mostrandovi la lista delle funzioni utilizzate dal programma. Noi siamo interessati alla funzione WinMain perché in essa viene definita la window class e proprio grazie a questa struttura possiamo capire dove si nasconde la window procedure. Il programma usa la funzione RegisterClassW per registrare la window class:
 
ATOM RegisterClass( 
    CONST WNDCLASS *lpWndClass   // Indirizzo della struttura WNDCLASS 
); 

e: 

typedef struct _WNDCLASS { 
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra; 
    int cbWndExtra; 
    HANDLE hInstance; 
    HICON hIcon; 
    HCURSOR hCursor; 
    HBRUSH hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
} WNDCLASS; 
 
La struttura qua sopra contiene gli attributi della finestra che verrà creata; a noi interessa il parametro lpfnWndProc perché punta direttamente alla window procedure. Torniamo in Ida e vediamo a che indirizzo si trova questa benedetta window procedure:

:01001472 mov [ebp+WndClass.style], edi   <----------------------- Parametro style
:01001475 mov [ebp+WndClass.lpfnWndProc], offset sub_100180A   <-- Parametro lpfnWndProc, l'indirizzo cercato è 100180A
:0100147C mov [ebp+WndClass.cbClsExtra], edi   <------------------ Parametro cbClsExtra
:0100147F mov [ebp+WndClass.cbWndExtra], edi   <------------------ Parametro cbWndExtra
:01001482 mov [ebp+WndClass.hInstance], ecx   <------------------- Parametro hInstance
:01001485 mov [ebp+WndClass.hIcon], eax   <----------------------- Parametro hIcon
:01001488 call ds:LoadCursorW
:0100148E push ebx
:0100148F mov [ebp+WndClass.hCursor], eax   <--------------------- Parametro hCursor
:01001492 call ds:GetStockObject
:01001498 mov [ebp+WndClass.hbrBackground], eax   <--------------- Parametro hbrBackground
:0100149B lea eax, [ebp+WndClass]
:0100149E mov esi, offset AppName
:010014A3 push eax ; lpWndClass
:010014A4 mov [ebp+WndClass.lpszMenuName], edi   <---------------- Parametro lpszMenuName
:010014A7 mov [ebp+WndClass.lpszClassName], esi   <--------------- Parametro lpszClassName
:010014AA call ds:RegisterClassW   <------------------------------ RegisterClassW
 
Adesso che abbiamo trovato quello che stavamo cercando passiamo ad utilizzare Ollydbg. 
Carichiamo il programma, saltiamo direttamente alla window procedure (ctrl-g) e mettiamoci un breakpoint. Ricordandoci che stiamo cercando la parte della procedura che controlla se un item menu è stato premuto o meno, dobbiamo break-are se e solo se un menu item è stato click-ato. Quando un menu item viene click-ato, uno specifico messaggio WM_COMMAND viene mandato alla window procedure. Questo specifico messaggio contiene l'identificatore del menu nella loworder word del parametro wParam. Diamo un'occhiata alla prima parte della window procedure:
 
:0100180A PUSH EBP
:0100180B MOV EBP,ESP
:0100180D SUB ESP,40
:01001810 MOV ECX,DWORD PTR SS:[EBP+C]   <-- ecx è il messaggio ricevuto (ad esempio WM_COMMAND)
:01001813 PUSH EBX
 
Se vogliamo intercettare il click su di un menu item ci conviene settare un breakpoint condizionale (Shift+F2) all'indirizzo 1001810. La condizione che ho utilizzato è: 
[EBP+0Ch]==111h && [EBP+10h]==209h
dove '[EBP+0Ch]==111h' controlla che il messaggio sia un WM_COMMAND mentre '[EBP+10h]==209h' controlla se l'item 'Beginner' è stato click-ato. Come ho fatto a sapere che il valore 209h si riferisce proprio a quell'item? Usando un qualsiasi editor di risorse! Ovviamente nella condizione potete usare l'item che più preferite :-). 
Lanciate il programma e click-ate sull'item su cui avete definito la condizione di breakpoint, Olly si fermerà. Steppate un pochino finchè non raggiungerete il punto in cui il programma controlla quale menu item è stato premuto:
 
:010018B7 MOV EAX,111
:010018BC CMP ECX,EAX   <-------------------- E' stato ricevuto un WM_COMMAND?
:010018BE JA winmine.01001B3F
:010018C4 JE winmine.010019AD   <------------ Saltiamo se WM_COMMAND è stato ricevuto
...
:010019AD MOV ECX,DWORD PTR SS:[EBP+10]   <-- ecx è il wParam
:010019B0 MOVZX EAX,CX   <------------------- eax è l'identificatore dell'item 
:010019B3 CMP EAX,20B   <-------------------- E' stato click-ato l'item "Expert"?
:010019B8 JG SHORT winmine.01001A30
:010019BA CMP EAX,209   <-------------------- E' stato click-ato l'item "Beginner"?
:010019BF JGE SHORT winmine.010019EE
 
Questo pezzo di codice è quello che stavamo cercando; dato che dovremmo controllare se il nostro nuovo item è stato premuto, cambieremo il jump a 10018C4 in un nuovo jump, ovviamente al nostro nuovo codice. Nel nuovo codice ci comporteremo di conseguenza abilitando o disabilitando lo Special mode. Pazientate ancora un attimo, vedremo dopo come.
 
Troviamo il punto in cui il programma controlla se un messaggio WM_MOUSEMOVE è stato ricevuto
Questo passo è necessario perché da questo punto salteremo direttamente alla funzione della dll che ci mostrerà se sotto la cella c'è la bomba oppure no. Per trovare questo punto sfruttiamo il precedente breakpoint condizionale modificando la condizione che diventa: [EBP+0Ch]==200h
 
:01001B3F MOV EAX,DWORD PTR SS:[EBP+C] <-- Il messaggio ricevuto
:01001B42 MOV ESI,200 <------------------- 200h = WM_MOUSEMOVE
:01001B47 PUSH 1
:01001B49 XOR EDI,EDI
:01001B4B CMP EAX,ESI <------------------- E' stato ricevuto un WM_MOUSEMOVE?
:01001B4D POP EBX
:01001B4E JA SHORT winmine.01001BB4
:01001B50 JE winmine.01001DD5 <----------- Si, WM_MOUSEMOVE ricevuto
 
Steppando un pochino vedrete che questo messaggio non serve praticamente a niente... sembra proprio che sia stato messo solo per noi! Il punto da patch-are sarà quindi 1001B50. 
 
Come modificare MineSweeper
Prima di tutto dobbiamo decidere dove inserire il nuovo codice; ci sono molti spazi vuoti nel file, basta sceglierne uno. Inserirò il nuovo codice a partire dall'indirizzo 10049A5; il nuovo codice dovrà, fondamentalmente, aggiungere il nuovo menu item e chiamare una delle funzioni presenti nella nostra dll. Come abbiamo già preannunciato, per aggiungere il nuovo item al menu esistente useremo la funzione InsertMenuItem. Vediamo come è fatta questa funzione:
 
BOOL WINAPI InsertMenuItem(
    HMENU hMenu,           // Handle del menu nel quale inseriremo il nuovo item 
    UINT uItem,            // Identificatore o posizione nella quale andremo a mettere il nuovo item 
    BOOL fByPosition,      // FALSE se uItem è un identificatore, TRUE se è una posizione 
    LPMENUITEMINFO lpmii   // Puntatore alla struttura MENUITEMINFO che contiene informazioni sul nuovo item 
);
 
e:
 
typedef struct tagMENUITEMINFO { 
    UINT cbSize;             // Dimensione della struttura, in bytes 
    UINT fMask;              // Membri da settare o recuperare
    UINT fType;              // Tipo del menu item 
    UINT fState;             // Stato del menu item 
    UINT wID;                // Identificatore del menu item 
    HMENU hSubMenu;          // Handle del menu di tipo drop-down menu o del sub-menu associato al nuovo menu item
    HBITMAP hbmpChecked;     // Handle della bitmap da visualizzare quando il nuovo item sarà check-ato 
    HBITMAP hbmpUnchecked;   // Handle della bitmap da visualizzare quando il nuovo item sarà non check-ato 
    DWORD dwItemData;        // "Application-defined value associated with the menu item"
    LPTSTR dwTypeData;       // Contenuto del menu item 
    UINT cch;                // Lunghezza del testo relativo al nuovo menu item 
} MENUITEMINFO, FAR *LPMENUITEMINFO;
 
Troppi parametri? Tranquilli, molti saranno messi a NULL :p. 
C'è un problemino, la funzione InsertMenuItem non è importata dal programma originale per cui dovremmo utilizzare la combinazione delle funzioni LoadLibrary/GetProcAddress per chiamarla.
 
- LoadLibrary: è la prima delle due funzioni da usare e mappa la dll nello spazio del processo chiamante. La funzione ritorna un handle che verrà usato dall'altra funzione, GetProcAddress, per avere l'indirizzo della funzione interna alla dll.
 
HINSTANCE LoadLibrary( 
    LPCTSTR lpLibFileName   // Stringa contenente il nome della dll 
);
 
- GetProcAddress: ritorna l'indirizzo della funzione interna alla dll.
 
FARPROC GetProcAddress( 
    HMODULE hModule,    // Handle della DLL (valore ritornato dalla LoadLibrary) 
    LPCSTR lpProcName   // Stringa contenente il nome della funzione 
);
 
Usare queste due funzioni è molto semplice. Io non aggiungerei altro, passiamo direttamente a vedere il codice da inject-are in Minesweeper:

:010049A5 ; Indirizzo iniziale della *cava* scelta 
:010049A5 dword_10049A5 dd 0   ; specialMode=08h se la nuova modalità è enable, specialMode=00h altrimenti
:010049A6 aZaiwinmine_dll db 'zaiWinmine.dll',0   ; Il nome della DLL
:010049B5 aUser32_dll     db 'User32.dll',0       ; Ci serve User32.dll perchè contiene InsertMenuItem

; Il nome delle due nuove funzioni
:010049C0 aChangeitemstat db 'changeItemStatus',0   ; Cambia lo stato dell'item 'Special Mode'
:010049D1 aRevealcell     db 'revealCell',0         ; Rivela (se c'è) la bomba sotto la cella
 
:010049DC dword_10049DC dd 0   ; Memorizza l'indirizzo della funzione changeItemStatus
:010049E0 dword_10049E0 dd 0   ; Memorizza l'indirizzo della funzione revealCell
:010049E4 aInsertmenuitem db 'InsertMenuItemA',0   ; La funzione che aggiunge il nuovo item
:010049F4 aSpecialMode    db 'Special Mode',0      ; Testo da inserire nel nuovo item

; Struttura MENUITEMINFO
:01004A01 dd 2Ch        ; Dimensione della struttura: 11 dwords = 2Ch bytes
:01004A05 dd 42h        ; MIIM_ID + MIIM_STRING
:01004A09 dd 0          ; MFT_STRING, visualizza il menu item usando del testo
:01004A0D dd 0          ; Stato di default
:01004A11 dd 212h       ; Id del nuovo item
:01004A15 dd 0          ; NULL perché l'item non apre un drop-down menu e nemmeno un submenu
:01004A19 dd 0          ; NULL, check mark di default (la classica 'v')
:01004A1D dd 0          ; NULL
:01004A21 dd 0          ; NULL
:01004A25 dd 10049F4h   ; Puntatore alla stringa che definisce il testo del nuovo item
:01004A29 dd 4          ; Dimensione della stringa precedente 

:01004A2D ; Arrivo qui da 10014E8: aggiungiamo il nuovo item e salviamo l'indirizzo di changeItemStatus e revealCell, le due funzioni presenti nella nostra dll
:01004A2D push offset loc_10014ED       ; Indirizzo di ritorno
:01004A32 pusha
:01004A33 push offset aUser32_dll       ; "User32.dll"
:01004A38 call ds:LoadLibraryA          ; Mappa User32.dll
:01004A3E push offset aInsertmenuitem   ; "InsertMenuItemA"
:01004A43 push eax                      ; Handle della dll
:01004A44 call ds:GetProcAddress        ; Ritorna l'indirizzo della funzione InsertMenuItemA
:01004A4A mov ebx, offset hMenu
:01004A4F push offset dword_1004A01   ; Puntatore alla struttura MENUITEMINFO
:01004A54 push 0                      ; FALSE
:01004A56 push 0                      ; La posizione dell'item
:01004A58 push dword ptr [ebx]        ; Handle del menu 
:01004A5A call eax                    ; Call InsertMenuItemA
:01004A5C push offset aZaiwinmine_dll   ; "zaiWinmine.dll", il nome della dll 
:01004A61 call ds:LoadLibraryA
:01004A67 mov esi, eax
:01004A69 push offset aChangeitemstat   ; "changeItemStatus"
:01004A6E push esi
:01004A6F call ds:GetProcAddress
:01004A75 mov ds:dword_10049DC, eax   ; Memorizza l'indirizzo di changeItemStatus
:01004A7A push offset aRevealcell     ; "revealCell"
:01004A7F push esi
:01004A80 call ds:GetProcAddress
:01004A86 mov ds:dword_10049E0, eax   ; Memorizza l'indirizzo di revealCell
:01004A8B popa
:01004A8C mov eax, dword_10052A4
:01004A91 retn   ; Torna al codice originale

:01004A92 ; Arrivo qui se lo 'specialMode' viene click-ato (10018C4). Chiamo changeItemStatus 
:01004A92 push offset loc_10019AD   ; Indirizzo di ritorno
:01004A97 pusha
:01004A98 call ds:dword_10049DC     ; Call changeItemStatus
:01004A9E jmp short loc_1004AB6

:01004AA0 ; Arrivo qui se ricevo un messaggio WM_MOUSEMOVE (1001B50). Chiamo revealCell se e solo se la nuova modalità è abilitata
:01004AA0 push offset loc_1001DD5   ; Indirizzo di ritorno
:01004AA5 cmp ds:byte_10049A5, 0    ; Special Mode abilitato?
:01004AAC jnz short loc_1004AAF     ; Si, salta sotto
:01004AAE retn                      ; Special Mode disabilitato, nessuna bomba da mostrare, torna al codice originale
:01004AAF pusha
:01004AB0 call ds:dword_10049E0     ; Call revealCell, mostra una eventuale bomba
:01004AB6 popa
:01004AB7 retn                      ; Torna al codice originale

Non credo ci sia bisogno di ulteriori commenti.
 
Restano ancora da applicare delle piccolissime modifiche alle istruzioni agli indirizzi 10014E8, 10018C4 e 1001B50 perché da li dobbiamo arrivare al codice qua sopra, ecco le modifiche:

:010014E8 jmp loc_1004A2D
:010018C4 jz loc_1004A92
:01001B50 jz loc_1004AA0
 
Le funzioni della DLL
Fino ad ora abbiamo visto tutte le modifiche da apportare al programma, da adesso in poi vedremo come implementare le due funzioni presenti nella nostra dll:
- changeItemStatus, questa funzione esegue il check/uncheck del nuovo menu item abilitando o disabilitando la nuova modalità.
- revealCell, questa funzione rivela (se c'è) la bomba sotto la cella.
 
changeItemStatus
Per fare il check/uncheck del nuovo item userò la funzione CheckMenuItem:
 
DWORD CheckMenuItem( 
    HMENU hmenu,         // Handle del menu
    UINT uIDCheckItem,   // Item menu da check-are/uncheck-are 
    UINT uCheck          // Menu item flags 
); 
 
Una funzione facile facile da usare che necessita di:
1. l'handle del menu. Noi non abbiamo l'handle ma dato che la funzione (CheckMenuItem) è già importata ed alcuni degli item menu presenti possono essere check-ati/uncheck-ati, la cosa più semplice da fare è quella di mettere un brekpoint sulla funzione e click-are su uno di questi item, per esempio 'Sound'. Perfetto, Olly ferma tutto mostrandoci dove è memorizzato l'handle.
2. l'id del menu. E' il numero che abbiamo associato al nostro nuovo item.
3. siamo interessati a uno di questi due valori:
MF_CHECKED (08h): setta l'item come check-ato
MF_UNCHECKED (00h): setta l'item come uncheck-ato
 
Ho scritto la dll in asm ma nessuno vi vieta di scriverla in qualsiasi altro linguaggio. Ecco qui la prima funzione nel dettaglio:

; Cambia lo stato del nuovo item
changeItemStatus proc
   .IF word ptr [ebp+10h] == 212h   ; 212h è l'id che ho usato per il nuovo item
      mov ebx, 10049A2h             ; byte [10049A2] memorizza lo stato dell'item: checked oppure unchecked
      xor byte ptr [ebx], 08h
      movzx eax, byte ptr [ebx]
      mov ebx, 10052BCh             ; Indirizzo dove viene memorizzato l'handle del menu
      invoke CheckMenuItem, dword ptr [ebx], 212h, eax
   .ENDIF
   ret 
changeItemStatus endp

L'unica cosa che richiede una spiegazione è il valore memorizzato all'indirizzo 10049A2. Questo byte assume due valori:
00h se l'item è non check-ato
08h se l'item è check-ato
Perché non ho usato 00h e 01h? Questo perché così facendo utilizzo lo stesso indirizzo per memorizzare sia lo stato dell'item che il menu item flag.
 
revealCell
Prima di scrivere questa seconda funzione dobbiamo capire come e dove vengono memorizzate le bombe.
Per far ciò cerchiamo una strada per break-are quando click-iamo su una qualsiasi cella; sfruttando il lavoro che abbiamo fatto fino ad ora userò ancora un breakpoint condizionale all'indirizzo 1001810; l'idea è quella di break-are quando il tasto sinistro del mouse viene premuto e poi rilasciato.  Quando questo avviene, un messaggio WM_LBUTTONUP (202h) viene generato per cui la condizione da settare sarà: [EBP+0Ch] == 202h
Settiamo questo nuovo breakpoint e click-iamo su una qualsiasi cella; Olly fermerà il tutto, steppate un pochino finchè Olly non vi dirà di fermarvi:
 
01001C30 XOR EDI,EDI                                 ; Cases 202 (WM_LBUTTONUP) <-- Grazie Olly :-D
01001C32 CMP DWORD PTR DS:[1005160],EDI
01001C38 JE winmine.01001D4C
01001C3E MOV DWORD PTR DS:[1005160],EDI
01001C44 CALL DWORD PTR DS:[<&USER32.ReleaseCaptu>   ; ReleaseCapture
01001C4A TEST BYTE PTR DS:[1005010],BL
01001C50 JE SHORT winmine.01001C5C
01001C52 CALL winmine.0100373E                       ; Hmmm...
01001C57 JMP winmine.01001D4C                        ; Salta alla fine della procedura
 
Ecco qua che cosa succede quando viene ricevuto questo messaggio; c'è soltanto una chiamata a ReleaseCapture, un salto alla fine della procedura e una call molto sospetta. Forse è la call che ci mostra dove sono nascoste le bombe. Entriamo nella call e diamogli un'occhiata:
 
010037F2 MOV EDX,ECX   ; ecx memorizza il numero della riga relativa alla cella click-ata, eax memorizza la colonna
010037F4 SHL EDX,5
010037F7 MOV DL,BYTE PTR DS:[EDX+EAX+1005700]   ; dl = valore nascosto dentro la cella clickata
 
Come potete vedere le celle presenti nella prima riga della griglia sono memorizzate a partire da 1005721 e terminano con il doppio valore 0x10. In pratica le celle presenti nella griglia sono memorizzate sequenzialmente riga per riga; due righe sono divise da alcuni caratteri particolari. Non conviene perdere tempo nel cercare di identificare tutti i caratteri usati per la memorizzazione dei vari valori associati ad ogni cella perchè facendo alcune prove capirete facilmente qual'è il valore associato ad una bomba, è 0x8F. 
Adesso che sappiamo praticamente tutto del programma vediamo se riusciamo a mostrare queste maledette bombe. Ogni bomba verrà mostrata quando il mouse passerà sopra una cella contenente la bomba e, le bombe rimarranno mostrate fino alla fine del gioco. 
Vediamo come è fatta l'altra funzione della dll, quella che ho chiamato revealCell. Che cosa dovrà fare questa funzione? Ricordandoci che la funzione sarà chiamata ad ogni movimento del mouse (e supponendo che la modalità Special Mode sia attiva) abbiamo questo schema da seguire:
 
1. Il punto click-ato è interno alla griglia?
   No: esci dalla funzione
   Si: salta al punto 2
2. Sotto la cella c'è una bomba?
   No: esci dalla funzione 
   Si: rivela la bomba
 
Il punto 1 è necessario perché una bomba non può essere al di fuori della griglia. La prima cella (angolo in alto a sinistra) ha come coordinate (0x0C, 0x37) e ogni cella ha dimensioni (0x10, 0x10). Sapendo queste informazioni possiamo facilmente implementare il punto 1: un semplice 'if' basato sulle coordinate del punto premuto con il mouse. Come facciamo a sapere le coordinate x e y relative al punto in cui abbiamo premuto? Ce lo dirà WM_MOUSEMOVE; infatti:
 
WM_MOUSEMOVE 
    fwKeys = wParam;         // Key flags 
    xPos = LOWORD(lParam);   // Coordinata x del cursore 
    yPos = HIWORD(lParam);   // Coordinata y del cursore
 
Per quanto riguarda l'implementazione del punto numero 2 dobbiamo, prima di tutto, controllare se la cella nasconde una bomba oppure no; questo non è assolutamente un problema perché sappiamo dove vengono memorizzate le bombe e possiamo facilmente implementare il controllo ma... come facciamo a mostrare la bomba? La risposta è dentro la libreria Graphics Device Interface (GDI); in questo caso specifico la useremo per mostrare una bitmap. La mia nuova versione di Minesweeper mostra l'immagine della bomba su sfondo rosso (la puoi vedere aprendo il programma con un editor di risorse: "Bitmap-410-1033"). Ovviamente potete mostrare la bitmap che più vi piace. Per mostrare la bitmap useremo queste funzioni: GetDC, BitBlt, ReleaseDC.
 
GetDC: ritrona l'handle del display Device Context (DC) per l'area della finestra specificata.
 
HDC GetDC( 
    HWND hWnd   // Handle della finestra 
); 
 
Questa è la prima funzione da chiamare. Come facciamo a sapere l'handle della finestra? Semplice, sia questa che le altre funzioni sono usate dal Minesweeper per cui, come abbiamo fatto prima, ci conviene *spiare* come vengono usate dal programma per capire come reperire tutti i vari parametri mancanti. Ad esempio, per trovare l'handle della finestra mettiamo un breakpoint sulla funzione GetDC e lanciamo di nuovo Minesweeper; Olly si fermerà e tutto quello che dovremmo fare sarà... prendere nota dell'indirizzo che contiene l'handle.
 
BitBlt: trasferisce una sezione rettangolare di una immagine da un DC sorgente ad un DC destinazione.
 
BOOL BitBlt( 
    HDC hdcDest,   // Handle del DC destinazione, il valore ritornato dalla funzione GetDC 
    int nXDest,    // Coordinata x del rettangolo di destinazione. La x è relativa all'angolo in alto a sinistra 
    int nYDest,    // Coordinata y del rettangolo di destinazione. La y è relativa all'angolo in alto a sinistra 
    int nWidth,    // Larghezza del rettangolo di destinazione 
    int nHeight,   // Altezza del rettangolo di destinazione 
    HDC hdcSrc,    // Handle del DC origine 
    int nXSrc,     // Coordinata x del rettangolo di origine. La x è relativa all'angolo in alto a sinistra 
    int nYSrc,     // Coordinata y del rettangolo di origine. La y è relativa all'angolo in alto a destra 
    DWORD dwRop    // Modalità di rastering 
);
 
Semplice, la funzione è molto intuitiva, dovete solo dirle cosa volete visualizzare e dove volete visualizzare l'immagine. Se sentite la necessità di visualizzare un'altra immagine dovrete cambiare il 6° parametro: hdcSrc. 
 
ReleaseDC: rilascia un device context.
 
int ReleaseDC( 
    HWND hWnd,   // Handle della finestra 
    HDC hDC      // Handle del device context 
); 
 
Ok, è arrivato finalmente il momento di vedere come cacchio è fatta questa funzione revealCell:

.data 
oldRow    db "0100499D"
oldColumn db "010049A1" 

; Visualizza la bomba sotto la cella
revealCell proc
   pushad
   
   ; Il click è avvenuto dentro la griglia?
  
.IF word ptr [ebp+14h] < 0Ch || word ptr [ebp+16h] < 37h
      jmp @exitRevealCell   ; No, esci
   .endif

   xor eax, eax
   mov ax, word ptr [ebp+14h]   ; La coordinata x del punto in cui è avvenuto il click
   sub eax, 0Ch
   shr eax, 4
   add eax, 1                   ; eax è il numero della colonna in cui è avvenuto il click
   xor ebx, ebx
   mov bx, word ptr [ebp+16h]   ; La coordinata y del punto in cui è avvenuto il click
   sub ebx, 37h
   shr ebx, 4
   add ebx, 1                   ; ebx è il numero della riga in cui è avvenuto il click

   mov edi, 100499Dh
   .IF ax == word ptr [edi] && bx == word ptr [edi+4]
      ; Il cursore del mouse è rimasto nella solita cella, non c'è bisogno di ri-visualizzare l'immagine
      jmp @exitRevealCell 
   .ENDIF

   mov ecx, ebx
   mov edx, ecx
   shl edx, 5
   mov ebx, 1005700h
   add ebx, eax
   add ebx,edx   ; ebx punta al byte contenente le informazioni della cella su cui abbiamo clickato  
   ; La cella viene scoperta se e solo se contiene la bomba
   .IF byte ptr [ebx] == 8Fh
      mov edi, 100499Dh
      mov dword ptr [edi], eax     ; Salvo la colonna della cella
      mov dword ptr [edi+4], ecx   ; Salvo la riga della cella
      push eax
      push ecx
      mov esi, 10052A8h   ; Puntatore all'handle della finestra
      push [esi]          ; Handle della finestra
      call GetDC
      mov esi, eax
      pop ecx
      pop eax
      shl ecx, 4
      add ecx, 27h
      shl eax, 4
      sub eax, 4
      mov ebx, 1005AE0h
      push 0CC0020h   ; SRCCOPY flag
      push 0
      push 0
      push [ebx+48]   ; DC sorgente, l'immagine da visualizzare
      push 10h        ; Altezza del rettangolo
      push 10h        ; Larghezza del rettangolo
      push ecx        ; Coordinata y
      push eax        ; Coordinata x
      push esi        ; Handle del DC destinazione
      call BitBlt
      mov ebx, 10052A8h
      push esi               ; Handle del DC da rilasciare
      push dword ptr [ebx]   ; Handle della finestra
      call ReleaseDC
   .ELSE
      mov dword ptr [edi], 0
      mov dword ptr [edi+4], 0
   .ENDIF

@exitRevealCell:
   popad
   ret
revealCell endp

Tutto molto semplice :D
 
Il tutorial finisce qua, adesso tutto quello che devi fare è scrivere il loader che lancerà il programma e gli aggiungerà tutto il nuovo codice a runtime. Dato che ci sono già abbastanza tutorial sull'argomento non vi dirò come si fa a scrivere un loader; il sorgente del loader che ho messo nell'attachment sarà più che sufficiente per farvi capire come funziona. 
 
Postambolo: conclusioni
Giocando con questa nuova versione del gioco ho notato una cosa molto strana; abilita la modalità 'Special mode' e click-a sopra una bomba. Non succede niente... anzi, la bomba è sparita completamente!?! Abbiamo sbagliato qualcosa? Lol, no! Dove sarà finita questa bomba? (Hint: è una caratteristica specifica del gioco. Good luck! :-))
 
ZaiRoN         

Note finali

Saluto tutti gli amici di #crack-it!
bona...

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly. <-- che fissa!!!