WinAce
2.6 Keygen |
||
Data |
by "Graftal" |
|
05/Nov/2005 |
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 |
|
you b0x0rz |
Difficolt� |
( )NewBies ( )Intermedio (G)Avanzato ( )Master |
Introduzione |
Tools usati |
URL o FTP del programma |
Essay |
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:
|
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.
|
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:
|
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
|
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.
.. |
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�
.. |
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') |
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.
.. |
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? |
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 |
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 |
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 ;).
.. |
Steppiamo:
.. |
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 |
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 |
Adesso sappiamo anche a cosa deve essere uguale la word presente in ECX+6D, vediamo allora quali sono i prossimi check:
|
Il codice � molto chiaro, non ci rimane che analizzare due call che abbiamo tralasciato e poi abbiamo finito il reversing =).
|
E adesso passiamo alla call che controlla se il sn � expired o meno:
|
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.