WinAce 2.6 Keygen
RSA120 rulez

Data

by "Graftal"

 

05/Nov/2005

UIC's Home Page

Published by Quequero

..

Mi piace quando mettono RSA nei programmi... Bravissimo graft, come al solito hai fatto un lavorone, chiaro, pulito e semplice da capire!

..

We r0x0r

E-mail: [email protected]
Graftal - Azzurra - #crack-it #cryptorev #informazionelibera

you b0x0rz

Difficolt�

( )NewBies ( )Intermedio (G)Avanzato ( )Master

 


Allegato

Introduzione

In questo tute tratteremo di winace, noto programma per comprimere file. Ci metteremo a reversarlo e faremo un keygen in asm ;). La versione che ho usato io � la 2.6b5.

Tools usati

Tools usati:
OllyDbg
RSA Tool2 v1.1 
MASM32 
e IDA Pro per una piccola parte.

URL o FTP del programma

www.winace.com

Essay

Cominciamo subito col dire che l'algo usato dal winace � basato sull'rsa. La teoria che sta dietro all'rsa non � argomento del tutorial, vi rimando al bel tute di evilcry 'Rsa defeating 'n reversing' per l'infarinatura, reperebile su cryptorev. Se siete poi interessati ad un maggiore approfondimento, leggetevi qualche libro oppure andate a fare un bel full-immersion di 3 giorni di matematica crittografica come ho fatto io :P
Nonostante ci� comunque, useremo un bel tool per trovare 'p' e 'q', oltre che, ovviamente, 'd' a partire dai soliti 'n' ed 'e'.
Ah, un piccolo appunto: in questo tutorial ho analizzato molto codice, ma fidatevi quando vi dico che � solo quello indispensabile: diciamo che reversando il winace, l'ho trovato un target davvero molto dispersivo, con molto codice inutile e poco efficiente. Per questo motivo andr� a spiegare pi� a fondo alcune parti (oltre a commentare sempre e abbondantemente il codice asm) mentre in altre ho preferito non dilungarmi troppo copiando il decompilato, certe volte poche parole bastano a spiegare delle call "mostruose". Detto questo, tutto il disasm qua incollato � stato "ordinato" da parte mia, nel senso che ho cercato di renderlo pi� leggibile: il codice � molto, e se si capisce con difficolt� non ha senso scrivere (e leggere :) un tutorial.
Ok, penso sia il caso di cominciare =)

__________
--=| Reversing |=--
����������

