Soluzione del crackme #1 di active85k
keygen & auto-registration

Data

by "ZaiRoN"

 

19-o9-2oo2

UIC's Home Page

Published by Quequero


Che meticolosita' complimentim bello pure il keygen solo che non mi genera il serial e mi fa in pagefault se come nick metto quequeroo :P una revisionatina mi sa che gliela devi dare

 

....

Home page se presente: macchè...
E-mail: [email protected]

....

Difficoltà

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

 

vedremo come è possibile risolvere questo crackme.


active85k's crackme#1
keygen & auto-registration
Written by ZaiRoN

Keygen

Introduzione

oggi ho fatto un salto alla UIC e ho visto che sono stati aggiunti un sacco di crackme per cui ho deciso di partire dal primo e vedere un pò di che cosa si tratta.
il crackme è molto semplice; vedremo due diversi approcci al problema:
nella prima parte di questo tutorial studieremo come opera la routine di protezione e scriveremo un keygen (in assembly), dopodichè daremo un'ulteriore soluzione non contemplata nelle rules ma che è sempre molto divertente da applicare: aggiungeremo una sezione all'eseguibile e vi inseriremo il codice necessario all'autoregistrazione del crackme.

Tools usati

i tools sono sempre i soliti...
- un debugger (ollydbg)
- un editor esadecimale (hexworkshop)
- un qualsiasi compilatore (masm)

URL o FTP del programma

UIC - sezione crackme

Notizie sul programma

crackme...

Essay

 primo metodo: studio dell'algoritmo e scrittura di un keygen
lanciate OllyDbg e aprite il crackme in questione. appena il file è interamente caricato cominciamo a dare un'occhiata al codice. il crackme è stato scritto in assembly e non è difficile trovare dove vengono letti il name e il serial; iniziamo ovviamente con il name:
 
004010E0 PUSH 0040320C   <---------------------------- lParam = 40320C, dove mette il name
004010E5 PUSH 1E   <---------------------------------- wParam = 1E, numero massimo di caratteri da leggere
004010E7 PUSH 0D   <---------------------------------- Message = WM_GETTEXT (0x0D), tipo di messaggio
004010E9 PUSH 0BB9   <-------------------------------- ID, editbox relativo al name
004010EE PUSH DWORD PTR SS:[EBP+8]   <---------------- handle della dialog che contiene l'editbox
004010F1 CALL <JMP.&USER32.SendDlgItemMessageA>   <--- chiamata a SendDlgItemMessageA
004010F6 CMP EAX,0A   <------------------------------- eax è il numero di caratteri presenti nel name inserito
004010F9 JNB SHORT 0040111E   <----------------------- deve essere maggiore o uguale a 10
 
il name deve essere composto da almeno 10 caratteri altrimenti errore! dopo questo controllo ha inizio la routine di generazione del serial valido.
ecco qua cosa succede:
 
0040111E XOR ECX,ECX   <----------------------- il nostro contatore
00401120 MOV AL,BYTE PTR DS:[ECX+40320C]   <--- 40320C punta al name inserito. prende l'ecx-esimo carattere
00401126 CMP AL,0   <-------------------------- controlla che il carattere appena letto sia diverso da 0x00
00401128 JE SHORT 00401139   <----------------- salta se sono stati processati tutti caratteri del name
0040112A CMP AL,5A   <------------------------- confronto con 0x5A ('Z')
0040112C JBE SHORT 00401130   <---------------- salta se minore o uguale
0040112E SUB AL,20   <------------------------- altrimenti sottrae 0x20 per rendere il chr maiuscolo
00401130 MOV BYTE PTR DS:[ECX+40320C],AL   <--- sovrascrive il nuovo valore al vecchio
00401136 INC ECX   <--------------------------- incrementa il contatore
00401137 JMP SHORT 00401120   <---------------- torna su a leggere il prossimo carattere
 
fin qui niente di difficile! il name viene portato tutto in maiuscolo.
 
