TabMail v2.4
viaggio al centro dell'MD5 + keygening

Data

by "ZaiRoN"

 

18-1o-2oo2

UIC's Home Page

Published by Quequero


Beh questo tute me l'hai fatto sudare un bel po :P tu sai perche' cmq complimenti e' davvero interessante

 

....

Home page se presente: macchè...
E-mail: ZaiRoNcrk(at)hotmail.com

....

Difficoltà

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

 

MD5!?! o cos'è???


TabMail v2.4
viaggio al centro dell'MD5 + keygening
 
Written by ZaiRoN
 
Allegato
 

Introduzione

ecco qua qualcosa che manca alla uic: un pò di crittografia!
era già un pò di tempo che pensavo di scrivere un tutorial su questo argomento ma non ho mai trovato un target ad hoc. questo programma è proprio quello che stavo cercando! all'interno della sua routine di protezione utilizza l'MD5 e altre semplicissime operazioni. prenderò spunto dal target per parlarvi un minimo di cos'è e di come funziona l'algoritmo MD5.
 
le informazioni sull'MD5 sono state prese dal seguente testo:
The MD5 Message-Digest Algorithm [RFC 1321]
che trovate nel sito degli rfc: http://www.rfc-editor.org/ (completo di sorgenti c).

Tools usati

- un debugger (ollydbg o softice) per studiare l'algoritmo
- un disassemblatore (ida o windasm) ma non necessariamente
- un compilatore (lcc o qualsiasi altro) per scrivere il keygen
 
musica: The Who - Tommy

URL o FTP del programma

http://dlg.krakow.pl/tabmail/

Notizie sul programma

hmmm...serve per la posta!?! bha...direttamente dall'help del programma:
"TabMail is a mail user agent (MUA) for sending, receiving and managing your email messages. It's small and fast, conforms Internet Standards and supports Unicode characters.Some other properties: Automatic attachments compression, Warn when open (or save) dangerous attachments, Simple view of compound messages, Message-thread can span many mailboxes, HTML messages views as plain text, Easy PGP message encryption/decryption, SMTP and POP3 authentication, Support for SSL/TLS connections, Scan for viruses in attachments, Multi-user and multi-account."

Essay

preambolo: due parole sull'MD5
 
L'MD5 è stato creato da Ronald L. Rivest ed è un algoritmo di crittazione che appartiene alla categoria degli algoritmi chiamata 'algoritmi di digest'.
Letteralmente il digest è il riassunto (o sommario) ma spesso lo trovate tradotto come impronta digitale. che cos'è in realtà questo digest? il digest è una serie di caratteri ed è ottenuto applicando l'algoritmo MD5 al messaggio da criptare. la cosa più importante da dire è che il digest così generato rispetta le seguenti proprietà:
- ha una lungezza fissa di 128 bit
- è computazionalmente impossibile trovare due messaggi m1 e m2 tali che abbiano la stessa impronta digitale (almeno in teoria:p)
- non è possibile partire dal digest e risalire al messaggio originale in chiaro
 
vediamo molto velocemente come opera l'algoritmo attraverso i seguenti 5 passi:
 
1) Append padding bits (aggiunta dei bit di riempimento)
il messaggio da criptare viene esteso con una serie di bit. il primo bit è settato ad 1 mentre tutti gli altri sono settati a 0. il numero dei bit aggiunti in questo modo sommato al n° dei bit del messaggio deve essere congruente a 448 modulo 512.
 
2) Append length
vengono aggiunti 8 byte che rappresentano la lughezza (in bit) del messaggio prima del riempimento.
 
3) Initialize MD Buffer
un buffer di 4 word viene inizializzato con i seguenti valori costanti:
word A: 01 23 45 67
word B: 89 AB CD EF
word C: FE DC BA 98
word D: 76 54 32 10
 
4) Process Message in 16-Word Blocks
questo è il fulcro di tutto: l'algoritmo vero e proprio. lavora utilizzando il messaggio ottenuto dai punti 1 e 2 e il buffer MD inizializzato al punto 3.
 
5) Output
ultima fase in cui viene prodotto l'output: il digest a 16 byte.
 
i primi tre passi appartengono alla fase di inizializzazione e possono essere eseguiti in un qualsiasi ordine si voglia; gli ultimi due passi, sfruttando il lavoro effettuato in precedenza, producono il digest.
 