Cominciamo il nostro approccio facendo finta di non sapere nulla del programma vittima. Facciamolo partire e vediamo subito una bella paginetta blu piena di incitazioni: compra il programma qua, compra il programma l� ecc ecc. Chiudiamo la pagina e nel caso stessimo usando il programma per un periodo di tempo superiore a quello di evaluation, addirittura una piccola dialog in alto rompe ancora. Va bene, ci� basta per invogliarci a reversarlo: andiamo su Help/Enter your registration code, mettiamo come user Graftal e come serial 1234567890. Clicchiamo OK e una finestrella stile msgbox ci informa che il serial � sbagliato.
Benissimo, chiudiamo tutto e apriamo subito ollydbg caricando winace.exe. Per la nostra gioia, � packato, pi� precisamente con l'aspack. Anche qua, un packer tritato e ri-tritato, non vedo quindi il motivo di un manual unpacking: unpackatelo con un qualsiasi unpacker e ricaricate olly. Il programma d� un msgbox di errore poich� l'import table � un po' 'smanettata' ma a noi non interessa: possiamo semplicemente noppare la call a MessageBox o modificare qualche jump, o semplicemente lasciar perdere. A questo punto dobbiamo trovare un punto debole in cui mettere qualche breakpoint. Si comincia sempre con i soliti, MessageBox, GetWindowText, GetDlgItemText et similia. Facciamo partire il prog, mettiamo user e serial e notiamo subito che ollydbg dorme sonni tranquilli. Togliamo i bp e cominciamo ad analizzare la string reference in cerca delle solite frasi. Si trova solo qualcosa come "Serial required", "Username", "Company name" ecc. Insomma, niente che possa far accendere qualche lampadina ;). Una cosa che mi piace fare quando sono in cerca di un punto in cui brekkare, � andare nell'about box. Ecco che ci sono le info di registrazione, nel nostro caso il programma � "Unregistered". Sempre tenendo olly aperto, � arrivato il momento di decompilare il programma col DeDe: ebbene s�, � programmato in delphi =) (evviva l'ottimizzazione del codice :P). DeDe ci dovrebbe dare una gran mano, facciamogli quindi analizzare l'eseguibile e dopo un po' di attesa possiamo vedere da dove cominciare. Andiamo nei Forms dove sono presenti le finestre del programma. Purtroppo, nemmeno qua la famosa lampadina si accende, tanto che ci troviamo immersi nel buio :\. Andiamo allora nelle Procedures ma anche qua niente di niente. Chiudiamo allora DeDe perch� come abbiamo visto, non ci � di aiuto. A questo punto dovreste aver cominciato a pensare che, semplicemente, il winace.exe _non_ contiene la procedura di registrazione e addirittura nemmeno quella che fa vedere l'aboutbox. Tra le dll che ci sono nella cartella di installazione del programma, acetools.dll e acev2.dll sono le uniche che potrebbe includere qualcosa di interessante. Lasciate per� perdere acetools.dll poich� � acev2.dll che contiene il necessario. Possiamo usare il wpe (incluso nel WARK) per vedere le funzioni esportate ma purtroppo crasha :P (almeno nella versione 1.3). Apriamolo con IDA e guardiamo prima di tutto la string reference: verso il fondo troviamo tante frasi del tipo "Registration succesfull..", "Unregistered version" e simili, ma ci� si riferisce all'ActiveAce, quindi fate attezione perch� non � quello che stavamo cercando. (Nonostante ci�, la chiave di registro in cui viene salvato il serial ha in effetti ActiveAce nel nome). Avrete pero probabilmente notato che nella Names Window c'� una funzione mooolto sospetta, ACERegister: ebbene s�, abbiamo (finalmente :P) trovato il punto d'attacco. Se avete tenuto aperto olly con winace.exe caricato, fate pure che chiudere tutte le altre istanze del debugger e concetriamoci sull'eseguibile. Volendo potevamo accorciare notevolmente la nostra ricerca andando a guardare tra le prime funzioni usate dal winace.exe dove perlappunto si vede la chiamata a ACERegister: ho per� preferito fare un giro un po' pi� largo cos� da analizzare tutte le opzioni che avevamo a disposizione, invece che gettarci subito sulla pi� facile ;). Ok, mettiamo un bp e facciamo partire il programma, inseriamo i dati e premiamo OK. Finalmente olly brekka e possiamo cominciare a reversare.
Steppiamo nella call premendo due volte F7, saltiamo la prima call steppandoci sopra mentre la seconda call comincia ad interessarci:

..
MOV EAX,DWORD PTR SS:[EBP+C] ;mette il serial in eax
CALL acev2.00851CF3          ;strlen
CMP EAX,1B                   ;serial lungo 1Bh?
JE SHORT acev2.0086D964      ;se s�, allora ok
..
> MOV EDX,DWORD PTR SS:[EBP+C]
MOV EAX,acev2.008BA214       ;ASCII "1234567890"
CALL acev2.00851E4A          ;strcpy
MOV EAX,acev2.008BA214       ;stringa appena copiata in eax
CALL acev2.00851A36          ;inutile
CALL acev2.008609AD          ;ecco la nostra call

Siccome il serial deve essere lungo 1Bh (ovvero 27) caratteri, riavviamo il programma e inseriamo un dummy serial della lunghezza giusta.
Io ho usato 1234567890ASDASDASDQ1234567. Questa volta il je salta e possiamo entrare nella call 008609AD con tranquillit�. Qui, la prima call che incontriamo � inutile (fa solo un mov eax, 1 e oltrettutto appena usciti dalla call c'� un test eax, eax seguito da je .. insomma, niente di pi� inutile :P); entriamo nella seconda call e finalmente arriviamo alla prima parte veramente importante del codice.

..
MOV EBX,18                    ;18h bytes da azzerare
XOR EDX,EDX                   ;0
LEA EAX,DWORD PTR SS:[EBP-30] ;indirizzo da azzerare
CALL acev2.00853C0D           ;memset
MOV EBX,18                    ;18h bytes da copiare
MOV EDX,acev2.008BA217        ;ASCII "4567890ASDASDASDQ1234567"
LEA EAX,DWORD PTR SS:[EBP-18] ;indirizzo in cui copiare
CALL acev2.00853C55           ;strncpy

Il codice � chiarissimo, l'unica cosa che mi premeva di sottolineare, � che i primi 3 char del serial vengono proprio "tagliati", tanto da farci insospettire: � probabile che ci siano 3 caratteri speciali da mettere all'inizio. Ma ne parleremo pi� avanti, per adesso continuiamo ad analizzare la prima routine:

..
XOR EAX,EAX
MOV ECX,DWORD PTR [8605A3]               ;"0123456789ABCDEFGHIJKMNPQRSTUVWXYZ"
JMP SHORT acev2.008606F4                 ;jmp a xor edx, edx
008606DB /INC EDX
008606DC |CMP EDX,22                     ;siamo arrivati alla fine dell'alfabeto?
008606DF |JGE SHORT 008606EE             ;se si, passa al char dopo
008606E1 |MOV BL,BYTE PTR DS:[EAX+EBP-18];char del serial
008606E5 |CMP BL,BYTE PTR DS:[ECX+EDX]   ;l'hai trovato nell'alfabeto?
008606E8 \JNZ SHORT 008606DB             ;
se no,passa al char dell'alfabeto successivo
008606EA MOV BYTE PTR [EAX+EBP-18],DL    ;salva l'index array dell'alfabeto
008606EE INC EAX                         ;passa al char del serial dopo
008606EF CMP EAX,18                      ;finito il serial?
008606F2 JGE SHORT 008606F8              
;se se si, salta
008606F4 > XOR EDX,EDX
008606F6 ^JMP SHORT 008606E1

In questo loop il serial viene modificato, da insieme di caratteri, diventa insieme di numeri: bisogna ovviamente fare attenzione, non � una specie di atoi, semplicemente quando un carattere del serial viene trovato nell'"alfabeto", l'indice di array di quest'ultimo viene usato per creare il "serial numerico": 07060504 0A000908 1A0A0D1A 0D1A0A0D 03020118 07060504

008606F8 >MOV ECX,17 ;loop di 18h iterazioni (nota il jl qua sotto)
008606FD JMP SHORT 00860704
008606FF >DEC ECX
00860700 TEST ECX,ECX
00860702 JL SHORT 0086073B
00860704 > XOR EDX,EDX
00860706 >/IMUL EAX,DWORD PTR DS:[EDX+EBP-30],22;ebp-30 � inizialmente vuoto
0086070B |MOV DWORD PTR DS:[EDX+EBP-30],EAX     ;salva il risultato
0086070F |ADD EDX,4                             ;passa alla dword dopo
00860712 |CMP EDX,14                            ;finito il loop?
00860715 ^\JNZ SHORT acev2.00860706             ;se no, salta
00860717 MOVZX EAX,BYTE PTR[ECX+EBP-18];primo numero del "serial numerico"+ecx (= 17h) in eax
0086071C ADD DWORD PTR SS:[EBP-30],EAX ;lo aggiunge nella locazione di memoria
0086071F XOR EAX,EAX                   ;azzera eax
00860721 >/MOV EDX,DWORD PTR [EAX+EBP-30];prende il primo numero della locazione di memoria
00860725 |SHR EDX,18                     ;shr per vedere se dei byte non stanno nella dword
00860728 |ADD DWORD PTR DS:[EAX+EBP-2C],EDX;in quel caso lo aggiunge nella dword precedente
0086072C |MOV BYTE PTR [EAX+EBP-2D],0;mette uno 0 cos� in una dword sono presenti 3byte
00860731 |ADD EAX,4                  ;contatore delle iterazioni
00860734 |CMP EAX,18                 ;finite queste iterazioni?
00860737 ^|JE SHORT acev2.008606FF   ;se si, ricomincia il loop precedente questo
00860739 ^\JMP SHORT acev2.00860721  ;
se no, ricomincia questo loop

Questa parte di codice, � in realt� un loop suddiviso in due loop pi� piccoli: � da questo loop che noi ricaviamo una variabile numerica che sar� ci� che l'RSA andr� a criptare: � quindi molto importante; in particolare, usando il dummy serial scritto precedentemente tale numero sar�: 00301B1E 0052FF51 006C2D0F 006E478B 00E7A6B1. Notate gli zeri presenti all'inizio di ogni dword.

..
0086073B >MOV ESI,008BA230         ;buffer dove salvare il valore
00860740 LEA ECX,DWORD PTR [EBP-30];il numero trovato prima
00860743 LEA EDI,DWORD PTR [ECX+14];punta alla fine di tale numero
00860746 /MOV EBX,3          ;3 iterazioni nella prossima call
0086074B |MOV EDX,ECX        ;� come se fossero dei push
0086074D |MOV EAX,ESI        ;si vede che la call usa ebx, edx, eax
0086074F |CALL 00853C55      ;mette i 3 byte della dword nel buffer 8BA230
00860754 |ADD ESI,3          ;esi punta a quell'indirizzo
00860757 |ADD ECX,4          ;passa alla dword dopo
0086075A |CMP ECX,EDI        ;finite le dword?
0086075C \JNZ SHORT 00860746 ;se no, rifai il loop

Anche qua roba molto elementare: mette a posto le dword di prima, in modo da non avere zeri all'inizio.
Ok, finita questa parte, troviamo dopo poche righe di codice un push con l'indirizzo del "numero" (facciamo che chiamare cosi questo numero qua: 1E 1B 30 51 FF 52 0F 2D 6C 8B 47 6E B1 A6 E7) seguito da una call. Di sicuro avviene tutto l�

..
ENTER 360,0
SUB EBP,19A
MOV ECX,EAX
MOV EBX,11E                   ;numero di byte da processare
XOR EDX,EDX                   ;valore da mettere in quei byte
LEA EAX,DWORD PTR SS:[EBP+7A] ;indirizzo in quei mettere il valore in edx
CALL 00853C0D                 ;memset (ZeroMemory in questo caso)
MOV EBX,10                    ;numero di byte da copiare
MOV EDX,00860B95              ;indirizzo da cui copiare
EAX,DWORD PTR SS:[EBP+7E]     ;indirizzo in cui copiare
CALL 00853C55                 ;strncpy
MOV DWORD PTR SS:[EBP+7A],0   ;mette 0 in [indirizzo-1dw]
XOR EAX,EAX                   ;contatore
00860EFD >/CMP WORD PTR [EAX+EBP+7E],0;finita la stringa?
00860F03 |JE SHORT 00860F0C           ;se si, esci dal loop
00860F05 |INC EAX
00860F06 |INC EAX
00860F07 |INC DWORD PTR SS:[EBP+7A]   ;inc il numero di word presenti nel numero
00860F0A ^\JMP SHORT 00860EFD         ;rifai il loop
MOV EBX,11A
XOR EDX,EDX
LEA EAX,DWORD PTR SS:[EBP-1C2]
CALL acev2.00853C0D ;ZeroMemory
MOV EBX,10
MOV EDX,ECX
LEA EAX,DWORD PTR SS:[EBP-1C2] ;il numero
CALL acev2.00853C55            ;ri-copialo nello stack

Tutto questo casino serve solo per prepararsi ad effettuare il crypting (che avviene nella prossima call): bisogna che facciate comunque l'abitudine al casino che c'� nel winace, perch� di codice davvero inutile ne � pieno (vi ho pure risparmiato tutte quelle righe di codice presenti nello stesso winace.exe che non fanno assolutamente NULLA e servono solo per distrarre i leims :P). Oltretutto, ai programmatori � piaciuto molto pacioccare con lo stack, lo azzerano e lo usano come una mega-variabile di continuo: la cosa positiva � che almeno tutte le cose assolutamente necessarie le tengono nelle variabili, siccome nello stack potrebbero essere cancellate e/o manomesse, e questo ci aiuta nella fase del reversing, poich� siamo sempre sicuri se una cosa � utile o meno.

LEA EBX,DWORD PTR SS:[EBP+7A] ; Modulo ('N')
LEA EDX,DWORD PTR SS:[EBP-1C6]; Il nostro numero
LEA EAX,DWORD PTR SS:[EBP-A6] ; Zona inutilizzata
CALL 00860E49                 ; cripta!

Il modulo N scelto dai programmatori � il seguente: E9EF370510CB9DB54CA78CC94A3495. Notate anche che ogni variabile ha sempre una dword che la precede, in cui si trova un numero che indica la quantit� di word presenti nella variabile stessa. Questo viene in aiuto nei loop, dove si pu� sapere con velocit� e soprattutto praticit� (siccome la velocit�, a quanto pare, non � l'obiettivo di questi programmatori, un codice peggiore solo il vb lo fa :P) il numero di iterazioni da fare nei loop. In eax troviamo il buffer che riceve il valore del numero criptato dall'rsa. Bene, adesso entriamo nella call e vediamo di trovare le "prove" che indicano che il winace usa l'rsa (ricordate che noi non dovremmo saperlo :P) e successivamente, ci mettiamo a trovare un modo per codare un keygen.

..
LEA EDI,DWORD PTR SS:[EBP-11E8]
LEA EAX,DWORD PTR DS:[EDI+11E]
MOV DWORD PTR SS:[EBP-4],EAX
LEA EAX,DWORD PTR DS:[EDI+10C2]
MOV DWORD PTR SS:[EBP-8],EAX
00860E79 />MOV EBX,DWORD PTR [EBP-4]   ;indirizzo dove mettere il risultato
00860E7C |MOV EDX,EDI                  ;modulo (la 'n')
00860E7E |MOV EAX,00860A77             ;2
00860E83 |CALL 00860C85                ;fai i calcoli
00860E88 |ADD EDI,11E                  ;contatore
00860E8E |ADD DWORD PTR SS:[EBP-4],11E ;passa all'indirizzo dopo
00860E95 |CMP EDI,DWORD PTR SS:[EBP-8] ;finito?
00860E98 \^JNZ SHORT 00860E79          ;se no, rifai
MOV EBX,ECX         ;indirizzo vuoto dove mettere il risultato
MOV EDX,ESI         ;il numero da moltiplicare
MOV EAX,ESI         ;per che cosa moltiplicare (ovvero il numero stesso)
CALL acev2.00860C85 ; moltiplica (eleva il numero alla seconda)
MOV EBX,ECX         ;indirizzo
MOV EDX,ECX         ;il risultato di prima
MOV EAX,ESI         ;il numero
CALL acev2.00860C85 ; moltiplica (eleva alla terza)
MOV EBX,ECX         ;il risultato di prima
LEA EDX,DWORD PTR SS:[EBP-11E8] ;modulo n
MOV EAX,ECX         ;il risultato di prima
CALL acev2.00860D53 ;finalmente criptalo

Questa parte ha in realt� bisogno di un po' di spiegazioni, ho infatti preferito non incollare tutta la marea di codice che usa il programma solo per moltiplicare due numeri il cui risultato � pi� grande di una dword (o due numeri che sono anch'essi pi� grandi di una dword).
Inizialmente, il programma prende il modulo e tramite il loop che ho copiato qua sopra, raddoppia tale valore e ogni volta salva il numero risultante in una variabile (da notare come ognuna di queste variabili siano a distanza di 11Eh byte tra di loro). Dopo aver creato molti di questi numeri (che in realt� sono delle costanti, poich� ricavati da un modulo, che altro non � che una costante) passa a elevare alla terza il nostro numero: questo � un passo importante; infatti, fino ad adesso non avevamo avuto modo di pensare che l'algo potesse essere basato su rsa. In questo punto del programma, abbiamo incontrato quindi una costante e un elevamento a potenza. E' per� ancora poco, anche perch� molti algo si basano su delle costanti senza usare l'rsa, ecco quindi che l'elevamento a potenza da solo non ci dice in realt� nulla. Dobbiamo quindi procedere ancora nel codice per renderci meglio conto cosa ci troviamo di fronte.

00860D9E>/CMP DWORD PTR SS:[EBP-8],0  ;finito il loop di 0Fh iterazioni?
00860DA2 |JL acev2.00860E3D            ;se si, basta cosi
00860DA8 |CMP ECX,DWORD PTR SS:[EBP-4] ;cmp il numero di word da processare
00860DAB |JNZ SHORT acev2.00860DBC
00860DAD |MOV EAX,DWORD PTR SS:[EBP-18]
00860DB0 |CMP WORD PTR DS:[EAX],0
00860DB4 |JNZ SHORT acev2.00860DBC
00860DB6 |DEC ECX
00860DB7 |JMP acev2.00860E2E
00860DBC>|CMP ECX,DWORD PTR SS:[EBP-4] ;di nuovo (mah)
00860DBF |JG SHORT acev2.00860DD7
00860DC1 |JNZ acev2.00860E2E
00860DC7 |MOV EBX,ECX
00860DC9 |MOV EDX,DWORD PTR SS:[EBP-1C] ;lea edx, [modulo]
00860DCC |MOV EAX,ESI               ;in eax le ultime 8 word dell'elevamento a potenza 3
00860DCE |CALL acev2.00860BB2       ;la analizziamo dopo (call1)
00860DD3 |TEST EAX,EAX              ;se eax = -1
00860DD5 |JL SHORT acev2.00860E2E   ;salta questa iterazione
00860DD7>|MOV DWORD PTR [EBP-C],0F  ;numero di iterazioni da fare nel loop qua sotto
00860DDE |MOV EAX,DWORD PTR [EBP-20];una delle costanti calcolate prima (*-const1-)
00860DE1 |MOV DWORD PTR SS:[EBP-10],EAX
00860DE4 |JMP SHORT acev2.00860DF6  ;comincia il loop
00860DE6>|/SUB DWORD PTR SS:[EBP-10],11E
00860DED ||DEC DWORD PTR SS:[EBP-C]
00860DF0 ||CMP DWORD PTR SS:[EBP-C],0
00860DF4 ||JL SHORT acev2.00860E2E
00860DF6>||IMUL EAX,DWORD PTR [EBP-C],11E;in base alle iterazioni fatte..
00860DFD ||ADD EAX,DWORD PTR SS:[EBP-24];..calcola l'indirizzo di una delle costanti
00860E00 ||MOV EDX,DWORD PTR DS:[EAX] ;numero di word da processare
00860E02 ||MOV DWORD PTR SS:[EBP-14],EDX
00860E05 ||CMP ECX,EDX               ;se ecx > edx
00860E07 ||JG SHORT acev2.00860E1B   ;vai alla call sotto
00860E09^||JNZ SHORT acev2.00860DE6  ;se minore rifai loop
00860E0B ||LEA EDX,DWORD PTR DS:[EAX+4] ;cfr *-const1-
00860E0E ||MOV EBX,ECX               ;numero di word
00860E10 ||MOV EAX,ESI               ;il numero (quello ^3)
00860E12 ||CALL 00860BB2             ;la analizziamo dopo (call1)
00860E17 ||TEST EAX,EAX              ;rifai il loop?
00860E19^||JL SHORT 00860DE6         ;se eax = -1 s�
00860E1B>||PUSH DWORD PTR SS:[EBP-14];numero di word da processare
00860E1E ||MOV EBX,ESI
00860E20 ||MOV EDX,DWORD PTR SS:[EBP-10] ;il numero (^3)
00860E23 ||MOV EAX,ESI         ;cfr *-const1-
00860E25 ||CALL acev2.00860BF1 ;la analizziamo dopo (call2)
00860E2A ||MOV ECX,EAX
00860E2C^|\JMP SHORT 00860DE6  ;rifai loop
00860E2E>|INC ECX
00860E2F |DEC ESI ;vai 1 word indietro all'interno del numero (quello elevato ^3)
00860E30 |DEC ESI
00860E31 |SUB DWORD PTR [EBP-18],2
00860E35 |DEC DWORD PTR [EBP-8];dec le iterazioni
00860E38^\JMP acev2.00860D9E

Siamo finalmente arrivati al loop "decisivo", quello che cripta la stringa. L'algo non � troppo complesso, ma � fatto in una maniera che ci viene molto difficile, se non impossibile, fare un keygen senza sapere che l'rsa � implementato nell'algo. Abbiamo 2 call da analizzare, quindi penso sia il caso di procedere, cos� possiamo finire tutto il codice che abbiamo trovato qua e poi passare ad una spiegazione pi� dettagliata.

PUSH ECX
PUSH ESI
LEA ECX,DWORD PTR DS:[EBX+EBX-2] ;una delle molte costanti calcolate prima
ADD EAX,ECX ;calcola l'indirizzo del numero (^3)
ADD EDX,ECX ;e quello di una delle costanti
00860BBC>/DEC EBX
00860BBD |CMP EBX,-1
00860BC0 |JE SHORT acev2.00860BEC     ;finito il loop?
00860BC2 |MOVZX ESI,WORD PTR DS:[EAX] ;l'ultima word del numero
00860BC5 |MOVZX ECX,WORD PTR DS:[EDX] ;l'ultima word della costante
00860BC8 |DEC EAX
00860BC9 |DEC EAX
00860BCA |DEC EDX
00860BCB |DEC EDX
00860BCC |CMP ESI,ECX ;uguali? se si rifai, non devono esserlo
00860BCE^\JE SHORT acev2.00860BBC
MOVZX ECX,WORD PTR DS:[EAX+2] ;mette le due word in ecx..
MOVZX EAX,WORD PTR DS:[EDX+2] ;..ed eax
CMP ECX,EAX
JLE SHORT acev2.00860BE4 ;ecx minore di eax?
MOV EAX,1                ;se no, va bene
POP ESI
POP ECX
RETN
00860BE4 >MOV EAX,-1     ;se si, niente da fare
POP ESI
POP ECX
RETN
00860BEC >XOR EAX,EAX
POP ESI
POP ECX
RETN

Non penso ci sia molto da dire su questa semplice call, se non che viene chiamata due volte nel loop principale (ovvero quello che abbiamo analizzato poco pi� sopra); adesso capiremo anche a cosa serve, infatti andiamo ad analizzare assieme la call2..

00860BF1 PUSH ESI
00860BF2 PUSH EDI
00860BF3 ENTER 10,0
00860BF7 MOV DWORD PTR SS:[EBP-10],0 ;variabili usate per vedere se..
00860BFE MOV DWORD PTR SS:[EBP-4],1; ..il risultato del sub �..
00860C05 MOV DWORD PTR SS:[EBP-8],0; ..negativo o positivo
00860C0C >/DEC ECX ;loop di 9 iterazioni (perch� 9 sono le word)
00860C0D |CMP ECX,-1
00860C10 |JE acev2.00860C7C ;finito il loop?
00860C16 |MOVZX ESI,WORD PTR DS:[EAX]  ;una word del numero trovata dalla call1
00860C19 |MOV DWORD PTR SS:[EBP-C],ESI ;salvalo nello stack
00860C1C |INC EAX                      ;passa alla word dopo
00860C1D |INC EAX
00860C1E |CMP DWORD PTR SS:[EBP+10],0 ;cmp numero_di_word_da_processare, 0
00860C22 |JE SHORT acev2.00860C2E     ;finito il loop?
00860C24 |MOVZX ESI,WORD PTR DS:[EDX] ;una word della costante (trovata dalla call1)
00860C27 |DEC DWORD PTR SS:[EBP+10]   ;dec word_da_processare
00860C2A |INC EDX                     ;passa alla word dopo
00860C2B |INC EDX
00860C2C |JMP SHORT acev2.00860C30
00860C2E>|XOR ESI,ESI
00860C30>|CMP DWORD PTR SS:[EBP-10],0 ;ebp-10 � settato se nell'iterazione precedente..
00860C34 |JE SHORT acev2.00860C37     ;..il risultato del sub � stato un numero negativo
00860C36 |INC ESI
00860C37>|CMP ESI,DWORD PTR SS:[EBP-C]
00860C3A |JBE SHORT acev2.00860C43
00860C3C |MOV EDI,1   ;risultato negativo?
00860C41 |JMP SHORT acev2.00860C45
00860C43>|XOR EDI,EDI ;risultato positivo?
00860C45>|MOV DWORD PTR SS:[EBP-10],EDI ;salva
00860C48 |MOV EDI,DWORD PTR SS:[EBP-C]  ;in edi una word del numero
00860C4B |SUB EDI,ESI                   ;in esi una word di una delle costanti: sottrai
00860C4D |MOV WORD PTR DS:[EBX],DI;salva il risultato sovrascrivendo la word del numero
00860C50 |INC EBX                 ;passa alla word dopo
00860C51 |INC EBX
00860C52 |TEST DI,DI
00860C55 |JE SHORT acev2.00860C5D ;non dovrebbe saltare mai Oo'
00860C57 |MOV ESI,DWORD PTR SS:[EBP-4]
00860C5A |MOV DWORD PTR SS:[EBP-8],ESI
00860C5D>|CMP DWORD PTR SS:[EBP+10],0
00860C61 |JNZ SHORT acev2.00860C77
00860C63 |CMP DWORD PTR SS:[EBP-10],0 ;check numero di iterazioni rimanenti
00860C67 |JNZ SHORT acev2.00860C77
00860C69 |TEST ECX,ECX
00860C6B |JLE SHORT acev2.00860C7C
00860C6D |MOV EAX,DWORD PTR SS:[EBP-4]
00860C70 |ADD EAX,ECX
00860C72 |MOV DWORD PTR SS:[EBP-8],EAX
00860C75 |JMP SHORT acev2.00860C7C
00860C77>|INC DWORD PTR SS:[EBP-4]    ;inc iterazioni_fatte
00860C7A^\JMP SHORT acev2.00860C0C    ;ricomincia il loop
00860C7C>MOV EAX,DWORD PTR SS:[EBP-8] ;return iterazioni_fatte
00860C7F LEAVE
00860C80 POP EDI
00860C81 POP ESI
00860C82 RETN 4

Questa � la call in cui avviene materialmente il cripting. Il tutto viene calcolato tramite il sub edi, esi: questo ha una importante conseguenza a mio parere, il fatto che l'rsa viene quasi "nascosto", o almeno si tenta di nasconderlo, dietro questa istruzione. Mi spiego meglio. Quando pensiamo all'rsa, le prime due cose che ci vengono in mente sono C = M^e mod N e M = C^d mod N: ci viene in mente, di conseguenza, un elevamento a potenza e una divisione. Sebbene non venga preso in considerazione il quoziente della divisione, bens� il resto, inzialmente non avevo pensato che, in effetti, fare il mod e fare la sottrazione � la stessa identica cosa, potete provarci con due numeri presi dal loop qua sopra e vedrete cosa intendo dire: la call1 che abbiamo analizzato prima, fa s� che il sub e il mod siano in effetti equivalenti, infatti devono sussistere delle condizioni tra due numeri affinch� si possa dire sub = mod; la condizione � che ecx sia maggiore di eax (cfr il disasm della call1 spiegata poco pi� sopra). Fino a qua abbiamo visto la maggior parte del codice, ma ci manca ancora una parte fondamentale: come avviene, materialmente, il checking del serial? Dobbiamo uscire da un po' di call e steppare poi fino a questo punto (tutta la parte precedente il codice qua sotto � inutile per i nostri scopi, quindi ve la risparmio ;).

..
00860A35 MOV EAX,acev2.008BA1C8 ;"Graftal"
00860A3A CALL acev2.0086084E ;testa il serial
00860A3F TEST EAX,EAX
00860A41 JE SHORT acev2.00860A5A
..

Steppiamo:

..
MOV EBX,EAX                   ;eax = userlen
LEA EDX,DWORD PTR SS:[EBP-40] ;ebp-40 = username uppercase
MOV EAX,-1                    ;valore d'inizio con cui si calcora il checksum
CALL acev2.0085A364           ;calcola uno "Pseudo-CRC32-Checksum" dell'username
MOV DWORD PTR DS:[ECX],EAX    ;salva il checksum ritornato dalla call
LEA ESI,DWORD PTR DS:[ECX+4C] ;primi 3 char del serial uppercase (ovviamente)
MOV EBX,3                     ;3 char da processare
MOV EDX,ESI                   ;il risultato di prima viene usato per calcolare il csum
CALL acev2.0085A364           ; calcola uno "Pseudo-CRC32-Checksum" dei 3 char
MOV DWORD PTR DS:[ECX],EAX    ;salva il risultato ritornato dalla call

Se vi ricordate, verso l'inizio, abbiamo notato che i primi 3 char del serial non vengono proprio considerati: ecco il motivo; ci sono in effetti 3 char speciali che vengono usati per fare il check. Possiamo scegliere 3 caratteri qualsiasi, se provate a modificare il keygen, vedrete che con una qualsiasi combinazione di char (tranne WIN) il serial cambia ma viene accettato lo stesso dal winace. Con moltissima probabilit� questi 3 char sono "ACE" =). Supponendo ci�, riavviamo il programma e inseriamo come serial ACE4567890ASDASDASDQ1234567 e ritorniamo qui. Steppiamo nella prima call, ricordandoci che le due chiamate sono uguali:

PUSH EBP
MOV EBP,ESP
PUSH ESI
PUSH ECX
PUSH EBX
MOV ESI,EDX              ;username uppercase in esi
MOV ECX,EBX              ;userlen in ecx
/SUB ECX,1               ;userlen iterazioni
|JB SHORT acev2.0085A389 ;finito il loop?
|MOVZX EBX,AL            ;al in ebx
|SHR EAX,8               ;shr eax di 1 byte
|XOR BL,BYTE PTR DS:[ESI];xor bl, byte ptr [username]
|INC ESI                 ;inc username
|SHL EBX,2               ;(risultato dello xor) * 4
|ADD EBX,acev2.008B6CDC  ;usa ebx come indice di array per questa variabile
|XOR EAX,DWORD PTR DS:[EBX] ;xor indirizzo ricavato dalla variabile con eax
\JMP SHORT acev2.0085A36E   ;ricomincia il loop
POP EBX                     ;in eax il risultato del loop
POP ECX
POP ESI
POP EBP
RETN

Qui troviamo una mega-variabile, pi� in particolare si tratta di una CRC-Table, con 256 entry: dobbiamo implementarlo nel keygen, quindi segnamoci da qualche parte l'indirizzo =). Viene da s� che se � presente una CRC-Table, quello qua sopra � in effetti il loop che viene usato dal CRC32 per calcolare il checksum: l'unica cosa diversa � che manca un "not" alla fine (vedi l'algo del CRC32 se hai dubbi = ). Questa stessa call � chiamata una seconda volta con "ACE" come stringa di cui fare il checksum, o meglio, pseudo-checksum, siccome avete visto che � leggermente diverso dalla versione "ufficiale" :).
Usciamo dalla call, steppiamo sopra la seconda call, che tanto � uguale a questa, e analizziamo il codice successivo:

MOVZX EAX,WORD PTR DS:[ECX+6D] ; una word particolare della 'C' calcolata dal programma
MOV EDX,DWORD PTR DS:[ECX]     ; checksum di "ACE"
SHR EDX,10                     ;mette la hiword nella loword
CMP EDX,EAX                    ;sono uguali?
JNZ acev2.0086099A             ;se no, beggar off

Adesso sappiamo anche a cosa deve essere uguale la word presente in ECX+6D, vediamo allora quali sono i prossimi check:

MOV EAX,ECX         ;indirizzo da dove ricavare i numeri per i check
CALL acev2.00870550 ;fai altri check (la analizziamo dopo)
TEST EAX,EAX
JNZ acev2.0086099A  ;se salta, beggar off
CMP WORD PTR DS:[ECX+73],4F ;alcuni numeri non vanno bene
JE acev2.0086099A           ;se anche uno di questi je salta, beggar off
CMP WORD PTR DS:[ECX+73],0C8
JE acev2.0086099A
CMP WORD PTR DS:[ECX+73],0AA
JE acev2.0086099A
CMP WORD PTR DS:[ECX+73],0AB
JE acev2.0086099A
MOV EBX,3
MOV EDX,acev2.0088445F ; ASCII "WIN"
MOV EAX,ESI
CALL acev2.00853C6C        ;compara i primi 3 char del serial con "WIN"
TEST EAX,EAX
JE SHORT acev2.0086099A    ;se sono uguali, beggar off
MOV EDX,DWORD PTR DS:[ECX] ; checksum di "ACE"
SHR EDX,10                 ;mette l'hiword nella loword
AND EDX,0FFFF              ;inutile (bah..)
MOV EAX,DWORD PTR DS:[ECX] ;lo rimette in eax
SHR EAX,4                  ;sposta di 4 bit a destra
XOR EDX,EAX                ;li xorra
AND EDX,7
MOVZX EAX,BYTE PTR DS:[ECX+71] ;prende un byte del numero criptato
SAR EAX,5                      ;� come un shr
CMP EDX,EAX                    ;se sono uguali..
JNZ SHORT acev2.0086099A       ;..va bene, se no beggar off
MOVSX EAX,WORD PTR DS:[ECX+73] ;una word del numero criptato
SUB EAX,0A375
AND EAX,0FF
MOV EAX,DWORD PTR DS:[EAX*4+8B6CDC] ;eax � indice di array della CRC-Table
SHR EAX,3
AND EAX,7                      ;lavora un po'
MOVZX EDX,BYTE PTR DS:[ECX+69] ;prende un byte del numero criptato
SAR EDX,2               ;lavora un po'..
AND EDX,7
CMP EAX,EDX             ;sono uguali?
JE SHORT acev2.0086098  ;se si, va bene, se no, beggar off
XOR EDX,EDX
MOV EAX,ECX             ;il solito indirizzo con dentro tutto il necessario
CALL acev2.00860780     ;check per vedere se il sn � "expired" (lo analizziamo dopo)
TEST EAX,EAX            ;eax = 1?
JE SHORT acev2.008609A2 ;se s�, beggar off
MOV BYTE PTR DS:[ECX+C],0
XOR EAX,EAX
JMP SHORT acev2.008609A7 ;jmp fine
MOV EAX,1
LEAVE
POP ESI
POP EDX
POP ECX
POP EBX
RET

Il codice � molto chiaro, non ci rimane che analizzare due call che abbiamo tralasciato e poi abbiamo finito il reversing =).

00870550 PUSH EDX
00870551 MOVZX EDX,BYTE PTR DS:[EAX+68] ;un byte del serial criptato
00870555 MOVZX EAX,BYTE PTR DS:[EAX+6D] ;un altro byte
00870559 XOR EAX,EDX                    ;li xorra
0087055B TEST AL,2                      ;al = 2?
0087055D JNZ SHORT acev2.00870580       ;se no, beggar-off
..                                      ;il resto � inutile per noi

E adesso passiamo alla call che controlla se il sn � expired o meno:

..
00860789 MOV DL,BYTE PTR DS:[EAX+75] ;un byte della 'C'
0086078C ADD DL,75                   ;ci aggiunge 75h
0086078F SUB DL,BYTE PTR DS:[EAX+73] ;sottrae il tutto con un altro byte della 'C'
00860792 MOVZX EBX,DL                ;DL = 0? (fate MOLTA attenzione, DL e non EDX!!)
00860795 TEST EBX,EBX 
00860797 JE SHORT acev2.008607F9     ;allora tutto ok!
..

Questa � una parte molto importante e molto delicata, ed � anche ci� che rende il winace in grado di accettare pi� serial per lo stesso username ;). A questo proposito, per capire meglio il tutto, vi rimando alla sezione qua sotto dove analizzo l'algoritmo.

E con questo ci ricaviamo gli ultimi due numeri: tutti i rimanenti, ovvero quelli non settati, li azzeriamo; il numero finale che otteniamo sar� il seguente:

00XXXXXX00A0000071AF000000XXAD

Dove vedete le X si possono inserire molti numeri diversi, a seconda di come si "affronta" la call descritta nel disasm qua sopra =).