00401139 XOR ECX,ECX   <----------------------- anche stavolta è il nostro contatore
0040113B XOR EBX,EBX   <----------------------- altro contatore
0040113D MOV AL,BYTE PTR DS:[ECX+40320C]   <--- prende l'ecx-esimo carattere del name
00401143 CMP CL,4   <-------------------------- il ciclo viene ripetuto soltanto per 4 volte
00401146 JE SHORT 0040116E
00401148 CMP AL,BYTE PTR DS:[EBX+4030B4]   <--- 4030B4 punta alla sequenza Seq1: "1QAZ2WSX3EDC4RFV5TGB6YHN7UJM8IK9OL0P "
0040114E JE SHORT 00401153   <----------------- confronto tra il chr corrente del name e di Seq1
00401150 INC EBX   <--------------------------- se non sono uguali si passa al prossimo chr di Seq1
00401151 JMP SHORT 00401148   <---------------- e si ripete il controllo
00401153 JMP SHORT 00401158
00401155 SUB EBX,9   <------------------------- ebx = (ebx - 9) se ebx non è minore di 9
00401158 CMP EBX,9   <------------------------- ebx >= 9 ???
0040115B JA SHORT 00401155   <----------------- si: salta...no: non salta
0040115D MOV DL,BYTE PTR DS:[EBX+4030A9]   <--- 4030A9 punta alla sequenza Seq2: "0123456789"
00401163 MOV BYTE PTR DS:[ECX+403248],DL   <--- salva il valore letto
00401169 INC ECX   <--------------------------- incrementa il contatore
0040116A XOR EBX,EBX   <----------------------- azzera l'altro contatore
0040116C JMP SHORT 0040113D   <---------------- torna su ad eseguire una nuova iterazione
 
iniziamo a vedere qualcosa di interessante:)
vengono generati i primi 4 caratteri del serial valido. il codice parla da solo ma vediamo comunque come ottenere uno dei quattro caratteri.
supponiamo ad esempio che il carattere corrente letto dal name sia 'F':
'F' viene cercato all'interno di Seq1. la cosa che ci interessa è la posizione che il carattere ha all'interno di Seq1 (ebx rappresenta la posizione).
l'algoritmo di ricerca termina e il valore contenuto in ebx è 0x0E (14 in decimale). in questo caso ebx risulta maggiore di 9 per cui si esegue la sub in 401155 e ebx diventa 5 (0x0E - 9 :p). finalmente possiamo determinare il nuovo carattere del serial: è il 6° carattere di seq2 cioè 5 (vedi seq2+ebx in 40115D).
semplice no!?! continuiamo con la parte finale dell'algoritmo:
 
0040116E MOV BYTE PTR DS:[ECX+403248],2D   <--- dopo i 4 caratteri appena generati viene piazzato il carattere '-'
00401175 INC ECX   <--------------------------- ecx = 5
00401176 MOV ESI,0040320C   <------------------ esi punta al name inserito
0040117B CMP ECX,0E    <----------------------- il nostro contatore ecx dovrà arrivare fino a 14, per cui 9 iterazioni
0040117E JNZ SHORT 00401182
00401180 JE SHORT 0040119E   <----------------- salta se il serial valido è stato generato
00401182 MOV AL,BYTE PTR DS:[ESI]   <---------- al = generico chr del name
00401184 XOR AL,CL   <------------------------- al = al ^ cl
00401186 CMP AL,5A   <------------------------- confronto tra al e 'Z'. al deve essere minore di 0x5B
00401188 JBE SHORT 0040118E
0040118A SUB AL,8   <-------------------------- se non lo è si toglie 0x08
0040118C JMP SHORT 00401194   <---------------- salto a scrivere il carattere
0040118E CMP AL,30   <------------------------- controllo simile al precedente. al deve essere maggiore di 0x2F
00401190 JNB SHORT 00401194
00401192 ADD AL,0A   <------------------------- se non lo è si aggiunge 0x0A
00401194 MOV BYTE PTR DS:[ECX+403248],AL   <--- al rappresenta un'altro carattere del serial valido
0040119A INC ESI   <--------------------------- un paio di aggiornamenti
0040119B INC ECX
0040119C JMP SHORT 0040117B   <---------------- si torna su e si passa al prossimo chr del name
 
alla fine di questo ciclo abbiamo il nostro serial valido a partire dall'indirizzo 403248. i 9 caratteri finali del serial sono ottenuti semplicemente con uno xor (vedi 401184) e un controllo sul valore ottenuto che deve essere compreso tra 0x30 e 0x5A (estremi compresi). da quest'ultima parte di codice si evince anche il fatto che i serial sono tutti composti da esattamente 14 caratteri.
in realtà, credo che active85k volesse mettere il carattere '.' in fondo al serial valido. infatti:
 