questo è quanto! non preoccupatevi se non avete capito qualcosa, vi farò rivedere tutti e 5 i punti applicandoli al programma che abbiamo tra le mani man mano che studiamo la routine di protezione.
 
1° parte della routine di protezione: MD5
 
dopo questa brevissima introduzione partiamo col vero e proprio tutorial.
il programma è compresso con upx, nessun problema! scaricatevi upx e fate un bel 'upx -d tabmail.exe' oppure dumpatelo e sistemate l'ep.
 
lanciamo il programma e cerchiamo di capire come approcciare il target. l'unico modo di registrarsi è tramite il pulsante 'register' presente nel form iniziale. inseriamo un nome (ZaiRoN...) e un serial (135790) e vediamo cosa succede se premiamo 'ok'. un bel messaggio di errore! bene, proviamo a cercare il testo nella string reference di windasm.
trovato! diamo un'occhiata al codice che ci porta al messaggio di errore e vediamo se si riesce a tirare fuori qualcosa di interessante:
 
:00483220 mov eax, dword ptr [ebp-0C]   <--- eax punta al nome inserito
:00483223 pop edx   <----------------------- edx punta al serial inserito
:00483224 call 0048319C   <----------------- hmmm...
:00483229 test al, al
:0048322B je 00483240   <------------------- se al = 0 saltiamo al messaggio di errore
    ...
:0048324B mov eax, 0048329C   <------------- 48329C punta al messaggio "Incorrect registation inform..."
:00483250 call 00473104   <----------------- visualizza il messagebox di errore
 
sembra proprio che ci siamo! entriamo nella call all'indirizzo 483224 e iniziamo a steppare:
 
:004831A4 call 00403DBC   <-------------- ritorna la lunghezza del nome inserito
:004831A9 cmp eax, 00000008   <---------- il nome deve essere composto da almeno 9 caratteri
:004831AC jle 004831C3   <--------------- altrimenti saltiamo a ERRORE
:004831AE mov edx, dword ptr [004CC220]
:004831B4 mov edx, dword ptr [edx]   <--- edx punta alla stringa "Magdaleine"
:004831B6 mov ecx, esi   <--------------- ecx punta al serial inserito
:004831B8 mov eax, ebx   <--------------- eax punta al nome inserito
:004831BA call 00480710   <-------------- dobbiamo entrare qua dentro
:004831BF test al, al
:004831C1 jne 004831C8
:004831C3 xor eax, eax   <--------------- mette eax = 0 e siamo...fottuti!
primissima informazione: c'è un controllo sul nome inserito, in particolare sulla sua lunghezza. il nome deve essere composto da almeno 9 caratteri. (uno che si chiama 'Pio Po' come fa??? non si può registrare:ppp)
entriamo nella call e saltiamo un pò di *codice spazzatura*. arrivati all'indirizzo 0048076E entrate nella call:
 
:0047F32A mov [edi+04], 67452301   <--- attenzione
:0047F331 mov [edi+08], EFCDAB89   <--- attenzione
:0047F338 mov [edi+0C], 98BADCFE   <--- attenzione
:0047F33F mov [edi+10], 10325476   <--- attenzione
 
vi dicono niente queste costanti? giusto! sono i valori di inizializzazione dell'MD5 trovati nel passo 3: Initialize MD Buffer.
proseguiamo:
 
:0047F35B mov     eax, esi   <---- eax punta al messaggio da criptare
:0047F35D call    00403DBC   <--- ritorna il numero di caratteri componenti il messaggio
 
hmmm...sbaglio o nel messaggio da criptare c'è qualcosa di troppo? il messaggio è: "Magdaleine",00,"ZaiRoN..."
cos'è questo "Magdaleine"??? la stringa "Magdaleine" seguita dal byte 0x00 viene messa, sempre, prima del nome inserito. Questo non è ovviamente correlato con l'MD5, fa soltanto parte della routine di protezione di questo programma. molto probabilmente questo nome è riferito alla ragazza (o moglie o figliola o spasimante o...ma chi cazzo se ne frega :) del programmatore :)
 
una piccola nota prima di proseguire: d'ora in poi con la parola message mi riferirò al messaggio da criptare cioè alla stringa composta da "Magdaleine" seguita da 0x00 e dal nome.
 
:0047F36A lea eax, dword ptr [esp+04]   <------- eax punta ad un buffer di 0x40 byte
:0047F36E xor ecx, ecx
:0047F370 mov edx, 00000040
:0047F375 call 00402B58   <--------------------- pulisce i byte (0x40) del buffer
    ...
