Zoom Icon

Lezione2 xspike solution

From UIC Archive

Soluzione Seconda Lezione Newbies 2009

Contents


Lezione2 xspike solution
Author: xspike
Email: mandatemi un PM sul forum
Website: http://xspike.altervista.org/
Date: 24/09/2008 (dd/mm/yyyy)
Level: Very Easy, if you can read this, you can do it
Language: Italian Flag Italian.gif
Comments: E' il mio primo tut sulla UIC, speriamo sia chiaro ;)



Introduzione

Quando sul forum si cominciò a parlare di queste lezioni mi ero ripromesso che sarabbe stata l'occasione buona per dare il mio contributo... Beh ora ci siamo ;) Spero di aver fatto un buon lavoro e sopratutto di aver dato una degna soluzione a questa lezione.


Essay

Cenni fondamentali su DialogBoxParam() e DialogProc()

Per prima cosa corriamo sulla msdn e vediamo a cosa servono queste due funzioni! Scopriamo che il compito della API DialogBoxParam() è quello di creare un dialog modale a partire da un modello definito tramite un file di risorse con estensione .RC (oppure .RES se già compilato) che si può ottenere con un qualsiasi editor di risorse; guardiamo subito com’è definita questa funzione:

INT_PTR DialogBoxParam(

   HINSTANCE hInstance,
   LPCTSTR lpTemplateName,
   HWND hWndParent,
   DLGPROC lpDialogFunc,
   LPARAM dwInitParam

);

Una volta compreso che un dialog modale viene generato tramite questa funzione non ci resta che porre l’attenzione sul parametro DLGPROC lpDialogFunc che è un puntatore a funzione. DialogBoxParam() ha il compito di “disegnare” a schermo un dialog ma non interagisce con i controlli del form, quindi ad esempio pensiamo al tasto X in alto a destra sulla caption di ogni dialog (parliamo sempre di quelli modali!). Cliccandoci sopra ci aspetteremmo che il dialog si chiuda, ed invece senza definire una corretta lpDialogFunc non verrebbe associato il click su quel tasto all’intenzione di chiudere il dialog. Cerchiamo di spiegare meglio questo concetto introducendo anche la funzione DialogProc(), ecco la sua sintassi:

INT_PTR CALLBACK DialogProc(

   HWND hwndDlg,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam

);

Questa funzione può essere considerata come il cuore di ogni dialog (modale e non) in quanto ha l’importante compito di gestire i messaggi che i vari controlli del form inviano. Spieghiamoci meglio! Riprendiamo l’esempio di prima, quando si esegue un click con il tasto sinistro del mouse sul tasto X di un dialog esso genera un messaggio WM_CLOSE (che è definito in Winuser.h come costante avente valore 0x0010). Il compito di DialogProc(), in questo caso, è chiamare EndDialog() che chiuderà il dialog. Così facendo abbiamo gestito correttamente il messaggio WM_CLOSE. E’ chiaro quindi che una DialogProc() viene associata ad un dialog inizializzando opportunamente il paramento lpDialogFunc della funzione DialogBoxParam(), in altre parole lpDialogFunc contiene l’indirizzo della DialogProc() che gestisce i messaggi di un form. Esistono molti altri messaggi che spiegano l’esistenza degli altri tre parametri di DialogProc(), HWND hwndDlg conterrà l’handle del dialog che ha inviato il messaggio, WPARAM wParam ed LPARAM lParam invece vengono utilizzati per passare informazioni aggiuntive al messaggio. Immaginate il form della calcolatrice di windows, tutti i tasti se cliccati generano sempre lo stesso messaggio, la DialogProc() quando si trova a gestire WM_COMMAND ha bisogno di sapere quale bottone e’ stato premuto e questa informazione si ottiene tramite questi ultimi due parametri. E’ quindi doveroso aspettarsi che la MessageBox alla quale dobbiamo arrivare per svolgere i nostri homework sia “generata” all’interno della DialogProc() passata alla DialogBoxParam().

Pseudo codice DialogProc() utilizzato per lezione2.exe

