Soluzione Crackme 45
From UIC
Soluzione CrackMe 45
Contents |
| Infos | |
|---|---|
| Author: | GeXxO |
| Email: | pelopelus@hotmail.it |
| Website: | None |
| Date: | 19/09/2009 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Formattazione Wiki: Antelox |
Tools
Link e Riferimenti
Questo è il Crackme n°45 disponibile alla pagina Crackme 26 - 50 e si chiama CM45.
Notizie sul Programma
Vi dico che l'eseguibile non è packato, cryptato o cose del genere e quindi non abbiamo bisogno di PeID.
Essay
Bene, allora si incomincia...
Aprendo il crackme vedremo una dialog con due textbox, una per l'username e l'altra per il seriale. Provando ad inserire dei caratteri si nota che la textbox del username non accetta caratteri speciali ma solo lettere, cifre e lo spazio. Inoltre se proviamo a premere il pulsante check ( con i dati sbagliati ) viene mostrata una messagebox che ci informa che i dati sono sbagliati. ( La MessageBox è utile per risalire alla routine di registrazione ).
Cerchiamo di risalire alla routine di registrazione tramite il messaggio che ci viene mostrato quando le informazioni per la registrazione sono sbagliate. Aprimo il CM con olly e facciamo tasto destro->Search for->All referenced text strings. Non c'è bisogno neanche di usare la funzione cerca perchè in tutto troveremo una decina di stringhe. Noteremo subito la presenza della stringa che ci viene mostrata quando inseriamo i dati errati. Cliccate sulla prima occorrenza e vi troverete in questo punto:
004010FD . 68 00304000 PUSH 001.00403000 ; |Title = "a85k CrackMe"
00401102 . 68 6A304000 PUSH 001.0040306A ; |Text = "Invalid Name or Serial Number"
00401107 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner = 7FFDC000
0040110A . E8 3F020000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
Appena sopra troviamo una chiamata a SendDlgItemMessageA che viene usata per recuperare il contenuto della textbox del username:
004010E5 . 6A 1E PUSH 1E ; |wParam = 1E
004010E7 . 6A 0D PUSH 0D ; |Message = WM_GETTEXT
004010E9 . 68 B90B0000 PUSH 0BB9 ; |ControlID = BB9 (3001.)
004010EE . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd = 7FFDC000
004010F1 . E8 64020000 CALL <JMP.&USER32.SendDlgItemMessageA> ; \SendDlgItemMessageA
Bene!!! Abbiamo trovato il punto dove dobbiamo incominciare ad analizzare il codice. Questo lo possiamo dire perchè subito dopo noteremo che il programma inizia subito a fare dei controlli sulla lunghezza del username e poi passa alla routine per la generazione del seriale. Voglio precisare che quando viene chiamata l'API SendDlgItemMessageA lParam è un parametro che ha significato di buffer, cioè da quell'indirizzo in poi verrà memorizzata la nostra username, quindi ci conviene subito portarci all'indirizzo 40320C ( nella finestra del dump della memoria ).
Subito dopo c'è un controllo sulla lunghezza del username che viene memorizzata in eax:
004010F9 . 73 23 JNB SHORT 001.0040111E
004010FB . 6A 10 PUSH 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
004010FD . 68 00304000 PUSH 001.00403000 ; |Title = "a85k CrackMe"
00401102 . 68 6A304000 PUSH 001.0040306A ; |Text = "Invalid Name or Serial Number"
00401107 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner = 7FFDC000
0040110A . E8 3F020000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
Quindi, come possiamo vedere, il nostro username deve avere una lunghezza minima di 10 caratteri. Se l'username ha una lunghezza minore di 10 caratteri il jnb non viene eseguito e quindi viene mostrata la messagebox che ci informa che i dati non sono validi
Se invece l'username ha una lunghezza di almeno 10 caratteri andremo a finire qui:
00401120 > 8A81 0C324000 MOV AL,BYTE PTR DS:[ECX+40320C] ;prendo un carattere
00401126 . 3C 00 CMP AL,0 ;vedo se ho finito
00401128 . 74 0F JE SHORT 001.00401139 ;se si salto
0040112A . 3C 5A CMP AL,5A ;confronto il carattere con 5A(il corrispettivo ASCII di Z)
0040112C . 76 02 JBE SHORT 001.00401130 ;se è minore o uguale salta perchè non c'è bisogno di fare nulla
0040112E . 2C 20 SUB AL,20 ;altrimenti sottraggo 20h al valore ascii del carattere, ottenendo il corrispettivo maiuscolo
00401130 > 8881 0C324000 MOV BYTE PTR DS:[ECX+40320C],AL ;salvo il carattere
00401136 . 41 INC ECX ;incremento il contatore che in questo caso fa da indice
00401137 .^ EB E7 JMP SHORT 001.00401120
Questa routine si occupa di rendere maiuscolo ogni carattere del username. Una volta finita questa operazione andremo a finire qui:
0040113B . 33DB XOR EBX,EBX
0040113D > 8A81 0C324000 MOV AL,BYTE PTR DS:[ECX+40320C]
00401143 . 80F9 04 CMP CL,4
00401146 . 74 26 JE SHORT 001.0040116E
00401148 > 3A83 B4304000 CMP AL,BYTE PTR DS:[EBX+4030B4] ;confronto con una costante
0040114E . 74 03 JE SHORT 001.00401153
00401150 . 43 INC EBX
00401151 .^ EB F5 JMP SHORT 001.00401148
00401153 > EB 03 JMP SHORT 001.00401158
00401155 > 83EB 09 SUB EBX,9
00401158 > 83FB 09 CMP EBX,9
0040115B .^ 77 F8 JA SHORT 001.00401155
0040115D . 8A93 A9304000 MOV DL,BYTE PTR DS:[EBX+4030A9]
00401163 . 8891 48324000 MOV BYTE PTR DS:[ECX+403248],DL
00401169 . 41 INC ECX
0040116A . 33DB XOR EBX,EBX
0040116C .^ EB CF JMP SHORT 001.0040113D
Qui cominciamo a calcolare le prime 4 cifre che andranno a comporre il nostro seriale. Perchè le prime 4? Perchè possiamo notare che avere cl=4 ci fa saltare ad un'altra porzione di codice che si occupa di calcolare il resto del seriale. Possiamo notare che i nostri primi 4 caratteri vengono ricercati in una stringa, infatti se nel dump ci portiamo all'indirizzo 4030B4 noteremo questa stringa:
004030C4 35 54 47 42 36 59 48 4E 37 55 4A 4D 38 49 4B 39 5TGB6YHN7UJM8IK9
004030D4 4F 4C 30 50 OL0P
In questa stringa, mischiate, ci sono tutte le lettere dell'alfabeto e le cifre da 0 a 9. Finquando il carattere non viene trovato, ebx viene incrementato. Praticamente viene effettuata una ricerca del carattere. Quando viene trovato saltiamo al cmp ebx,9. Da qui capiamo che le prime quattro componenti del seriale sono delle cifre perchè l'indice, cioè ebx viene decrementato mentre è maggiore di 9. Quando rientra nell'intervallo 0-9 viene preso il corrispettivo carattere dall'indirizzo 4030A9 dove troviamo quando segue:
Una volta preso il carattere corrispettivo viene memorizzato all'indirizzo 403248. Ovviamente agli indirizzi vanno sommati i valori dei registri che aumentano man mano che si va avanti.
Una volta calcolate le 4 cifre, saltiamo ad un'altra porzione di codice che si occupa di calcolare il resto.
00401175 . 41 INC ECX ;ecx = 5
00401176 . BE 0C324000 MOV ESI,001.0040320C ;ASCII "PROVAPROVA"
0040117B > 83F9 0E CMP ECX,0E
0040117E . 75 02 JNZ SHORT 001.00401182
00401180 . 74 1C JE SHORT 001.0040119E
00401182 > 8A06 MOV AL,BYTE PTR DS:[ESI]
00401184 . 32C1 XOR AL,CL
00401186 . 3C 5A CMP AL,5A ;Switch (cases 0..2F)
00401188 . 76 04 JBE SHORT 001.0040118E
0040118A . 2C 08 SUB AL,8
0040118C . EB 06 JMP SHORT 001.00401194
0040118E > 3C 30 CMP AL,30
00401190 . 73 02 JNB SHORT 001.00401194
00401192 . 04 0A ADD AL,0A ; Cases 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20,21,22,23,24,25,26,27,28,29,2A,2B,2C,2D,2E,2F of switch 00401186
00401194 > 8881 48324000 MOV BYTE PTR DS:[ECX+403248],AL ; Default case of switch 00401186
0040119A . 46 INC ESI
0040119B . 41 INC ECX
0040119C .^ EB DD JMP SHORT 001.0040117B
Qui viene inserito il segno '-' per separare le cifre dal resto del seriale. ESI punta al nostro username ed abbiamo ecx che viene incrementato e quindi ha valore 5. In parole povere la stringa viene xorata con ecx ( che man mano viene incrementato ), il risultato viene controllato. Se maggiore di 5A 'Z', al risultato viene sottratto 8 per riportarlo nel range dei caratteri. Se invece è minore di 30 '0', al risultato viene aggiunto 0Ah per riportarlo nel range delle cifre.
Siamo alla fine, in basso troviamo i seguenti pezzi di codice:
004011B0 . 68 2A324000 PUSH 001.0040322A ; |Buffer = 001.0040322A
004011B5 . 68 BA0B0000 PUSH 0BBA ; |ControlID = BBA (3002.)
004011BA . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd = 002B07FE ('a85k CrackMe',class='#32770')
004011BD . E8 80010000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
004011C2 . 83F8 0E CMP EAX,0E
004011C5 . 72 05 JB SHORT 001.004011CC
004011C7 . 83F8 0E CMP EAX,0E
004011CA . 76 16 JBE SHORT 001.004011E2
004011E7 . BF 2A324000 MOV EDI,001.0040322A
004011EC . 68 2A324000 PUSH 001.0040322A ; /String2 = ""
004011F1 . 68 48324000 PUSH 001.00403248 ; |String1 = ""
004011F6 . E8 89010000 CALL <JMP.&KERNEL32.lstrcmpA> ; \lstrcmpA
004011FB . 0BC0 OR EAX,EAX
004011FD . 75 1D JNZ SHORT 001.0040121C
Qui troviamo una GetDlgItemTextA che viene usata per rimediare al seriale che abbiamo inserito e con una lstrcmpA vengono confrontate le due stringhe. Con un OR EAX,EAX e relativo jnz viene verificato se le due stringhe sono uguali.
Bene, ora abbiamo tutti gli elementi per scrivere il nostro keygen.
Scriviamo il Keygen
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define dmaxS 255
int main(){
char username[dmaxS],serial[dmaxS];
char *str = "1QAZ2WSX3EDC4RFV5TGB6YHN7UJM8IK9OL0P ";
// str è la stringa che usiamo per calcolarci le prime quattro cifre del serial
int i=0,j,k=0;
printf("Soluzione crackme n#45 by GeXxO\n\n");
printf("Nome utente: ");
gets(username);
if(strlen(username)<10){
printf("Il nome utente deve essere lungo almeno dieci caratteri!!!\n");
}
else{
//controlliamo che la username contenga solo lettere e cifre
while((isalpha(username[i])||isdigit(username[i]) ||username[i]==' ')&& username[i]!='\0')
i++;
if(i!=strlen(username))
printf("I caratteri speciali non sono ammessi!!!\n");
else{
for(i=0;i<strlen(username);i++)
if(username[i]>='a' && username[i]<='z')
username[i]=username[i]-'a'+'A'; //rendiamo maiuscoli tutti i caratteri della username
//ci calcoliamo le prime 4 cifre del serial
for(i=0;i<4;i++){
j=0;
while(str[j]!=username[i])
j++;
while(j>9)
j-=9; //facciamo rientrare l'indice nell'intervallo 0-9
serial[k++]=j+'0'; //ne rendiamo il corrispettivo in ascii
}
j=0;
serial[k++]='-';//aggiungiamo il trattino che separa le prime 4 cifre dal resto del serial
i++;
for(;i<14;i++)
{
serial[k]=username[j++]^i;//xorriamo il seriale con l'indice i
if(serial[k]>'Z')//se il risultato è maggiore di Z, gli sottraiamo 8 per riportarlo nell'intervallo delle lettere
serial[k]-=8;
else if(serial[k]<'0')//se invece il risultato è minore di 0, gli aggiungiamo 10 per riportarlo nell'intervallo delle cifre
serial[k]+=10;
k++;
}
}
}
puts(serial);
system("pause");
return 0;
}
Ed ecco qui il nostro KeyGen :P
Note Finali
Come abbiamo potuto vedere, non essendo necessario unpacking e con un algoritmo molto semplice, il CrackMe risulta molto facile e soprattutto utile per chi è agli inizi. Ciao un saluto 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 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.