0040119E ADD CL,20
004011A1 MOV BYTE PTR DS:[ECX+403248],CL   <--- mette 0x2E dopo i 14 caratteri messi in precedenza
004011A7 MOV BYTE PTR DS:[ECX+403249],0   <---- mette 0x00 dopo il '.'
 
il problema è che "ecx+403248" non punta alla posizione corretta perchè ecx è stato modificato dalla add precedente....ovviamente la mia è ovviamente soltanto una supposizione; lo sa soltanto lui cosa voleva fare :ppp.
perfetto siamo quasi in dirittura d'arrivo, è il momento di leggere il serial che abbiamo inserito:
 
004011AE PUSH 1E   <------------------------------ numero massimo di caratteri da leggere
004011B0 PUSH 0040322A   <------------------------ buffer per il serial inserito
004011B5 PUSH 0BBA   <---------------------------- ID del serial box
004011BA PUSH DWORD PTR SS:[EBP+8]   <------------ hWnd
004011BD CALL <JMP.&USER32.GetDlgItemTextA>   <--- chiamata a GetDlgItemTextA
004011C2 CMP EAX,0E   <--------------------------- controlla il numero dei caratteri letti
004011C5 JB SHORT 001.004011CC   <---------------- ...
004011C7 CMP EAX,0E   <--------------------------- devono essere esattamente 0x0E. lo sapevamo già ;)
004011CA JBE SHORT 001.004011E2   <--------------- ...
    ...
004011E2 MOV ESI,00403248   <--------------------- serial valido
004011E7 MOV EDI,0040322A   <--------------------- serial che abbiamo inserito
004011EC PUSH 0040322A
004011F1 PUSH 00403248
004011F6 CALL <JMP.&KERNEL32.lstrcmpA>   <-------- confronta i due serial
004011FB OR EAX,EAX   <--------------------------- e ovviamente se sono uguali non
004011FD JNZ SHORT 001.0040121C   <--------------- saltiamo e siamo registrati :)
004011FF PUSH 40
00401201 PUSH 00403000
00401206 PUSH 0040300D   <------------------------ Text = "Good work! Your serial number is valid! :D"
0040120B PUSH DWORD PTR SS:[EBP+8]   <------------ hOwner
0040120E CALL <JMP.&USER32.MessageBoxA>   <------- messagebox di congratulazioni
 
ecco qua come è implementato il check finale! un controllo tra il serial che abbiamo inserito e il serial calcolato a partire dal name.
 
bene, a questo punto ci rimane soltanto da scrivere un keygen.
in casi come questo, dove possiamo copiare paripari la routine di generazione del serial, la mia immane pigrizia mi porta a scrivere il keygen in assembly sfruttando il cut&paste...codice e source nel file allegato keygen.zip :)
 
secondo metodo: auto-registration
in casi come questo, è molto divertente fare in modo che il programma si autoregistri facendogli mettere il serial valido nel relativo box. tutto questo può non avere molto senso in quanto -in questo caso- basta invertire un jump per registrarsi ma così facendo non avremmo imparato niente quindi...rimbocchiamoci  le maniche e partiamo!
l'idea che sta dietro tutto è la seguente:
lasciamo calcolare il serial valido al programma; appena è pronto saltiamo al codice che abbiamo aggiunto e gli facciamo scrivere il serial valido nel box relativo al serial; dopodichè torniamo al programma originale e aspettiamo il messagebox di congratulazioni :)
il funzionamento dell'algoritmo che implementa il name/serial check lo abbiamo visto prima per cui sappiamo già quale sarà il giusto punto d'attacco:
 
0040117B CMP ECX,0E    <--------- controllo sul numero di cicli da eseguire
0040117E JNZ SHORT 00401182
00401180 JE SHORT 0040119E   <--- quando arrivo qui il serial valido sarà pronto
0040119E ADD CL,20   <----------- per cui qui metteremo il nostro jump
 