Ora che, a grandi linee, abbiamo compreso queste due funzioni cerchiamo di capire tramite un esempio come potrebbe essere scritta la DialogProc(). Rimandiamo a dopo le spiegazioni ed iniziamo a guardare lo pseudo codice:

BOOL CALLBACK unaDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { ...... switch (uMsg) { case WM_CLOSE: //messaggio generato premendo il tasto X CHIUDI_DIALOG(); break;

case WM_INITDIALOG: //messaggio che si genera subito prima che venga //visualizzato a schermo il dialog QUALCOSA(); break;

case WM_COMMAND: //messaggio che si genera ad esempio quando //clicchiamo sul tasto OK posizionato sul form alla //destra del textedit IDENTIFICA_CONTROLLO_CHIAMANTE(); ESEGUI_OPERAZIONE(); break;

default: //se uMsg non è nessuno dei precedenti messaggi NON_FARE_NIENTE(); //inutile aggiungere dettagli di programmazione break; } }

In questo listato ho gestito solo questi messaggi perché steppando l’allegato della lezione questi sono gli unici casi che vengono considerati. Anche per i meno pratici del C/C++ dovrebbe essere evidente che tutta la DialogProc() “ruota” intorno al valore di uMsg che di volta in volta contiene il messaggio invocato dai controlli del form in esame. Per evitare di spiattellare subito la soluzione della lezione a questo punto spieghiamo solo il “blocco” default ! Come detto in precedenza esistono molti messaggi che i controlli possono inviare, non è detto però che al nostro programma importi gestirli tutti, ad esempio se esaminiamo il form del file allegato alla lezione scopriamo che cliccando con il tasto destro del mouse sul form (non sulla caption!) non succede nulla. Questo accade perché la DialogProc() dell’allegato non gestisce il messaggio WM_RBUTTONDOWN. In queste occasioni entra in gioco il “blocco” default che ignora il messaggio. Tutti gli altri casi credo siano sufficientemente chiari leggendo i commenti.

Steppiamo fino alla prima MessageBox brekkando su DialogBoxParam()

Finalmente si steppa !! Per cominciare iniziamo a seguire le regole ! Apriamo Olly ed andiamo ad aprire l’allegato della lezione. Dopo qualche secondo di attesa nella window cpu vedremo il codice da analizzare e l’esecuzione del file sarà in stato “Paused”. Questo è il momento per piazzare un breakpoint su DialogBoxParam(), per far ciò assicuriamoci di aver aperto la commandbar di olly e digitiamo il comando bpx DialogBoxParamA. Vedremo apparire la window “Intermodular calls” che sostanzialmente ci mostra un breakpoint all’indirizzo 004010C5, proprio sulla chiamata a DialogBoxParam(), possiamo verificarlo anche premendo alt+b che ci mostrerà la window “Breakpoints”. Torniamo alla finestra cpu, ora siamo pronti a mandare in esecuzione il nostro allegato, premiamo F9. L’esecuzione passa qualche istante allo stato di “Running” per poi tornare molto rapidamente allo stato “Paused”, come mai ? Beh siamo arrivati al breakpoint che abbiamo impostato su DialogBoxParam() ! L’esecuzione si ferma prima di eseguire DialogBoxParamA. Se scorriamo un po’ più su il codice possiamo vedere come vengono passati i parametri alla funzione prima che venga invocata, riprendendo la dichiarazione descritta all’inizio di questo tutorial possiamo intuire quale possa essere il nostro prossimo passo, guardiamo il codice:

004010B0 /$ 8B4424 04 MOV EAX,SS:[ESP+4] 004010B4 |. 6A 00 PUSH 0  ; /lParam = NULL 004010B6 |. 68 00104000 PUSH 00401000  ; |DlgProc = lezione2.00401000 004010BB |. 6A 00 PUSH 0  ; |hOwner = NULL 004010BD |. 6A 6A PUSH 6A  ; |pTemplate = 6A 004010BF |. 50 PUSH EAX  ; |hInst 004010C0 |. A3 B0B64000 MOV DS:[40B6B0],EAX  ; | 004010C5 |. FF15 E8804000 CALL DS:[<&USER32.DialogBoxParamA>]  ; \DialogBoxParamA

Se vi state chiedendo perché su olly vedete i parametri di DialogBoxParam() “invertiti”, o per meglio dire pushati sullo stack in ordine inverso rispetto alla sintassi descritta in precedenza in questo tutorial saprete solo che è una convenzione, i parametri vengono pushati sullo stack “da destra a sinistra”, per saperne di più questo può essere un buon inizio. Fatta questa precisazione torniamo all’analisi dell’allegato, siamo nel punto in cui sta per essere disegnata la finestra. Facciamo il punto della situazione, noi dobbiamo arrivare a tracciare la chiamata alla prima MessageBox, mettiamo da parte un secondo il debugger e concentriamoci sull’allegato. Se lo eseguiamo normalmente notiamo una cosa MOLTO importante; la nostra MessageBox obbiettivo viene visualizzata PRIMA che venga mostrato il dialog, quest’ultimo infatti si visualizza a schermo solo dopo aver cliccato il tasto ok della MessageBox. Questa per noi è un’informazione importantissima! Torniamo al paragrafo 2 ed osserviamo i commenti nel “blocco” WM_INITDIALOG… Tombola, cominciamo a capire dove e quando questa benedetta MessageBox viene chiamata… Sicuramente nella DialogProc() quando deve gestire il messaggio WM_INITDIALOG. Non mi credete ? Adesso lo andiamo a verificare! Torniamo in Olly e digitiamo nella commandbar bp 00401000, mettiamo un breakpoint proprio a questo indirizzo perché è il valore che viene passato al paramentro lpDialogFunc, a questo indirizzo troveremo la DialogProc() che gestisce WM_INITDIALOG (se non vi è chiaro il perché di questo passaggio rileggete il capito 1). Diamo una rapida ma attenta occhiata al listato ed ai commenti, quando Olly brekka ci troviamo qui:

00401000 . 83EC 2C SUB ESP,2C 00401003 . A1 10A04000 MOV EAX,DS:[40A010] 00401008 . 33C4 XOR EAX,ESP 0040100A . 894424 28 MOV SS:[ESP+28],EAX 0040100E . 8B4424 34 MOV EAX,SS:[ESP+34]  ;[ESP+34] contiene il messaggio da gestire 00401012 . 83E8 10 SUB EAX,10  ; Switch (cases 10..111) 00401015 . 8B4C24 30 MOV ECX,SS:[ESP+30] 00401019 . 74 79 JE SHORT 00401094 0040101B . 2D 00010000 SUB EAX,100 00401020 . 74 4B JE SHORT 0040106D 00401022 . 83E8 01 SUB EAX,1 00401025 . 75 76 JNZ SHORT 0040109D 00401027 . B8 E8030000 MOV EAX,3E8  ; Case 111 (WM_COMMAND) 0040102C . 66:394424 38 CMP SS:[ESP+38],AX 00401031 . 75 6A JNZ SHORT 0040109D 00401033 . 6A 14 PUSH 14  ; /Count = 14 (20.) 00401035 . 8D5424 04 LEA EDX,SS:[ESP+4]  ; | 00401039 . 52 PUSH EDX  ; |Buffer 0040103A . 68 E9030000 PUSH 3E9  ; |ControlID = 3E9 (1001.) 0040103F . 51 PUSH ECX  ; |hWnd 00401040 . FF15 EC804000 CALL DS:[<&USER32.GetDlgItemTextW>]  ; \GetDlgItemTextW 00401046 . 6A 00 PUSH 0  ; /Style = MB_OK|MB_APPLMODAL 00401048 . 68 3C814000 PUSH 0040813C  ; |Title = "Caption" 0040104D . 8D4424 08 LEA EAX,SS:[ESP+8]  ; | 00401051 . 50 PUSH EAX  ; |Text 00401052 . 6A 00 PUSH 0  ; |hOwner = NULL 00401054 . FF15 F0804000 CALL DS:[<&USER32.MessageBoxW>]  ; \MessageBoxW 0040105A . 33C0 XOR EAX,EAX 0040105C . 8B4C24 28 MOV ECX,SS:[ESP+28] 00401060 . 33CC XOR ECX,ESP 00401062 . E8 69000000 CALL 004010D0 00401067 . 83C4 2C ADD ESP,2C 0040106A . C2 1000 RETN 10 0040106D > 6A 00 PUSH 0  ; Case 110 (WM_INITDIALOG) 0040106F . 68 30814000 PUSH 00408130  ; |Title = "InitDialog" 00401074 . 68 00A04000 PUSH 0040A000  ; |Text = "breakpointMe" 00401079 . 6A 00 PUSH 0  ; |hOwner = NULL 0040107B . FF15 F4804000 CALL DS:[<&USER32.MessageBoxA>]  ; \MessageBoxA 00401081 . 33C0 XOR EAX,EAX 00401083 . 8B4C24 28 MOV ECX,SS:[ESP+28] 00401087 . 33CC XOR ECX,ESP 00401089 . E8 42000000 CALL 004010D0 0040108E . 83C4 2C ADD ESP,2C 00401091 . C2 1000 RETN 10 00401094 > 6A 00 PUSH 0  ; Case 10 (WM_CLOSE) 00401096 . 51 PUSH ECX  ; |hWnd 00401097 . FF15 F8804000 CALL DS:[<&USER32.EndDialog>]  ; \EndDialog 0040109D > 8B4C24 28 MOV ECX,SS:[ESP+28]  ; Default case of switch 00401012 004010A1 . 33CC XOR ECX,ESP 004010A3 . 33C0 XOR EAX,EAX 004010A5 . E8 26000000 CALL 004010D0 004010AA . 83C4 2C ADD ESP,2C 004010AD . C2 1000 RETN 10

Avete già visto la nostra MessageBox ? Non importa, tanto continuiamo a tracciare comunque :P Grazie ai commenti di Olly identifichiamo subito il costrutto switch-case usato per determinare il flusso della DialogProc() in funzione dei messaggi (se vi siete persi rileggete il capitolo 2). Notiamo che, come supposto nel capitolo 2, vengono gestiti solo WM_INITDIALOG, WM_CLOSE, WM_COMMAND rispettivamente agli indirizzi 0040106D, 00401094, 00401027. Prima di proseguire a descrivere la procedura è bene spendere due parole per vedere come sono dichiarati in winuser.h questi messaggi:

  1. define WM_INITDIALOG 0x0110
  2. define WM_CLOSE 0x0010
  3. define WM_COMMAND 0x0111

Ora che sappiamo come sono dichiarati i messaggi sapremo di volta in volta cosa aspettarci in EAX dopo aver steppato oltre l’istruzione MOV EAX,SS:[ESP+34] all’indirizzo 0040100E. Concentriamoci sul seguente codice estratto dal precedente listato:

0040100E . 8B4424 34 MOV EAX,SS:[ESP+34]  ; [ESP+34] indirizzo di UINT uMsg 00401012 . 83E8 10 SUB EAX,10  ; EAX == WM_CLOSE ? 00401015 . 8B4C24 30 MOV ECX,SS:[ESP+30] 00401019 . 74 79 JE SHORT 00401094 0040101B . 2D 00010000 SUB EAX,100  ; EAX == WM_INITDIALOG ? 00401020 . 74 4B JE SHORT 0040106D 00401022 . 83E8 01 SUB EAX,1  ; EAX == WM_COMMAND ? 00401025 . 75 76 JNZ SHORT 0040109D 00401027 . B8 E8030000 MOV EAX,3E8  ; Case 111 (WM_COMMAND) La logica usata per tradurre la struttura dello switch-case è abbastanza semplice, se i messaggi che desideriamo gestire, in questo caso, sono codificati come 10h, 110h e 111h la routine copia il messaggio in EAX e inizia a verificare quale messaggio è arrivato in ordine crescente. Come fa ? Sfrutta le proprietà dell’addizione, WM_INITDIALOG (110h) e WM_COMMAND (111h) possono scomporsi rispettivamente come 10 + 100 e 10 + 100 + 1 quindi la routine inizia sottraendo 10h, se il risultato è uguale a 0 viene automaticamente settato a 1 lo Zero Flag ed il JE all’istruzione successiva porta il flusso d’esecuzione direttamente all’indirizzo 00401094 dove troviamo le due push che passano i parametri alla chiamata di EndDialog() per chiudere il dialog. Se invece la SUB EAX, 10h non setta lo Zero Flag allora l’esecuzione procede senza salti e la routine va subito a controllare se il messaggio ricevuto è WM_INITDIALOG (110h) sottraendo al risultato della precedente sottrazione 100h. A questo punto il gioco si ripete poiché 110h può essere scomposto in 10h + 100h, se originariamente EAX conteneva 110h, cioè WM_INITDIALOG, sottraendo 100h dopo aver già sottratto 10h siamo in grado di stabilire con certezza se il messaggio caricato in EAX è o no WM_INITDIALOG. Se il risultato di quest’operazione è uguale a 0 il JE subito sotto farà saltare l’esecuzione all’indirizzo 0040106D dove vedremo pushare sullo stack tutti i parametri necessari per invocare MessageBox(), ecco come si arriva alla nostra MessageBox() ! Per verificare se il messaggio sia WM_COMMAND il gioco è sempre il medesimo ma c’è una differenza, se il risultato della sub non sarà uguale a 0 vuol dire che la DialogProc() è stata chiamata a gestire un messaggio che al programmatore non interessa gestire e quindi un JNE ci fa saltare al default-case dello switch (riguardatelo al capitolo 2). Fate un po’ di prove steppando con F8 e tenendo sotto controllo i valori dei registri ed i risultati delle sub. Possiamo “giocare” con dei breakpoint condizionali per vedere di volta in volta come vengono gestiti i messaggi che “provochiamo” al dialog. Iniziamo a guardare WM_RBUTTONDOWN (204h controllate in winuser.h), all’indirizzo 0040100E premiamo shift+f2 (bp condizionale) e scriviamo la condizione [esp+34] == 204h e clicchiamo su Ok. La DialogProc() che analizziamo non gestisce questo messaggio, per provarlo premiamo F9 in Olly (in questo modo il programma torna in esecuzione) e clicchiamo con il tasto destro sul form (non sulla caption, textedit, bottone ok). Improvvisamente Olly brekka, se continuate a tracciare con F8 vedrete che verranno eseguite tutte e 3 le sub con esito negativo e verrete quindi riportati al default-case. Per concludere proviamo con WM_CLOSE (10h), ripetiamo la procedura di prima cambiando però la condizione del bp, scriviamo [esp+34] == 10h , premuto F9 clicchiamo sulla X rossa della caption, Olly brekka e continuando a tracciare arriverete alla chiamata di EndDialog(). Per provare WM_COMMAND impostate come condizione [esp+34] == 111h e cliccate sul tasto ok.


Note Finali

Siamo giunti alla fine di questa soluzione, spero di non essere stato esageratamente prolisso ma credo che per comprendere il flusso di questo “debugme” sia necessario prima capire a livello programmativo come funzionano queste due API.

Ci tengo a salutare il buon Quequero che grazie al suo lavoro ed a quello di tutta la UIC mi ha tenuto compagnia per anni nei momenti di svago sapendo stuzzicare la mia curiosità costantemente. Saluto anche Ntoskrnl che nonostante la sua poca pazienza (sto scherzando :p) si rende sempre disponibile ad aiutare. Mi piacerebbe infine sapere come sta girando ad AndreaGeddon e che fine ha fattoche latita da molto. Infine un ringraziamento speciale va anche a Pnluck e Bender0 (ormai diventato mips) che hanno contribuito a questa nuova lezione per newbies ed anche a tutti i newbies come me che leggeranno questa soluzione !

Grazie a tutti.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.