Zoom Icon

Lezione 6 Serial Fishing

From UIC

Simple serial fishing

Contents


Infos
Author: Pn
Email: Email
Website: No Site
Date: 15/10/2008 (dd/mm/yyyy)
Level: Working brain required
Language: Italian Image: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
Image: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 malevoli 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.