perfetto, abbiamo il jump da modificare ma...dove mettiamo il nuovo codice? bella domanda...
solitamente si va alla ricerca di un pò di byte '00' consecutivi e si piazza li il nuovo codice; purtroppo stavolta il discorso è diverso perchè all'interno del file ci sono pochi e piccoli spazi vuoti. a questo punto abbiamo due possibilità:
- provare ad utilizzare una di queste fessure e sperare che il nuovo codice ci entri tutto
- andare sul sicuro e aggiungere una nuova sezione in fondo al file
ovviamente noi aggiungeremo una nuova sezione! con i tools che circolano potremmo fare questo passaggio in 5 secondi ma non stavolta perchè faremo tutto a mano in 5 minuti!
 
prima di proseguire vorrei fare presente che questo non è un tutorial sul formato PE per cui vi consiglio di leggervi una guida specifica prima di procedere con il tutorial.
 
fondamentalmente i passi da eseguire sono i seguenti:
1) aggiungere un pò di byte in fondo al file
2) fixare qualche valore nel pe header/optional header
3) aggiungere un nuovo section header alla section table
 
partiamo subito col primo dei tre punti.
apriamo il crackme col nostro editor esadecimale di fiducia e posizioniamoci in fondo al file, precisamente all'offset 0xE00. aggiungiamo un pò di byte; per ora mettiamone 0x64 sovrastimando - siamo sempre in tempo a aggiungere/eliminare altri byte.
 
adesso dobbiamo in qualche modo far sapere al file che abbiamo aggiunto un pò di byte per cui andiamo a modificare opportunamente alcuni valori necessari per far eseguire correttamente il programma.
per questo tipo di lavoro, conviene avere sottomano una lista contenente tutti gli offset dei vari campi contenuti all'interno del pe; ne trovate una carina alla pagina: http://insel.heim.at/madeira/340943/Tuts/tut_pe.htm
i valori da fixare sono due: Number of Section e  Image Size.
- Number of Section (dimensione: word; offset: PE+06h):
rappresenta il numero di sezioni presenti nel file. il valore corrente è 4; ovviamente il nuovo valore sarà 5 (ricordatevi di inserire il valore seguendo la notazione intel: 05 00).
- Size of Image (dimensione: word; offset: PE+50h):
questo valore rappresenta la dimensione dell'immagine del file mappato in memoria. quale sarà il nuovo valore!?!
il valore del SizeOfImage è ottenuto sommando tutti gli headers delle sezioni, allineate al SectionAlignment.
il valore del SectionAlignment è 0x1000 mentre il valore dell'ImageSize corrente è 0x5000; noi abbiamo aggiunto soltanto una sezione con dimensione inferiore al valore del SectionAlignment per cui non dobbiamo fare altro che eseguire la seguente addizione:
Size of Image = old_SizeOfImage + SectionAlignment = 0x1000 + 0x5000 = 0x6000
 
fatte queste due piccole modifiche non ci resta altro da fare che inserire una nuova (o un nuovo!?!) Section Header nella Section Table.
prima di proseguire è meglio dare un'occhiata a quali informazioni sono contenute in un generico SectionHeader:
 
+0 8byte name   <----------------------------- nome della nuova sezione (al massimo 8 caratteri)
+8 dword Virtual Size   <--------------------- dimensione effettiva della sezione (n° byte del nuovo codice)
+C dword Virtual Offset   <------------------- RVA a partire dal quale la nuova sezione sarà mappata in memoria
10 dword SizeOfRawData   <-------------------- grandezza della sezione all'interno del file (fisico)
14 dword PointeToRawData (o Raw Offset)   <--- indirizzo fisico a partire dal quale viene mappata la sezione nel file
18 dword pointerToRelocations   <------------- questo e i prossimi tre non ci interessano...
1C dword PointerToLinenumbers   <-------------
20 word NumberOfRelocations   <---------------
22 word NumberOfLineNumbers   <---------------
24 dword Characteristics   <------------------ caratteristiche della sezione...
 
