KeygenMe 2 by no!se | ||
Data |
by "Zero_G" |
|
22/08/2004 |
Published by Quequero | |
|
Sempre impeccabile, non potrei aggiungere altro per te :) |
e la nostra breve vita è circondata dal sonno..." - W. Shakespeare - |
|
|
|
Difficoltà |
( )NewBies (X)Intermedio ( )Avanzato ( )Master |
Salve a tutti!
Ultimamente mi sono dato
ai crackme... sarà perché quando tocco qualche programma commerciale comincio a
temere la punizione dello zio Bill? UAHUAHUAH!! :-D
Introduzione |
Tools usati |
URL o FTP del programma |
Notizie sul programma |
Essay |
Adesso possiamo farlo partire per vedere di cosa si
tratta: due TextField (nome e seriale) e 3 pulsanti, uno dei quali serve per
controllare se il codice corrisponde al nome immesso; per adesso può bastare,
chiudiamolo e carichiamolo in OllyDbg. Da lì possiamo settare i soliti
breakpoint scrivendo "bpx GetWindowTextA" e "bpx GetDlgItemTextA"
nell'apposita CommandBar in basso a sinistra; se lo facciamo ripartire e
scriviamo il nome ed un seriale fittizio, dopo aver premuto REGISTER succede una
cosa strana: il programma si termina senza darci la possibilità di fare il
debug! grrrr...
Cosa si è inventato il nostro amico(?) no!se per farci
perdere tempo? sicuramente qualcosa che una volta rilevato Olly in memoria,
chiude tutto senza pietà... come fare? :-/
Innanzitutto bisogna sapere che la funzione addetta alla terminazione di un processo è ExitProcess che sta dentro kernel32.DLL; bene, allora cominciamo proprio da quella e settiamoci sopra un bel breakpoint con "bpx ExitProcess". Se riavviamo il programma, stavolta, Olly si ferma prima e ci ritroviamo qui:
Le funzioni a 00403AA8, 00403B24 e a 00403BA0 caricano delle stringhe che verranno poi ricercate nei titoli delle finestre attive mediante la funzione 00403C1C; in caso di esito positivo, il processo viene terminato. Avete capito l'arcano? Se in giro c'è Olly, Softice o Win32Dasm, lui se ne accorge; per bypassare questi controlli basta cambiare i tre JE in JMP (li ho sottolineati nel listato). Fatelo (sapete come, no?), salvate il file patchato e ricaricaricatelo nel debugger.
Se inseriamo di nuovo il nome ed il seriale e poi premiamo Register, il programma esce lo stesso! :-( Perché? eppure abbiamo evitato i controlli sul debugger... sì, ma solo quelli sui titoli delle finestre; c'è anche un altro modo per accorgersi della sua presenza: la funzione IsDebuggerPresent. Se ci mettete un breakpoint sopra come abbiamo fatto per ExitProcess vi ritroverete qui:
Non so perché carichi due volte in memoria il nome inserito... comunque subito dopo possiamo vedere i due controlli che servono per assicurarsi che sia composto da almeno 4 caratteri (il JGE significa "maggiore o uguale"); in caso positivo, si prosegue avanti fino a
Alla fine abbiamo LOCAL.1 che contiene la lunghezza diviso 2 e LOCAL.2 quella originale modulo 2; più avanti troviamo invece un loop che lavora sul seriale a partire dal secondo carattere
per semplicità, ve lo traduco in C (in assembly può essere utile, ma evidenziarvi la sintassi del C forse confonde soltanto, quindi lo lascio così):
alla fine (dopo 0040415E), in ESI avremo un valore che è il risultato di questo calcolo effettuato su tutto il nome inserito.
Subito dopo c'è un altro loop che, dopo alcune inizializzazioni, va da 00404174 a 0040418F.
senza incasinare troppo il listato con i commenti, traduco anche questo in C, così magari si capisce meglio:
al termine del loop, in EBX ci trovate un altro valore che viene subito confrontato con quello presente in ESI (l'avevamo calcolato prima e nessuno l'ha toccato, vi ricordate?); se EBX > ESI viene chiamata la funzione 00403958, altrimenti la 00403980
La funzione 00403958 restituisce il valore 1CC7D16F, mentre la 00403980 restituisce FA91E187: per completezza vi riporto comunque lo snippet delle due funzioni, ma non fanno altro che eseguire delle operazioni su alcuni valori esadecimali statici che si trovano nel segmento dati compreso tra 004066D8 e 004066E4 (potete verificarlo anche voi facendo "Follow in Dump"); l'unica cosa che le differenzia è l'ordine con cui vengono fatte le operazioni.
Tornando a dove eravamo, oltre ad EBX e ESI, adesso abbiamo un nuovo valore anche in EDI (1CC7D16F o FA91E187 a seconda del valore degli altri due registri) di cui viene fatto il modulo (valore assoluto) poche righe dopo (caro no!se, ma se in EDI ci metti due costanti, perché dopo ti devi girare le scatole a farci un test per il valore assoluto se sai già quanto valgono? :-) ).
Nella parte successiva, spiccano delle stringhe
strane, come se fossero delle parti di un seriale; ed infatti lo sono! Il
seriale corretto è così costruito "RK302(EDI)Q-ZX(ESI)A-PM(EBX)OU", dove le parentesi indicano il
risultato ASCII della funzione 004038F8 applicata al valore contenuto
nel registro indicato (più precisamente si tratta della funzione wsprintf() con
l'opzione di formattazione %u per ogni valore).
Man mano che procedete con gli step,
vedrete i PUSH che accumulano alternatamente nello stack un pezzo fisso ed un
valore ASCII (nella stack window in basso a destra potete osservare il seriale
giusto mentre viene costruito, ricordatevi che si legge dal basso in
alto).
Dopo varie elucubrazioni per memorizzare nei vari
registri d'appoggio il seriale giusto e quello immesso, quando vi ritroverete
all'indirizzo 00404247, finalmente vediamo il nostro bel codicione in chiaro nello
stack, pronto per essere "pescato", perché è quello che corrisponde al nostro
nome! :-) se volete, appuntatevelo da qualche parte...
Signore e signori,
poco più giù ecco a voi un bel good/bad boy jump (sfido chiunque a non
riconoscerlo...)
Poco prima potevamo fare serial fishing, adesso sapete anche dov'è che si può fare una patch... ve lo devo dire? nooooo.... dai lo so che lo sapete... no? via giù... basta sostituire il JNZ a 0040424E con 2 NOP (è quello sottolineato). ;-p
Dato che l'algoritmo è totalmente reversibile, per costruire un keygen basta munirsi di un qualsiasi compilatore del vostro linguaggio preferito e riscrivere ad alto livello quelle operazioni che abbiamo visto prima (da 004040DC a 00404247), stampando infine la stringa risultante; per (vostra) comodità vi riassumo (sempre in C) i vari passi...
int length = strlen( name );
if( length < 4 )
{
strcpy( serial,
"**il nome deve essere almeno 5 caratteri!**" );
return;
}
int local1 = length/2;
int local2 =
length%2;
int ESI = 0;
for( int i = 1; i <= local1;
i++ )
{
ESI = ( ESI + name[i]*local1 ) ^
0xCC;
ESI = abs( ESI );
}
int EBX
= 0;
for( int i = local1; i < length; i++ )
{
EBX = ( EBX * name[i]*local2 ) ^
0xDD;
EBX = abs( EBX );
}
int EDI
= EBX > ESI ? 0x1CC7D16F : 0xFA91E187;
EDI = abs( EDI
);
wsprintf(serial, "RK302%uQ-ZX%uA-PM%uOU", EDI, ESI, EBX
);
}
Beh, direi cha abbiamo capito praticamente tutto di questo crackme (un keygenme a sentire no!se), ma... possiamo fare di più!! sento già le voci.. "ma che vole 'sto pazzo?? non gli basta un keygen?? è tutto il pomeriggio che leggo numeri e simboli senza senso, ora spengo il computer e mando a quel paese lui e tutta la UIC..." ;-p
sì, certo che basta, anzi è senza dubbio la soluzione migliore, però, come era già successo altre volte, senza fare troppa fatica a SCRIVERE un keygen, è possibile trasformare il programmuzzo malefico nel suo stesso generatore di chiavi. (il tanto decantato keygen injection) di solito è meglio sempre lasciare il programma integro, ma di sicuro si imparano un sacco di cose in questo modo...
Diamo un'occhiata a cosa succedeva alla fine della
generazione del seriale giusto (3 code snippets più in alto): all'indirizzo
00404203, la chiamata alla funzione 00403180 concatenava i 7 pezzi del
seriale e poneva il risultato nello stack (nello slot di memoria compreso tra i
vari frammenti e le stringhe usate per l'anti-debug... date un'occhiata allo
stack in basso a destra); a 00404208 questo finisce in EAX, poi da
EAX in
EDX e
poi i vari LEA riscrivono gli indirizzi dentro i registri ed il seriale scompare (la
funzione 00403134 legge il seriale da EAX e lo sovrascrive con la sua
lunghezza, '21' in esadecimale); il PUSH
EAX a 00404225 mette nello stack la lunghezza
del seriale... però attenzione, a 00404226 il seriale giusto viene
ricaricato dallo stack in EAX e poi pushato (infatti quando siete steppate fino a
0040422A, lo trovate in cima allo stack); infine, a 00404240, il nostro
seriale viene pushato un attimo prima di fare StrCmpNA().
Come saprete, dopo che si
esce da una funzione, lo stack dei parametri ad essa relativo viene svuotato, ed
infatti, appena proseguiamo a 0040424C, non c'è più traccia dei due
seriali, né nei registri, né nello stack... ma non è così! no!se non si è
ricordato che aveva copiato nello stack (NON pushato) anche il seriale completo,
senza cancellarlo prima di passarlo come parametro a StrCmpNA(); vi
ricordate dove l'aveva messo? La chiave di volta è la chiamata alla funzione
00403180, che scrive il seriale intero non in cima allo stack, ma più in basso,
insieme ad i pezzi che lo compongono. Per sapere dove si trova, basta ritornare
all'istruzione subito successiva alla call, cioè il MOV EAX,DWORD PTR SS:[EBP-21C] che si trova a 00404208: questo ci dice da quale punto
dello stack (SS sta per StackSegment) viene prelevato il seriale per copiarlo in
EAX.
L'avete visto? è EBP-21C, cioè si prende il
puntatore alla base dello stack per questa funzione (BP sta per
BasePointer) e si calcola un offset di -21C; senza stare a fare troppi conti, se
durante il debug cliccate su quell'istruzione, Olly traduce nella
status-pane in basso gli indirizzi assoluti:
siccome nessuno lo ha cancellato da lì, potete
tranquillamente scorrere lo stack (sempre nella stack-window in basso a destra)
e verificarne la presenza; non è detto che vi venga lo stesso indirizzo
assoluto, perché l'allocazione è dinamica ed il BP viene aggiustato di
conseguenza, ma è l'indirizzo relativo EBP-21C che vi interessa; infatti, se
scorrete più in giù, ritroverete lo schemetto good/bad boy che abbiamo visto
prima, però... guardate bene cosa succede: per avvertirci che il seriale è
sbagliato, viene utilizzata la funzione SetWindowTextA() che scrive il messaggio
"Fake serial !!!" nel TextBox del programma...
Ma allora, perché non farsi stampare
direttamente il seriale giusto al posto del messaggio d'errore? La stringa da
scrivere è quella che viene pushata all'indirizzo 00404262, e dopo
00404229
abbiamo il nostro bel seriale in cima allo stack: saltando direttamente da
0040422A
a 00404267, in EAX verrebbe caricato l'handle della finestra e poi pushato (istruzioni
00404267
e 0040426C): perfetto, avremmo il seriale giusto e l'handle corretto come parametri
di SetWindowTextA()!
Ecco il codice modificato:
Se premete il destro e fate "Copy to Executable --> All Modifications", "Copy All" e poi "Save File" avrete il vostro bel keygen nato dallo stesso programma per cui serve! :-)
Un'ultima nota: forse vi starete chiedendo perché
non abbiamo semplicemente sostituito il PUSH KeygenMe.00404318 ("Fake Serial
!!!") con un PUSH DWORD PTR
SS:[EBP-21C] (il seriale giusto nello stack)...
bene, se provate a farlo, capirete il perché: l'istruzione originale è lunga 5
bytes (da 00404262 a 00404267), mentre quella che vorremmo
inserire ne occupa 6 (se la sostituite vedrete che andrà da 00404262 a
00404268); i NOP invaderebbero anche l'istruzione successiva (MOV EAX,DWORD PTR DS:[4066A0]) rendendo errato l'handle hWnd.
Quando un byte fa la
differenza...
-=Zero_G=-
Note finali |
Beh, anche questo crackme ha avuto il trattamento completo: analisi, unpacking, reversing dell'algoritmo e, a scelta, serial fishing, patching o keygen injection. sono commosso... ;-p
Ringrazio tutta la UIC che ogni giorno scopro
sempre di più essere un posto pieno di gente veramente valida.
In particolare
saluto il capoccia Que, AndreaGeddon, LonelyWolf, Alfa, il giovanissimo
Federico, folletto_burlone, rewil, giuseppe e tutti gli altri di cui adesso non
ricordo il nome... siete tanti, sapete? ;-)
Come al solito, ringrazio i
Dream Theater (mmmhh.. sai cosa gliene frega a loro se li ringrazio o
no...) che ho avuto modo di sentire a Firenze, ed anche i miei chitarristi
preferiti, Joe Satriani e Steve Vai, che dopo il Pistoia Blues
hanno davvero lasciato il segno... :-D
Disclaimer |
Vorrei ricordare che il software va comprato e non rubato, e che 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.