Zoom Icon

Lezione 6 Serial Fishing

From UIC Archive

Simple serial fishing

Contents


Lezione 6 Serial Fishing
Author: Pn
Email: Pnmail.png
Website: No Site
Date: 15/10/2008 (dd/mm/yyyy)
Level: Working brain required
Language: Italian Flag Italian.gif
Comments: Lo sto scrivendo il più velocemente possibileee...



Introduzione

Benvenuti in questa sesta lezione per newbie 2009, oggi si parlerà di serial fishing: quindi parleremo di GetDlgItemText e compagnia bella, e di come trovare la password corretta direttamente nella memoria del processo.


Tools

Lezione6 Crackme
OllyDbg


Link e Riferimenti

Msdn Sito che contiene tutte le API di windows.
Codifica Unicode Informazioni sulla codifica UNICODE che verrà usata nel crackme.
Discussione di questa lezione sul Forum UIC


Essay

Prima di iniziare a reversare, analizziamo la preda come fa ogni predatore: sentite l'aria sulla vostra pelle, sentite l'odore dell'erba e lanciate l'a...pplicazione: vi apparirà una windows con due textbox e tre pulsanti, infatti ogni pulsante rappresenta un livello del crackme.
Al primo livello viene usata l'API più diffusa per prelevare il testo da una textbox; al secondo livello viene usata un API meno comune; attivando il terzo livello, il controllo della password verrà fatto, ogni qual volta verrà digitato un carattere nella textbox della password.

Le basi

Prima di mostrarvi il disasm assembly, vi do delle info su GetDlgItemText: questa API prende il testo da una textbox e lo mette in un buffer, ecco la dichiarazione della funzione: UINT GetDlgItemText(

   HWND hDlg,
   int nIDDlgItem,
   LPTSTR lpString,
   int nMaxCount

); /* Ecco a voi la spiegazione dei parametri più importanti e del valore di ritorno

nIDDlgItem [in] Specifica l'identificatore della TEXTBOX che contiene il testo da prelevare lpString [out] Indica il buffer nel quale verrà storato il testo nMaxCount [in] Specifica la lunghezza massima di caratteri da prelevare.

Valore di ritorno Se tutto è andato per il verso giusto, la funzione ritorna il numero di caratteri letti.*/ Capito il funzionamento di quest'api, possiamo iniziare a fare sul serio.

Reversing pratico

Debuggiamo il crackme della lezione 4 in olly e settate due breakpoint (abbreviato bp), digitando questi comandi nella command line: bp GetDlgItemTextA bp GetDlgItemTextW Ho settatto un bp sia sulla versione ascii che sulla versione unicode della funzione, poichè negli ultimi anni, sempre meno programmatori, per ragioni di portabilità, usano le funzioni che adoperano le stringhe ascii
Avviate il debugee (il programma da debuggare) in olly premendo F9, digitiamo in user "ABCDEF" ed in password "123456", e premete sul pulsante relativo al livello 1: il debugger ci fermerà su GetDlgItemTextW.
Per tornare al codice del programma possiamo steppare fino al RET, oppure andare nel menu "DEBUG" e cliccare su "Execute till return": Crtl+F9. A questo punto steppate una volta (F8) e tornerete a 40132F.
Vi mostro il codice relativo all'evento generato dal pulsante: 00401316 MOV ESI,DWORD PTR SS:[ESP+C]  ; Case 3E8 of switch 00401275 0040131A MOV EDI,DWORD PTR DS:[<&USER32.GetDlgItemTextW>]; USER32.GetDlgItemTextW 00401320 PUSH 14  ; /Count = 14 (20.) 00401322 PUSH lezione4.0040AC68  ; |Buffer = lezione4.0040AC68 00401327 PUSH 3E9  ; |ControlID = 3E9 (1001.) 0040132C PUSH ESI  ; |hWnd 0040132D CALL EDI  ; \GetDlgItemTextW 0040132F CMP EAX,14  ;<------------- voi tornate qua. 00401332 JNB SHORT lezione4.0040138D 00401334 PUSH 14  ; /Count = 14 (20.) 00401336 PUSH lezione4.0040AC40  ; |Buffer = lezione4.0040AC40 0040133B PUSH 3EB  ; |ControlID = 3EB (1003.) 00401340 PUSH ESI  ; |hWnd 00401341 CALL EDI  ; \GetDlgItemTextW 00401343 CMP EAX,14 00401346 JNB SHORT lezione4.0040138D 00401348 PUSH lezione4.0040AC40 0040134D PUSH lezione4.0040AC68  ; UNICODE "ABCEDF" 00401352 CALL lezione4.00401000 00401357 ADD ESP,8 0040135A POP EDI 0040135B XOR EAX,EAX 0040135D POP ESI 0040135E RETN 10 Come vedete olly, accanto ai vari push, vi dice anche te parametro è (hwnd, controlID, buffer), se riconosce la funzione che segue i push stessi.
Ciò che interessa a noi, in questo caso, sono i due buffer che conterranno le stringe sulle quali verrà effettuato il controllo per la registrazione.
0040AC68 è il buffer della textbox relativa all'user (se non ci credete, nella finestra del hexdump, fate pulsante destro e poi Go to -> expression e digitate 40AC68: vedrete in unicode la scritta ABCDEF) ed 0040AC40 è il buffer relativo alla textbox della password.