la nuova SectionHeader sarà messa appena dopo l'ultima presente nel file (la .rsrc); ogni sezione ha un header di 0x28 byte per cui metteremo i dati della nuova sezione a partire dall'offset 0x258 (dall'inizio del file...).
 
dopo questa piccola descrizione passiamo ad inserire i nuovi valori!

- Name: diamo un bel nome alla nostra creazione! potete metterci ciò che volete basta che rientriate nel limite degli 8 caratteri!

- Virtual Size: non sappiamo ancora da quanti byte sarà composto il nuovo codice quindi per ora mettete pure 64h (tutta la sezione). se proprio siete pignoli lo sistemate alla fine:p

- Virtual Offset: visto che la sezione '.rsrc' è mappata a partire da 0x4000 e il SectionAlignment è 0x1000, qui ci mettiamo un bel (0x4000 + 0x1000) = 0x5000

- Size of RawData: questo valore dipende dal VirtualSize (0x64) e dal FileAlignment (0x200). in particolare è il il multiplo più piccolo del FileAlignment maggiore del VirtualSize. per cui abbiamo un SizeofRawData pari a 0x200

- Raw Offset: questo è facile: (size of RawData + RawOffset) della precedente sezione. per cui: 0xC00 + 0x200 = 0xE00

- Pointer to Relocations, Pointer to Line Numbers, Number of Relocations, Number of Line Numbers: mettete pure tutti i byte a 0x00

- Characteristics: che caratteristiche diamo a questa sezione? essendo una sezione contenente codice che deve essere eseguito sicuramente la sezione dovrà avere attivi i tag ' code' e ' executable'. quindi si ha: 0x20 + 0x20000000 = 0x20000020. 

perfetto, abbiamo appena aggiunto una nuova sezione al crackme! semplice no!!!
adesso possiamo finalmente aggiungere le poche linee di codice necessarie all'autoregistrazione.
 
solitamente la funzione più adatta allo scopo è SetDlgItemText. purtroppo però tale funzione non è importata dal crackme per cui, cosa facciamo? la aggiungiamo alla import table!?! non ne vale la pena! diamo bene uno sguardo alle varie api importate dal programma. vedete niente di interessante? esatto, possiamo utilizzare SendDlgItemMessageA:
 
LRESULT SendDlgItemMessage(
    HWND hDlg,   <-------- handle della dialog che contiene l'editbox al quale vogliamo mandare il messaggio
    int nIDDlgItem,   <--- identificatore del controllo che riceve il messaggio (edit box relativo al serial)
    UINT Msg,   <--------- messaggio da mandare (WM_SETTEXT)
    WPARAM wParam,   <---- non usato per questo tipo di messaggio (quindi NULL)
    LPARAM lParam   <----- puntatore alla stringa da mettere nell'edit box (il serial valido)
);
 
come potete vedere, i parametri da passare alla funzione sono molto intuitivi e credo non ci sia bisogno di un'ulteriore spiegazione per cui passiamo direttamente al codice da inserire nella nuova sezione:
 
00405000 68 48 32 40 00   PUSH 00403248   <--------------------- 403248 punta al serial valido
00405005 6A 00            PUSH 0   <---------------------------- wParam
00405007 6A 0C            PUSH 0C   <--------------------------- valore numerico del messaggio WM_SETTEXT
00405009 68 BA 0B 00 00   PUSH 0BBA   <------------------------- identifica il serial box
0040500E FF 75 08         PUSH DWORD PTR SS:[EBP+8]   <--------- handle della dialog
00405011 E8 12 08 B5 BF   CALL USER32.SendDlgItemMessageA   <--- chiamata della funzione
00405016 E9 93 C1 FF FF   JMP 001.004011AE   <------------------ jump per tornare al codice originale...
 
il codice nella nuova sezione è tutto qua; facciamo l'ultima modifica in 40119E per deviare il normale flusso del crackme:
 
0040119E E9 5D 3E 00 00   JMP 00405000   <--- salto alla nuova sezione
 
come vi avevo detto in precedenza il nuovo codice è veramente esiguo (i pignoli possono mettere 0x1B al VirtualSize ed eliminare i byte 0x00 superflui :ppp).
ok, tutto sembra essere pronto per la prova finale: verificare se effettiamente il crackme si autoregistra.
inseriamo un name (almeno 10 caratteri), un serial (14 caratteri a caso) e premiamo 'Check!'.
WoW...funziona :)
 
                                                                                                            ZaiRoN
 

Note finali

un saluto a tutti gli amici della uic! alla prossima... 

Disclaimer

è soltanto un crackme :ppp