Con tutto sto casino di codice, spero proprio di non aver dimenticato nulla :P.
Direi che � il caso di metterci a spiegare tutto d'un fiato il disasm visto fin'ora, cos� da avere una visione chiara della situazione: solo a quel punto, possiamo passare a codarci il keygen =)

_________
--=| Algorithm |=--
���������

Dividiamo l'algo in pi� passi e analizziamoli, prestando attenzione al punto 8 dove viene descritta in maniera approfondita la routine di check del serial:

1) Per prima cosa viene fatto un strlen al serial per vedere se � lungo 27 caratteri
2) Assegna un numero ad ogni carattere del serial in base alla posizione del carattere stesso nell'alfabeto.
-IMPORTANTE- L'alfabeto scritto dai programmatori contiene prima tutti i numeri da 0 a 9 e subito dopo tutti i caratteri dalla A alla Z SENZA la L e la O.
3) Tramite un loop si moltiplica ognuno di questi numeri trovati per 22h e se il risultato non sta in una dword, si aggiunge il sovrappi� in un'altra e si continua cos� finch� non si ottengono 5 dword in cui ognuna ha 3 byte effettivamente usati e il 4� byte sempre uguale a 0 (ci� a causa del "sovrappi�" di cui parlavo prima).
4) Ordina le 5 dword in modo che il risultato sia contenuto in sole 4 dword: ci� avviene eliminando gli zeri iniziali di ogni dword e riordinando il tutto.
5) Prende una costante, ovvero la nostra 'n', e tramite un loop abbastanza lungo, lo moltiplica ogni volta per 2 e salva i risultati in indirizzi ognuno a distanza 11Eh l'uno dall'altro. Da qua sappiamo che la 'n' vale E9EF370510CB9DB54CA78CC94A3495. Ci manca solo pi� la 'e'.
6) Comincia la fase di crypting vera e propria: eleva alla terza il numero (quello del passo 4, contenuto in un totale di 4 dword). Da qui capiamo che 'e' vale 3.
7) Cripta il numero elevato alla terza usando l'equazione C = M^e mod N, dove 'C' � il testo criptato, 'M' il testo in chiaro, 'e' vale 3 e il modulo vale E9EF370510CB9DB54CA78CC94A3495.
La routine di crypting usa una sottrazione nel momento in cui deve fare il mod. Ci� che pu� confondere, � anche il fatto che questa sottrazione avviene sempre con una costante diversa (le costanti sono state ricavate al punto 5) e quindi potremmo non capire subito che si tratta di rsa. Diciamo che � la parte forse pi� "confusionaria" di tutto il programma =).
8) Adesso avviene il check della 'C' trovata dal serial inserito da noi, e la 'C' corretta. Per fare ci�, si comincia prima di tutto calcolando un checksum dell'username e dei primi 3 caratteri del serial (che possono essere tutto tranne che "WIN": noi useremo "ACE") usando l'algoritmo del CRC32 leggermente modificato. Partendo dal presupposto che la 'C' si trova all'indirizzo 0x008BA230, la word all'indirizzo 8BA235 deve essere uguale al checksum di "ACE" (ricordo ancora una volta che tale checksum cambia sempre perch� viene calcolato a partire dal checksum dell'username, e non dalla costante 0xFFFFFFFF); la 'C' corretta per adesso � quindi

00 00 00 00 00 00 00 00 71 AF 00 00 00 00 00

Una volta passato il primo check, entriamo nella call 870550 e il programma prende l'ultimo byte della 'C' (ricordo che debuggando vediamo il numero al contrario) e lo xorra con AF, che abbiamo trovato qua sopra: se il risultato � 2, allora va bene, altrimenti beggar-off. Ecco quindi che l'ultimo byte deve valere AD:

00 00 00 00 00 00 00 00 71 AF 00 00 00 00 AD

Benissimo, usciamo dalla call ignorando le istruzioni restanti e il prossimo check riguarda il 3� e 4� byte, ovvero la 2� word: non deve essere uguale n� a 4F, n� a C8, n� ad AA e nemmeno ad  AB.  A questo punto i primi 3 char del serial devono essere diversi da "WIN" e cos� possiamo procedere nei check. Il programma carica in edx ed eax il checksum di "ACE": nel primo registro mette la hiword nella loword e nel secondo registro lo shifta a destra di 4 bit, li xorra e il risultato viene and-ato con 7. Prende il 6� byte della 'C', fa uno sar di 5 bit (possiamo considerarlo uguale ad uno shr) e confronta il valore di prima e questo: se sono uguali, va bene. Basta shiftare a sinistra di 5 bit il primo numero per trovare il valore corretto:

00 00 00 00 00 A0 00 00 71 AF 00 00 00 00 AD

Proseguendo, si prende la 2� word e ci sottrae 0xA375, fa un and con 0xFF e usa il byte risultante (moltiplicato a 4) come indice di array nella CRC-Table; il numero trovato viene shiftato a destra di 3 bit e poi and-ato con 7 [1]. Ora prende il penultimo byte della 'C', lo shifta a destra di 2 e lo and-a con 7 [2]: se il numero [1] � uguale a [2] allora tutto ok. Questa volta ci sono troppe incognite per poter trovare i valori corretti, ma dall'altro lato, possiamo dare valori arbitrari e calcolare di conseguenza il [2]. Tuttavia, non dobbiamo scordarci di analizzare anche la call dove viene controllato se il serial risulta expired, perch� � proprio qua che troviamo la relazione tra il 2� e 4� byte del numero criptato. Se vi ricordate il codice analizzato sopra, prende il 2� byte, ci aggiunge 0x75, sottrae il risultato al 4� byte e se DL vale 0, allora � tutto ok. Di conseguenza, pu� andare teoricamente bene qualsiasi numero del tipo 0x100, 0x200, 0x300 ecc, basta che abbia 2 zeri alla fine =). Teniamo a mente questo, perch� andremo a codarci un bel keygen con serial diversi per uno stesso username ;). Nella nostra attuale analisi, useremo come 4� byte il numero 0x01 (che fantasia :D) e quindi abbiamo che:

X + 0x75 - 0x01 = 0xXXXXXX00

Ho messo delle X nel risultato solo per ricordarvi che � inutile tenerne conto, poich� nei calcoli con i byte si tiene in considerazione solo l'ultimo byte. Ora, da questa equazioncina otteniamo una X = 8C. E finalmente, la 'C' cos� ottenuta:

008C010100A0000071AF00000004AD

� quella corretta! Ma non � l'unica, ce ne sono tantissime altre (in totale 65532), e noi riprodurremo un numero diverso ad ogni avvio del keygen in modo da ottenere sempre serial diversi.

Ok, questo � quanto c'era da dire sull'algoritmo. Passiamo al prossimo punto, ovvero.. codiamoci un keygenerator :)