:0047F387 lea edx, dword ptr [esp+04]   <------- edx punta al buffer
:0047F38B lea eax, dword ptr [esi+ebx-01]   <--- eax punta a message
:0047F38F mov ecx, dword ptr [esp]
:0047F392 call 004027B4   <--------------------- copia message all'inizio del buffer
:0047F397 mov eax, dword ptr [esp]   <---------- numero di caratteri presenti nel message
:0047F39A mov [esp+eax+04], 80   <-------------- mette 0x80 dopo il message all'interno del buffer
    ...
:0047F3C0 mov eax, esi   <---------------------- eax punta al message
:0047F3C2 call 00403DBC   <--------------------- ritorna in eax il numero di caratteri presenti nel message
:0047F3C7 shl eax, 03   <----------------------- eax = eax * 8 = n° bit in message (= A0h nel mio caso)
:0047F3CA mov dword ptr [esp], eax   <---------- salva il valore contenuto in eax
:0047F3CD lea edx, dword ptr [esp+3C]   <------- si sposta su di un particolare byte all'interno del buffer
:0047F3D1 mov eax, esp
:0047F3D3 mov ecx, 00000004
:0047F3D8 call 004027B4   <--------------------- scrive A0 nel byte su cui si era posizionato in precedenza
 
ok, vediamo di dare un senso a tutto questo :)
in queste poche linee di codice sono racchiusi un paio di passi effettuati dall'algoritmo, in particolare il 1° e il 2°.
per cercare di farvi capire al meglio cosa è successo, eccovi qua come appare il *mio* buffer dopo l'esecuzione di questo pezzo di codice:
 
come potete vedere il buffer contiene 64 byte (77F218/77F257) di cui molti a zero.
la prima parte del buffer contiene il message da criptare (77F218/77F22B).
dopo il message trovate un byte settato al valore 0x80; da dove viene fuori questo valore? questo è il primo di una serie di byte che vengono aggiunti per fare in modo che il numero dei bit aggiunti dopo il message sommato al n° dei bit del message sia congruente a 448 modulo 512.
vi ricordate il punto 1 dell'algoritmo? questo 0x80 è una conseguenza immediata del passo 1: Append padding bits. come ci dice l'algoritmo, il primo bit dopo il message deve essere posto a 1 e tutti gli altri a 0. per quanto riguarda il primo byte abbiamo gli 8 bit così settati: 10000000 = 1 * 2^7 + 0 * 2^6 + ... + 0*2^0 = 128 = 0x80. il passo uno però non ci dice esplicitamente (esattamente) quanti bit a 0 devono essere aggiunti; per sapere il numero preciso si dovrebbe risolvere la congruenza...
visto che il message da criptare è piccolo e visto che non mi sembra il caso di piazzarvi nozioni matematiche, per farla breve vi basti sapere che il numero di bit contenuti nel message sommati al numero di bit contenuti nel padding (77F22C/77F24F) deve essere uguale a 448. in questo modo la congruenza è banalmente soddisfatta :). se il message fosse stato più lungo avreste dovuto fare un minimo di calcoli in più per risovere la congruenza.
contolliamo che sia effettivamente così:
il numero dei bit presenti nel message è 160 (20 byte * 8 :)
il numero dei bit presenti nel padding è 288 (36 byte * 8)
per cui: 160 + 288 = 448
perfetto! siamo riusciti a dare un senso ai 2/3 del contenuto del buffer.
resta soltanto da capire cosa rappresentano gli ultimi 8 byte. questo in realtà è molto semplice ed è il risultato ottenuto dal passo 2 dell'algoritmo: Append length. il message -come abbiamo appena visto- è composto da 160 bit per cui qui mettiamo il corrispettivo valore in esadecimale: 0xA0.
bene! adesso l'algoritmo ha tutto ciò che gli serve (all'interno del buffer) per poter generare il digest.
 
:0047F3DD lea edx, dword ptr [esp+04]   <--- edx punta al buffer
:0047F3E1 mov eax, edi   <------------------ eax+4 punta al buffer contenente le costanti di inizializzazione
:0047F3E3 mov ecx, dword ptr [eax]
:0047F3E5 call [ecx+10]   <----------------- finalmente l'algoritmo: Process Message in 16-Word Blocks!!!
 