Ora analizziamo il codice assembly (abbreviato asm): l'EIP è settato a 40132f e lì viene confrontato il valore di EAX con 14h (20 dec), vi ricordo che EAX è usato dalle funzioni per riportare il valore di ritorno: GetDlgItemTextW è una funzione che ritorna un intero, intero che va ad indica la lunghezza della stringa letta, quindi se la stringa letta non è minore (Jump Not Belove) di 20 caratteri, il programma salta in un codice che non serve a controllare la password, e che quindi non interessa per i nostri "nobili" scopi :P; come vedete lo stesso controllo viene effettuato anche dopo la seconda chiamata a GetDlgItemTextW.
Successivamente da 401348 vendiamo due PUSH che storano nello stack gli indirizzi dei due buffer, e poi subito di seguito c'è una CALL.
Io direi di analizzarci la CALL: settiamo un bp su 401352, e premiamo F9 finchè non arriviamo proprio su quest'address, poi steppiamo dentro (F7) e finalmente ci ritroveremo in 401000.

Prima di tutto vi voglio far notare che olly individua subito i vari loop, ciò semplifica molto l'analisi, ecco un immagine chiarificatrice
Lez4 1.jpg

Iniziamo l'analisi della prima parte della funzione 00401000 SUB ESP,2C  ; vengono allocati 2Ch (44 dec) byte nello stack 00401003 MOV EAX,DWORD PTR DS:[40A000] ;superfluo per la nostra analisi 00401008 XOR EAX,ESP ;superfluo 0040100A MOV DWORD PTR SS:[ESP+28],EAX ;superfluo 0040100E PUSH EBX ; viene salvato nello stack il valore 0 Tutto questo può essere considerato il prologo della funzione, ora vi posto il codice relativo al primo loop 0040100F MOV EBX,DWORD PTR SS:[ESP+34] ;in ebx c'è l'address del buffer dell'user 00401013 PUSH ESI ; viene salvato il valore di esi nello stack 00401014 LEA ESI,DWORD PTR SS:[ESP+8] ; viene caricato un indirizzo dello stack in ESI (imho meglio tenerlo d'occhio) 00401018 XOR ECX,ECX  ;ECX come dovreste sapere è usato come contatore, quindi un contatore viene azzerato 0040101A MOV EAX,EBX ; in EAX va a finire il puntatore alla stringa relativa all'user 0040101C SUB ESI,EBX 0040101E MOV EDI,EDI ; fa nulla 00401020 /MOVZX EDX,WORD PTR DS:[EAX] ; movzx: sposta un dato a 8 o 16 bit in una destinazione a 16 o 32 bit inserendo zeri 00401023 |TEST DX,DX ; 'test registro, registro' equivale a 'cmp registro, 0' 00401026 |JE SHORT lezione4.00401035 ; se il registro DX è vuoto il ciclo termina 00401028 |MOV WORD PTR DS:[ESI+EAX],DX ; viene salvato in un buffer la word appena letta

l'indirizzo del buffer è generato sommando i valori di ESI e di EAX.

0040102C |INC ECX ; il contatore viene incrementato 0040102D |ADD EAX,2 ; il puntatore della stringa unicode viene incrementato

se la stringa fosse stata di tipo char(size type
1 byte) e non wchar_t(size type: 2 byte) il codice sarebbe stato ADD EAX, 1

00401030 |CMP ECX,14 ;compara il contatore con 14h (20 dec) 00401033 \JL SHORT lezione4.00401020 ; se non è minore il ciclo può continuare

qui siamo fuori dal ciclo

00401035 XOR EAX,EAX  ; azzera eax 00401037 CMP ECX,5  ; compara il contatore con 5 0040103A MOV WORD PTR SS:[ESP+ECX*2+8],AX ; mette il valore 0 alla fine del buffer 0040103F JL lezione4.0040113C; salta alla beggar off (http://www.quequero.org/Glossary#B), se il contatore è minore di 5 Riassumo brevemente il codice appena analizzato: è una strcpy modificata un pò.
Ora facciamo un breve esercizio: convertiamo questo breve codice da asm in C int contatore = 0; while(stringa_user[contatore] != 0) {

 buffer[contatore] = stringa_user[contatore];
 contatore++;
 if(contatore >= 0x14) break;

} buffer[contatore] = 0; if(contatore < 5) {

 // beggar off

} else {

 // fai altra roba

} Spero che quest'ultima cosa vi sia piaciuta molto, anche perchè fare quest'esercizio vi facilita di molto l'analisi.
Continuiamo con altro codice, ecco a voi un altro loop: vi dico immediatamente che per dire che EDI è usato come contatore ed ESI come indice, ho analizzato il codice prima di riportarlo. 00401045 MOVZX EAX,WORD PTR DS:[EBX] ; EAX contiene il primo carattere della stringa user 00401048 PUSH EDI 00401049 XOR EDI,EDI  ; EDI è il contatore 0040104B TEST AX,AX ; se il primo carattere è NULL 0040104E JE lezione4.004010F3 ;il ciclo sottostante non viene eseguito 00401054 MOV ECX,EBX ; qui ecx non è più usato come contattore ma come puntatore ad una stringa 00401056 MOVZX EAX,AX ; EAX contiene solo il carattere 00401059 XOR ESI,ESI  ; ESI è usato come indice per un vettore 0040105B JMP SHORT lezione4.00401060 0040105D LEA ECX,DWORD PTR DS:[ECX]  ;quest'istruzione non viene eseguita mai 00401060 /MOV CL,BYTE PTR DS:[ECX]  ; prende il carattere dalla stringa 00401062 |CMP CL,61  ; e lo compara con 0x61 ('a') 00401065 |JL SHORT lezione4.0040109E ; se è minore va su 'Ramo2' 00401067 |CMP CL,7A  ; ora compara il carattere con 0x7A ('z') 0040106A |JG SHORT lezione4.0040109E ; se è più grande salta 0040106C |MOVZX ECX,AX  ; altrimenti in ecx viene messo solo il carattere letto 0040106F |SUB ECX,5D  ; gli viene sottratto 0x5D (93 dec) 00401072 |CMP ECX,1B  ; poi viene comparato il risultante con 0x1B (27 dec) 00401075 |JLE SHORT lezione4.00401094 ; e se questo valore è minore o uguale salta a 'Ramo2' 00401077 |MOV EAX,4BDA12F7 ; viene messo un valore arbitrario in EAX 0040107C |IMUL ECX  ; che poi viene moltiplicato per il valore di ECX che ha il carattere 0040107E |SAR EDX,3  ; EDX (che contiene i bit più significativi del risultato della moltiplicazione se questa supera i 32bit) viene shiffato a destra di tre bit 00401081 |MOV EAX,EDX  ; viene messo il risultato in EAX 00401083 |SHR EAX,1F  ; EAX con quest'istruzione conterrà 0 00401086 |ADD EAX,EDX  ; equivale a MOV EAX, EDX 00401088 |IMUL EAX,EAX,1B  ; Moltiplica EAX con 1Bh 0040108B |MOV EDX,1  ; EDX contiene 1 00401090 |SUB EDX,EAX  ; questa sottrazione dovrebbe dare un valore negativo 00401092 |ADD ECX,EDX  ; ECX dovrebbe contenere un valore positivo 00401094 |ADD ECX,60  ; viene aggiunto a ECX 'a'-1 00401097 |MOV WORD PTR SS:[ESP+ESI+C],CX ; viene messo il carattere appena calcolato in buffer2[ESI], buffer2 inizia a ESP+C 0040109C |JMP SHORT lezione4.004010DF ; salta

ok questa parte non è troppo chiara ma non è molto importante capirla a pieno;
l'importante è l'aver capito che qui vengono eseguiti dei calcoli sui caratteri della stringa dell'user;
e che quindi dobbiamo tener d'occhio il buffer in cui viene salvato il carattere calcolato

0040109E |Ramo2: 0040109E |CMP CL,41  ; 41h sarebbe 'A' 004010A1 |JL SHORT lezione4.004010DA 004010A3 |CMP CL,5A ; 0x5A sarebbe 'Z' 004010A6 |JG SHORT lezione4.004010DA 004010A8 |MOVZX ECX,AX 004010AB |SUB ECX,3D  ; sottrae 3Dh a ECX 004010AE |CMP ECX,1B  ; da qui è come il codice precedente 004010B1 |JLE SHORT lezione4.004010D0 004010B3 |MOV EAX,4BDA12F7 004010B8 |IMUL ECX 004010BA |SAR EDX,3 004010BD |MOV EAX,EDX 004010BF |SHR EAX,1F 004010C2 |ADD EAX,EDX 004010C4 |IMUL EAX,EAX,1B 004010C7 |MOV EDX,1 004010CC |SUB EDX,EAX 004010CE |ADD ECX,EDX 004010D0 |ADD ECX,40  ; 40 sarebbe 'A'-1 004010D3 |MOV WORD PTR SS:[ESP+ESI+C],CX ; rimette il carattere appena calcolato in buffer2[ESI] 004010D8 |JMP SHORT lezione4.004010DF

004010DA |Else: 004010DA |MOV WORD PTR SS:[ESP+ESI+C],AX ; viene messo il carattere senza essere modificato in buffer2[ESI]

004010DF |Ultima_parte_ciclo: 004010DF |INC EDI  ; incrementa il contatore 004010E0 |LEA ESI,DWORD PTR DS:[EDI+EDI]  ; equivale a ESI = EDI + EDI; avrebbe potuto fare 'ADD ESI, 2' per incrementare l'indice 004010E3 |MOVZX EAX,WORD PTR DS:[ESI+EBX] ; prende il carattere successivo della stringa e lo mette in EAX 004010E7 |LEA ECX,DWORD PTR DS:[ESI+EBX]  ; come sopra solo che il carattere va in ECX 004010EA |TEST AX,AX  ; se il carattere preso non è nullo 004010ED \JNZ lezione4.00401060 ; re-inizia il ciclo Wau che bel ciclo: come avete capito, qui vengono fatti dei calcoli su ogni carattere della stringa relativa all'user, e poi il risultato viene salvato in un buffer: vi dico subito che questo non è altro che l'implementazione del Cifrario di Cesare (ho scritto io il crackme), nulla di che quindi. 004010F3 MOV EDX,DWORD PTR SS:[ESP+40] ; EDX ha il puntato alla stringa della password 004010F7 LEA ECX,DWORD PTR SS:[ESP+C] ; ECX ha il puntatore del buffer che contiene la stringa appena calcolata 004010FB POP EDI 004010FC LEA ESP,DWORD PTR SS:[ESP] ; non fa nulla 00401100 /MOV AX,WORD PTR DS:[ECX] ; prende una word

dato che ECX è puntatore a stringa unicode, la word è da intendersi wchar_t

00401103 |CMP AX,WORD PTR DS:[EDX] ; prende un wchar_t dalla stringa password 00401106 |JNZ SHORT lezione4.00401126 ; compara i due wchar_t 00401108 |TEST AX,AX 0040110B |JE SHORT lezione4.00401122 ; se il carattere è nullo salta 0040110D |MOV AX,WORD PTR DS:[ECX+2] 00401111 |CMP AX,WORD PTR DS:[EDX+2] 00401115 |JNZ SHORT lezione4.00401126 ; fa lo stesso il carattere successivo 00401117 |ADD ECX,4  ; aumenta il contatore 0040111A |ADD EDX,4  ; aumenta il contatore 0040111D |TEST AX,AX 00401120 \JNZ SHORT lezione4.00401100 ; se il carattere preso in considerazione non è nulla, re-inizia il ciclo

se arriviamo qua tutto è andato per il verso giusto

00401122 XOR EAX,EAX 00401124 JMP SHORT lezione4.0040112B

se arriviamo qua significa che le due stringhe erano diverse

00401126 SBB EAX,EAX  ; equivale a MOV EAX, 0 o ad XOR EAX, EAX 00401128 SBB EAX,-1  ; EAX = -1 0040112B TEST EAX,EAX  ; verifica se EAX è 0 0040112D JNZ SHORT lezione4.0040113C Good_work_strings: 0040112F PUSH EAX 00401130 PUSH lezione4.0040818C  ; UNICODE "Lezione 3" 00401135 PUSH lezione4.00408168  ; UNICODE "Password Corretta" 0040113A JMP SHORT lezione4.00401148 Bad_string_strings: 0040113C PUSH 0 0040113E PUSH lezione4.0040818C  ; UNICODE "Lezione 3" 00401143 PUSH lezione4.00408148  ; UNICODE "Password Errata" 00401148 PUSH 0  ; |hOwner = NULL 0040114A CALL DWORD PTR DS:[<&USER32.MessageBoxW>>; \MessageBoxW: mostra la MessageBox Il codice sopra non è altro che un strcmp, seguita da un if che fa apparire una MessageBox di OK se le stringhe sono uguali, altrimenti appare una MessageBox di KO.
La nostra password viene confrontata con una stringa salvata nello stack, quindi per vedere qual è la password esatta per l'user inserito, basta andare a vedere che contiene l'indirizzo di memoria contenuto in ECX all'inizio del ciclo: aka serial fishing.

Approfondimento

Ecco a voi altri modi usati per il controllo delle stringhe: vi voglio solo dire che mettere dei breakpoint in modo produttivo, sulle api che vi mostrerò, è una cosa che avviene molto di rado, soprattutto perchè sono api usate spessissimo e per fare cose diverse, quindi se arrivate al punto di voler mettere dei bp su queste, vi consiglio di risalire all DialogProc della Dialog interessata al controllo dell'user e psw ed analizzarla per bene, eccovi un esempio, e vedere se queste api sono lì presenti.

Livello 2: Il pulsante relativo al secondo livello utilizza, per catturare il testo, l'api SendDlgItemMessage LRESULT SendDlgItemMessage(

   HWND hDlg,
   int nIDDlgItem,
   UINT Msg,
   WPARAM wParam,
   LPARAM lParam

); che passa a Msg la costante WM_GETTEXT, per l'appunto, ecco il disasm 004012F2 PUSH lezione4.0040AC68  ; /lParam = 40AC68 => è il buffer 004012F7 PUSH 14  ; |wParam = 14 => max caratteri da leggere 004012F9 PUSH 0D  ; |Message = WM_GETTEXT 004012FB PUSH 3E9  ; |ControlID = 3E9 (1001.) 00401300 PUSH ESI  ; |hWnd 00401301 CALL EDI  ; \SendDlgItemMessageW Vi mostro quest'api solo per scopo informativo: è adoperata di rado, ma saperne la conoscenza non è affatto una cattiva cosa.

Livello 3: Attivando il terzo pulsante, ed inserendo la scritta "CESARE" (senza gli apici :P), vedrete che appena avrete digitato la 'e' finale, vi apparirà una bella MessageBox di complimenti: ciò avviene perchè la password viene controllata ogni qual volta viene inserito un carattere nella textbox (l'ho detto anche all'inizio della lezione), tutto questo grazie al subclassing delle finestre: non lasciatevi intimorire, non è nulla d'eccezionale.
Per il subclassing su usa l'api SetWindowLong //The SetWindowLong function changes an attribute of the specified window LONG SetWindowLong(

   HWND hWnd,
   int nIndex,
   LONG dwNewLong

);

//ecco un esempio dell' utilizzo in C wpOrigEditProc = (WNDPROC) SetWindowLong(GetDlgItem(hWnd, IDC_EDIT2), GWL_WNDPROC, (LONG) EditSubclassProc); // dove EditSubclassProc è dichiarata in questo modo LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Quindi per sarebe quale funzione gestisce gli eventi per una data window (i pulsanti, come tutti gli altri toolbox sono considerate delle finestre (window in inglese)), bisognerebbe settare un breakpoint su SetWindowLong e trovare l'handle giusto per una data window
Vi mostro il disasm 004012C6 PUSH lezione4.00401170  ; /NewValue = 401170 ; è l'addr della funzione che gestirà gli eventi 004012CB PUSH -4  ; |Index = GWL_WNDPROC 004012CD PUSH 3EB  ; |/ControlID = 3EB (1003.) 004012D2 PUSH ESI  ; ||hWnd 004012D3 CALL EDI  ; |\GetDlgItem 004012D5 PUSH EAX  ; |hWnd 004012D6 CALL DWORD PTR DS:[<&USER32.SetWindowLon>; \SetWindowLongW

Compito per Casa

Dato che il compito di queste lezioni è fare di voi dei reverser, convertitemi da asm in C la funzione 401170 =).


Note Finali

Ringrazio tutti e soprattutto Ntoskrnl, EvilCry, Quake2, Que, AndreaGeddon, jnz_ e Dottò.

Pn =)


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.