|
Benvenuti a tutti nella seconda lezione della UIC, stavolta il livello sarà leggermente
più alto della prima lezione, oggi parleremo di programmi che generano una chiave e la
salvano nel seriale nel registro in forma crittata.
Nella prima lezione avevamo fatto un accenno ai generatori di chiavi ma non eravamo
andati oltre dal momento che dovevamo crackare il programma e non reversarlo, questa volta
invece vi verrà chiesto di creare un keymaker per un programma e di spiegare la routine
di generazione, quindi questo corso è indicato a chi ama di più il reversing rispetto al
cracking.
Dal momento che dovevamo parlare di programmi che creano un seriale da un nome facciamo
subito un piccolo esempio: al giorno d'oggi la maggior parte dei programmi shareware mette
a disposizione dell'utente un box di registrazione nel quale deve essere inserito il
proprio nome ed un numero che verrebbe venduto in genere a molto da una
SoftwareHouse, il
lavoro del reverser è allora quello di capire cosa cerca il programma in quel box e
darglielo. Come al solito per brekkare nella routine principale si possono usare i tre
breakpoint più famosi nel mondo del reversing cioè: GetWindowTextA,
GetDlgItemTextA,
hmemcpy
una volta premuto il pulsante di registrazione steppate finchè vi ritrovate nella
routine, una volta dentro osservate il codice ed andate avanti, in linea di massima
vedrete che ogni carattere del vostro nome verrà manipolato nei più vari modi per poi
essere confrontato con il seriale che avevate inserito, o meglio vedrete che verrà
generato un seriale dal vostro nome, verrà poi crittato e dopo ancora verrà confrontato
con il vostro serial crittato allo stesso modo, per rendervi le idee più chiare vi faccio
uno schemetto che riassume il modo "generale" di agire di quasi tutti i
programmi di questo genere:
-
- Fase 1)
-
- Viene preso il nome...
- Quequero
|
-
|
....Che viene
manipolato.....------------
-
|
|
- ----
(Quequero*2)+(Que*quero)+(Que/2) = 568
|
-
| .....E
crittato!
-
|
-
------ (568 xor 25)rol6(xor 40)...874
- Fase 2)
-
- Viene preso il numero.....
- 666111666 |
|
|
-
| .....Che viene crittato allo stesso modo
-
|
-
--------- (568 xor 25)rol6(xor 40)...874
-
-
- Fase 3)
-
- I due numeri crittati vengono confrontati.....
- 874 & 874 = OK
- 874 & 975 = NO
- ...E se combaciano il numero viene salvato nel registro con il
nome, altrimenti NO, talvolta il numero viene salvato anche se sbagliato ma il programma
quando viene avviato se ne accorge e si setta automaticamente in modalità
shareware.
-
- Adesso che sapete come agisce la generazione del numero, vediamo
anche come ne avviene la lettura al riavvio del programma:
-
- 1) Il prg apre la propria chiave nel registro
(RegOpenKeyA) e la
legge (RegQueryValueA)
- 2) Il prg apre la chiave che contiene il nostro nome e lo legge,
lo manipola, lo critta e lo salva
- 3) Il prg richiama il numero e nome (manipolato) letti e li
confronta
- 4) Sono uguali? Setta modalità Registrata altrimenti setta
modalità Shareware
-
- Da questo momento sapete che per brekkare nella routine avete ben
due momenti cioè: la registrazione e la lettura, se volete crackare il target vi
consiglio di usare il secondo attacco, se invece lo volete reversare vi consiglio il
primo.
- Attenzione però che alcuni programmi (come Advanced Zip Password
Recovery) fanno un terzo check al numero ogni volta che avviate una qualunque funzioni,
quindi amici crackatori state attenti.
- Dopo questo noioso ma indispensabile pezzo di teoria vi presento
qualche esempietto pratico, partiamo con una semplice routine di manipolazione che
manipola i primi 4 caratteri del nome:
-
- Call
GetWindowTextA, hwnd, offset nome, 15 ; Prende il nome e lo mette nel buffer "nome"
- mov
edi, offset nome
; routine di calcolo della lunghezza del nome
- or
ecx, -1
; routine di calcolo della lunghezza del nome
- not ecx
; lunghezza in ECX
- xor eax, eax
- mov
eax, dword ptr [nome] ; Muovi 4 caratteri del nome in eax
- cdq
; Convert DoubleWord to Quad per preparare una divisione
- mov
edi, 185h
; Prepara il divisore
- div edi
; Dividi EDI per eax (nome/185h)
- add
eax, edx
; Aggiungi al risultato il resto della divisione
- sub
ecx, 4
; Sottrai 4 lettere alla lunghezza del nome (le abbiamo messe
in eax)
- cmp
ecx, 0
; il nome è finito?
- jle ......
;...........
-
- Prima di affrontare una routine di crittazione vorrei fare un
piccolo preambolo teorico: allora, crittare un seriale significa mascherarlo in modo da
non farlo sembrare ciò che in realtà è, i programmatori per confonderci ricorrono a
svariate tecnice di crittazione per non farci capire niente ma con un po' di pratica tutti
saranno in grado in più o meno tempo di capire l'algoritmo. Adesso vi starete chiedendo:
ora che sappiamo che significa crittare un seriale come facciamo a sapere se il nostro
programma usa questa tecnica? Dunque il procedimento non è troppo complesso, vi dico
subito che per cifrare qualcosa si usano degli operatori matematici e logici quali ad
esempio: Rol, Ror, Shl, Shr e Xor che tratteremo dopo, se durante lo step vedete una delle
prime 2 istruzioni state pur certi che quella è mano del programmatore dal momento che
nessun, e ribadico NESSUN compilatore produce istruzioni come Rol e Ror, in genere se in
una comune routine di calcolo trovate anche qualche shl o shr sarete quasi certi che
anch'essa sia una routine di crittazione, se invece state reversando una demo grafica
allora non fate caso ai vari shl o shr dal momento che in quel campo sono ampiamente
usati.
- Dopo questo breve preambolo è ora di fare le presentazioni per
spiegare il funzionamento di queste istruzioni:
- Ror = Rotate
Right: ruota un bit a destra
- Rol = Rotate
Left: ruota un bit a sinistra
- Shr = Shift
Right: Shifta un bit a destra
- Shl = Shift
Left: Shifta un bit indovinate un po' dove? :)
- Xor = Exclusive Or: Or esclusivo
-
- ------------------------------------------------------------------------------------------------------------
- Mentre per gli operatori Shift e Rotate non mi sembra il caso di
perdere tempo a spiegare, credo che sia invece necessario per lo Xor.
- L'istruzione xor non fa altro che confrontare due valori e
riportare un 1 se i due valori erano differenti ed uno 0 se erano uguali, ecco un esempio,
prendiamo un numero binario e lo xoriamo con un altro numero sempre binario:
-
-
101100 Xor
-
110101 =
-
----------
-
011001
-
- ecco cosa fa lo
xor, la sua peculiarità e che se il risultato si
rixora per lo stesso valore riotteniamo il numero di prima:
-
-
011001
Xor
011001 Xor
-
110101 =
& 101100 =
-
-----------
-----------
-
101100
110101
-
- elementare no? Si xora un valore per un altro valore e quindi lo
si critta, lo si rixora per lo stesso valore e lo si decritta.
- ------------------------------------------------------------------------------------------------------------
-
- bene bene, per capirne di più sullo Xor andate a leggere il mio
tutorial alla UIC. Dunque vediamo ora come vengono usati questi operatori con un'altra
semplice routine:
-
- valore
dd 1234h
- serial_esatto dd ?
- ----- snip snip
-----
- mov
eax, dword ptr [serial_esatto] ; Muove il serial
esatto dal buffer in eax
- rol eax, 5
; Ruota a sinistra di 5 bit eax
- shr eax, 4
; Shifta a destra eax
- xor eax, 45h
; Xora il risultato con un valore (45h)
- xor eax, serial_esatto
; Xora
eax con il serial esatto
- xor eax, dword ptr [valore]
; Xora il risultato
con un valore predefinito preso da valore
-
- con questa routine abbiamo dimostrato alcuni dei più comuni modi
di usare uno xor, ma partiamo dall'inizio, il seriale esatto viene spostato dal proprio
buffer in eax, viene rotato di 5 bit, viene shiftato di 4 bit ed il risultato viene xorato
con un valore predefinito, il numero viene poi rixorato con il serial esatto (e quindi con
un numero variabile e non con un valore predefinito) e poi viene xorato con i primi 4 byte
presi da una doubleword definita ad inizio programma, questa tecnica viene usata per non
rendere troppo esplicito il valore con cui viene xorato il numero. Ma ora che il numero è
crittato fino alle gengive come facciamo a decrittarlo? Innanzitutto nella routine
di un programma vero non sarebbe necessario ma se volessimo farlo per sniffare il seriale
che ci interessa potremmo utilizzare un metodo abbastanza banale cioè: per decrittare un
valore che è stato shiftato basta rishiftarlo dall'altro lato per lo stesso valore e
questo vale anche per un valore rotato, se invece c'è anche stato uno xor, basta
rixorarlo per lo stesso valore, ecco ad ogni modo la routine di decrittazione che dovremmo
utilizzare per leggere il numero che esce fuori dalla routine di sopra, sarebbe totalmente
inutile decrittare il numero dal momento che potremmo leggerlo dal buffer [serial_esatto]:
-
- mov
eax, dword ptr [serial_crittato] ; Muove il serial crittato in eax
- ror
eax, 5
; Ruota a destra di 5 bit eax
- shl
eax, 4
; Shifta a sinistra eax
- xor
eax, 45h
; RiXora
il risultato con un valore (45h)
- xor
eax, serial_esatto
; RiXora eax con il serial esatto
- xor
eax, dword ptr [valore] ; RiXora il risultato con un valore predefinito
preso da valore
-
- come vedete decrittare qualcosa conoscendo la routine "madre" è semplice ed
è sempre la stessa pappa, quando studieremo il codice auto-modificante (SMC da
Self-Modifyng Code) impareremo altre tecniche di crittazione più sofisticate.
- Ci si ribecca alla prossima lezione :)
|