avete provato a steppare l'ultima call? ganzo vero! rappresenta il passo 4: Process Message in 16-Word Blocks.
l'algoritmo vero e proprio non è altro che una lunghissima serie di istruzioni (forse è meglio dire blocchi di istruzioni) che si ripete senza neanche un jump (sia esso condizionale che non-condizionale). non mi sembra il caso di spiegarvi quali sono i passi che effettua; se proprio siete curiosi fate riferimento al testo originale:)
questa particolare struttura dell'algoritmo è più o meno una caratteristica comune a molte funzioni hash. inutile dire che non c'è modo di reversarla in quanto è una funzione one-way!
prima di proseguire con la routine di protezione del serial vediamo che cosa ritorna questa funzione. entriamo in quest'ultima call e andiamo nella parte finale dell'algoritmo:
 
:480407 mov dword ptr [ebx+08], eax
:48040A add dword ptr [ebx+04], edi   <--- ebx+4 punta al digest
:48040D add dword ptr [ebx+08], ebp
:480410 mov eax, dword ptr [esp]
:480413 add dword ptr [ebx+0C], eax
:480416 mov eax, dword ptr [esp+04]
:48041A add dword ptr [ebx+10], eax
 
ecco qua le operazioni finali di salvataggio del digest. il digest relativo al messsage che vi ho fatto vedere prima è composto dai seguenti byte:
E7 1F 65 B3   32 1E BC C8   61 08 BF 7A   84 70 4D 94
 
qui finisce la parte di tutorial relativa all'MD5. adesso sappiamo (o almeno dovremmo) come è strutturato e come funziona questo algoritmo. ovviamente in futuro non dovrete cercare di identificare tutti i passi dell'algoritmo per riconoscere questo tipo di hash. le due cose che fanno scattare la molla per dire la parola MD5 sono:
- i valori costanti di inizializzazione
- la serie semi-infinita di istruzioni senza jmp/jnz
solitamente queste due cose bastano ma spesso può non essere così (capita a volte di trovare l'MD5 modificato...) per cui vi consiglio un piccolissimo programmino. è stato codato da Ivanopulo, si chiama DAMN Hash Calcultor e lo trovate qui: http://www.damn.to/software/hashcalc.html
in rete ne trovate diversi (forse anche migliori...non so) ma questo mi è sembrato subito niente male.
 
il resto della routine di protezione
 
è arrivato il momento di vedere quale assurdo algoritmo verrà applicato al digest ottenuto:)
in realtà, come vedremo tra poco, il tutto è molto semplice per cui non preoccupatevi.
steppiamo un pochino fino a che non arriviamo qui:
 
:00480798 mov edx, dword ptr [ebp-0C]   <--- edx punta alla stringa "E71F65B3-321EBCC8-6108BF7A-84704D94"
:0048079B mov eax, dword ptr [ebp-04]   <--- eax punta al serial che abbiamo inserito
:0048079E call 0047F1F4   <----------------- hmmm...
 
ci siamo quasi. la prima volta che ho incontrato queste istruzioni ho pensato che il digest (mappato a stringa e esteso con i 3 '-' come separatori) fosse in realtà il serial valido ma...mi sbagliavo.
d'ora in poi mi riferirò al digest in questa sua nuova forma.
entriamo nella call che presenta al suo interno un ciclo ripetuto varie volte; i valori che ho affiancato tra parentesi - relativi al mio digest e al mio serial - sono ottenuti dalla prima iterazione e li ho messi per alleggerire la spiegazione:
 