______________
--=| KeyGen Coding |=--
   ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½   

Il keygen che ho codato � fatto completamente in win32asm, e la routine per il decrypting l'ha reso abbastanza lungo, esattamente (e quando dico "esattamente" intendo proprio "E-S-A-T-T-A-M-E-N-T-E" ;p) 1000 righe di codice: abbastanza lungo da copiarlo qua ovviamente :). Ho comunque preferito commentare pooooco poco il sorgente; non c'� niente di troppo complicato da essere spiegato, � solo.. lungo =). A proposito, per generare i due byte casuali (vedi l'ultima parte del punto 8 qua sopra) ho semplicemente usato un GetTickCount: tenete conto che essendo espresso in ms, la loword del valore ritornato dalla api � diversissimo ogni volta che si genera un serial, quindi � proprio perfetto: veloce e preciso :D. Non penso ci sia altro da dire, se non che se vi serve qualche funzione, fate pure che rubacchiarla dal mio sorgente, ma almeno datemi un paio di crediti :P.

                                                                                                                Graftal

Note finali

Fiuu, anche questo tute � finito! Cavoli, ce ne ho messo davvero di tempo a scriverlo, e pure impegno (;p), spero davvero che vi sia piaciuto, per me � stato un gran piacere e divertimento ;)). Ringraziamenti (in ordine sparso e con scuse agli eventuali individui dimenticati):

ZaiRoN (per avermi dato un'idea su come fare la GUI e perch� � sempre gentile e disponibile ;), evilcry (sei un grande, amico, non c'� altro da dire =), Quequerone (eUUiUa la IA :D), il mis� (ahi�!), Eimiar (e si programma che � un piacere :P), cyberdude (e rianimiamoci!), Akagi (che questa pagina non la legger� mai, ma vabbe :P) e a tutti quelli che bazzicano su #crack-it e #cryptorev =).

Alla proxima.

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 che ogni sviluppatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.