Lezione 6 Serial Fishing
From UIC
Simple serial fishing
Contents |
| Infos | |
|---|---|
| Author: | Pn |
| Email: | |
| Website: | No Site |
| Date: | 15/10/2008 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| 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
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:
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 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:
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.

Iniziamo l'analisi della prima parte della funzione
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
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
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.
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.
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
HWND hDlg,
int nIDDlgItem,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
che passa a Msg la costante WM_GETTEXT, per l'appunto, ecco il disasm
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
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
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.