:0047F21D cmp byte ptr [edi+ebx-01], 2D   <--- confronta l'ebx-esimo carattere del digest con il separatore '-'
:0047F222 jne 0047F225
:0047F224 inc ebx   <------------------------- incrementa ebx soltanto se trova '-'
:0047F225 lea eax, dword ptr [ebp-08]
:0047F228 push eax
:0047F229 mov ecx, 00000002
:0047F22E mov edx, ebx
:0047F230 mov eax, edi   <-------------------- eax punta ad un carattere del digest (il primo)
:0047F232 call 00403FC0   <------------------- prende due caratteri dal digest (i primi due: E7)
:0047F237 mov eax, dword ptr [ebp-08]   <----- eax punta ai due caratteri presi
:0047F23A push eax   <------------------------ pusha il puntatore ai due caratteri presi
:0047F23B lea eax, dword ptr [ebp-0C]
:0047F23E push eax
:0047F23F mov ecx, 00000002
:0047F244 mov edx, ebx
:0047F246 mov eax, esi   <-------------------- eax punta ad un carattere del serial inserito (il primo)
:0047F248 call 00403FC0   <------------------- prende due caratteri dal serial (i primi due: 13)
:0047F24D mov eax, dword ptr [ebp-0C]   <----- eax punta ai due caratteri presi dal serial (13)
:0047F250 pop edx   <------------------------- edx punta ai due caratteri presi dal digest (E7)
:0047F251 call 0047F1B8   <------------------- check sulla coppia di caratteri
:0047F256 test al, al   <--------------------- se il check da esito positivo ritorna al = 1 altrimenti 0
:0047F258 je 0047F26C   <--------------------- se si salta si va drittidritti al messaggio di errore
:0047F25A add ebx, 00000002   <--------------- incrementa il contatore
:0047F25D mov eax, esi   <-------------------- eax punta al serial
:0047F25F call 00403DBC   <------------------- ritorna in eax la lunghezza del serial
:0047F264 cmp ebx, eax   <-------------------- dobbiamo continuare con il check o abbiamo finito???
:0047F266 jl 0047F21D
:0047F268 mov [ebp-01], 01   <---------------- se arriviamo qui il serial è valido!!!
 
ecco qua il confronto finale che avviene prendendo due caratteri dal digest e due dal serial. se tutte le coppie prese rispettano una determinata proprietà (che è racchiusa all'interno della call in 47F251) allora siamo registrati.
vediamo cosa si nasconde dentro l'ultima call di questo tutorial (facendo sempre riferimento alla prima coppia di caratteri):
 
:0047F1D3 and eax, 000000FF   <--- al = E7 (primi due caratteri del digest)
:0047F1D8 test al, 01   <--------- al è pari o dispari !?!
:0047F1DA je 0047F1EC   <--------- se è pari effettua il salto e passa direttamente a confrontare la coppia di caratteri
:0047F1DC mov edx, ebx   <-------- ebx = 00000013 (i due caratteri presi dal serial...)
:0047F1DE imul edx, ebx   <------- ne fa il quadrato
:0047F1E1 imul edx, ebx   <------- e ora il cubo
:0047F1E4 and edx, 000000FF   <--- edx and 0xFF = 1ACB and 0xFF = 0xCB
:0047F1EA mov ebx, edx   <-------- ebx = 0xCB
:0047F1EC cmp ebx, eax   <-------- confronto tra il valore ottenuto(0xCB) e i due caratteri presi dal digest (0xE7)
:0047F1EE sete al   <------------- ovviamente devono essere uguali!
 
ecco qua come si presenta l'ultima parte di questa routine di protezione. come potete vedere sono soltanto semplici operazioni.
con questo termina lo studio della routine di protezione.
 
un ultimo sforzo: scriviamo il keygen
 
a questo punto dobbiamo capire come è possibile ottenere un serial valido partendo da un nome qualsiasi.
se avete seguito attentamente tutti i passi effettuati dall'algoritmo non dovreste avere problemi; ad ogni modo ecco qua come dobbiamo agire:
 
1. prendiamo il nome, costruiamo il message e utilizziamo l'MD5 per generare il digest a 16 byte
2. prendiamo un byte alla volta dal digest e controlliamo se rappresenta un numero pari o dispari
2.1. se il numero è pari, il byte del digest viene convertito in due nuovi caratteri del serial (i.e. il byte 32 genera i due caratteri '3', '2')
2.2. se il numero è dispari, con un piccolo brute (contenente le istruzioni in 47F1DE/47F1E4) andiamo alla ricerca dei due nuovi caratteri del serial
3. ripetiamo il punto 2 per 16 volte (aggiungendo quando serve il separatore '-')
4. game over...
 
se il passo 1 vi spaventa, non preoccupatevi. in circolazione si trovano molte implementazioni dell'MD5. come vi ho detto all'inizio del tutorial potete utilizzare direttamente la versione originale. si tratta solo di sceglierne una e leggere come invocare le varie funzioni :)
quando avrete trovato il serial esatto registratevi e poi chiudete il programma. al seguente riavvio il programma partirà senza problemi e voi sarete finalmente registrati:)
 
nel file allegato trovate il keygen + source code commentato + file di implementazione MD5 originale
 
bona!
 
                                        ZaiRoN
 

Note Saluti Finali

 
bene amici, mi sembra di aver detto tutto. un saluto a tutti gli amici della UIC!!!
per qualsiasi cosa contattatemi senza problemi....
 

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...bha!?! siamo sicuri ? :ppp