- Secure Hash Algorithm, un
algoritmo sicuro!
-
Il Secure Hash Algorithm fu creato nel 1993 dalla National
Security Agency (NSA) ed è oggi identificato, nella sua forma
originale, con il nome sha-0. Questa
primissimissima versione dell'algoritmo venne subito dopo (nel 1995) sostituita
dalla Sha-1, una versione che andava a correggere una piccola falla
presente nella vecchia versione; quelli dell'NSA non hanno mai rilasciato una
dichiarazione ufficiale sul problema che affliggeva la sha-0 ma il fatto
che nel 1998 due ricercatori siano riusciti ad attaccare con successo
l'algoritmo fa pensare ad un bug che riduceva il livello di sicurezza. Di questo algoritmo esistono in realtà
altre 3 versioni denominate sha-256, sha-384 e sha-512, create dal
National Institute of Standards and Technology (NIST) e sviluppate
seguendo le caratteristiche dell'sha-1.
-
- L'sha-1 prende in ingresso un file
o una stringa (di lunghezza inferiore a 2^64 bit) e produce un 'digest' (riassunto) di 160
bit, 20 caratteri. Questo hash è molto più lento rispetto
all'MD5 ma producendo un digest di dimensioni maggiori (160 bit contro
i 128 bit dell'MD5) viene considerato più sicuro dell'MD5. Come
l'MD5 però mantiene delle proprietà comuni a tutti gli hash:
- - è computazionalmente
impossibile sha-1-are due messaggi diversi ed ottenere lo stesso
digest
- - il processo non è invertibile,
è cioè impossibile partire dal digest e trovare il messaggio
originale
- Come
altri tipi di hash, anche l'sha-1 utilizza una serie di operazioni
molto semplici per produrre il digest finale. L'algoritmo è
abbastanza incasinato per cui non starò qui a spiegarvi per filo
e per segno quali sono le operazioni svolte ma mi limiterò a
farvi vedere come si può facilmente identificare l'algoritmo in
pochissimi secondi. Se volete farvi del male date un'occhiata qua:
http://www.faqs.org/rfcs/rfc3174.html :p
-
- In generale, il modo più semplice
per identificare un algoritmo di questo tipo consiste prima di
tutto nello studio
delle costanti di inizializzazione usate dall'algoritmo. L'sha-1 inizializza un buffer
di 20 byte in questo modo:
-
- dword_1 = 0x67452301
dword_2 = 0xEFCDAB89
dword_3 = 0x98BADCFE
dword_4 = 0x10325476
dword_5 = 0xC3D2E1F0
-
- Come potete vedere le prime 4
dword sono le stesse utilizzate dall'MD5 ma in più viene definita una ulteriore dword. In futuro, quando e se vi
capiterà di incontrare una serie di istruzioni simili a queste:
-
- MOV DWORD PTR DS:[reg],67452301
MOV DWORD PTR DS:[reg+4],EFCDAB89
MOV DWORD PTR DS:[reg+8],98BADCFE
MOV DWORD PTR DS:[reg+C],10325476
MOV DWORD PTR DS:[reg+10],C3D2E1F0
-
- dovrete subito drizzare le orecchie e dire: "hmmm...
sha-1". Ma siamo sicuri
che sarà proprio sha-1? Potrebbe anche essere ripemd160...
- Nella sua forma originale, questo
tipo di algoritmo si presenta come una serie di call dove la prima
esegue l'inizializzazione del buffer con le 5 dword viste qua
sopra mentre le
altre si occupano di generare il digest. Per avere la certezza che
siete di fronte all'sha-1 avete due possibilità:
- 1. steppare tutte le call
successive a quella di inizializzazione cercando di identificare
tutti i passi eseguiti dall'algoritmo
- 2. andare a spulciare i valori
passati e ritornati alle/dalle varie call seguenti la call di
inizializzazione
- Io opterei per la seconda ipotesi
sfruttando uno dei tanti hash-calculator che si trovano in rete.
Perchè usare anche questo tipo di tool? Semplice, con questo tool
possiamo sapere in anticipo quale sarà il digest calcolato per
cui saremo facilitati nel nostro compito di ricerca del digest tra
i vari valori ritornati dalle call.
- Bene, questo è più o meno tutto
quello che useremo per identificare l'sha-1.
-
- File Carbon, adesso ti
registriamo!
- Lanciamo il programma e vediamo un
pò come possiamo approcciare la routine di registrazione.
Inserendo una combinazione di name/password sbagliata appare un
bel messaggio di errore: "License Code do not match to User Name, click OK to try again".
Bene, andiamo in Ida e cerchiamo il messaggio di errore nella
finestra denominata "Strings" (Shift-F12); appena
trovate la stringa fate un bel doppio click sulla xref e vediamo a
che indirizzo viene visualizzato il box di errore: 499E5C. La routine
contenente la visualizzazione del box di errore parte
all'indirizzo 499A08 e molto probabilmente questa routine contiene
tutto l'algoritmo di registrazione. Torniamo in Ollydbg e mettiamo
un breakpoint all'inizio della routine; proviamo a registrarci di
nuovo e
vediamo che Olly brecka. Steppate qualche linea di codice:
-
- CODE:00499A3A call sub_444B64
<--------- legge il nome inserito
CODE:00499A3F mov eax, [ebp+var_100] <-- eax
punta al nome inserito
CODE:00499A45 call sub_404A50 <--------- ritorna in eax
la lunghezza del nome inserito
CODE:00499A4A cmp eax, 7Eh <------------
confronta la
lunghezza del nome con il valore 7Eh
CODE:00499A4D jle short loc_499A7E <---- se il
nome è lungo più di 0x7E caratteri viene tagliato a 0x7E caratteri
- ...
- CODE:00499A7E lea edx, [ebp+var_18]
CODE:00499A81 mov eax, [ebp+var_4]
CODE:00499A84 mov eax, [eax+308h]
CODE:00499A8A call sub_444B64 <--------- rilegge il
nome
CODE:00499A8F lea edx, [ebp+var_C]
CODE:00499A92 mov eax, [ebp+var_18] <--- eax
punta al nome inserito
CODE:00499A95 call sub_40891C <---------
mette il nome in maiuscolo
CODE:00499A9A lea edx, [ebp+var_10C]
CODE:00499AA0 mov eax, [ebp+var_4]
CODE:00499AA3 mov eax, [eax+310h]
CODE:00499AA9 call sub_444B64 <--------- legge il
serial inserito
CODE:00499AAE mov eax, [ebp+var_10C] <-- eax
punta al serial inserito
CODE:00499AB4 lea edx, [ebp+var_108]
CODE:00499ABA call sub_40891C <---------
mette il serial in maiuscolo
CODE:00499ABF mov eax, [ebp+var_108] <-- eax
punta al serial maiuscolo
CODE:00499AC5 lea edx, [ebp+var_1C]
CODE:00499AC8 call sub_48D060 <--------- sposta il
serial in un nuovo buffer
-
- Ecco qua la prima semplicissima
parte della routine di protezione che in sostanza legge il nome e
il serial inseriti e li converte in maiuscolo... niente altro per
ora. Proseguiamo con la routine:
-
- CODE:00499AEF mov eax, [ebp+var_1C]
<-- eax punta al serial inserito
CODE:00499AF2 call sub_48D114 <-------- controlla che
il serial contenga soltanto determinati caratteri
CODE:00499AF7 test al, al <------------ se al=1 il serial
è corretto
CODE:00499AF9 jnz short loc_499B25
CODE:00499AFB push 1
CODE:00499AFD mov ecx, offset dword_499EEC
CODE:00499B02 mov edx, offset aLicenseCodeMus <-- "License Code must be containing only the letters a..f, A..F,digit(s), dash(es) and space(s), click OK to try again"
CODE:00499B07 mov eax, ds:off_4A1104
CODE:00499B0C mov eax, [eax]
CODE:00499B0E call sub_46533C <-------- message box
che ci comunica quali sono i caratteri validi per il serial
-
- Un primo controllo sul serial, precisamente sul tipo di
caratteri che compongono il serial inserito. Come potete vedere
non c'è nessun bisogno di steppare la call a 499AF2 perché è il
programma stesso a dirci quali caratteri non sono validi,
meglio di così :)
-
- CODE:00499B25 cmp [ebp+var_C], 0
<--- controlla che il nome sia presente
CODE:00499B29 jz short loc_499B31
CODE:00499B2B cmp [ebp+var_1C], 0 <-- controlla
che il serial sia presente
CODE:00499B2F jnz short loc_499B4A
-
- Controlla di nuovo la
presenza del nome e del serial... mah.
-
- CODE:00499B4A lea eax, [ebp+var_FC]
<-- eax punta ad un buffer
CODE:00499B50 xor ecx, ecx
CODE:00499B52 mov edx, 14h <----------- edx = 20
CODE:00499B57 call sub_403110 <-------- pulisce
(mettendo a 00h) 20 byte del buffer, vedremo tra poco per cosa
viene usato questo buffer di 20 byte
...
- CODE:00499B7B push offset aAedb3456
<-- pusha il puntatore alla stringa: "AEDB3456"
CODE:00499B80 lea edx, [ebp+var_110]
CODE:00499B86 mov eax, [ebp+var_C] <--- eax
punta al nome inserito (maiuscolo)
CODE:00499B89 call sub_40891C <-------- ri-sposta il
nome inserito (maiuscolo)
CODE:00499B8E push [ebp+var_110] <----- pusha il
puntatore al nome (maiuscolo)
CODE:00499B94 push offset aAebca386 <-- pusha il
puntatore alla stringa: "AEBCA386"
CODE:00499B99 lea eax, [ebp+var_20]
CODE:00499B9C mov edx, 3
CODE:00499BA1 call sub_404B10 <-------- concatena le
tre stringhe pushate qua sopra
-
- Dopo una serie di operazioni semi-inutili iniziamo a vedere come
si evolve la routine di protezione. Il nome inserito viene messo
in mezzo alle due stringhe "AEDB3456" e
"AEBCA386" ottenendo una cosa di questo tipo:
AEDB3456<nome>AEBCA386
- D'ora in poi farò riferimento a
questa stringa unica come 'nome espanso'. Vediamo cosa combina con
la
stringa appena costruita:
-
- CODE:00499BA6 mov eax, [ebp+var_8]
CODE:00499BA9 mov edx, [eax]
CODE:00499BAB call dword ptr [edx+40h] <--
hmmm...
-
- Fino ad
ora non abbiamo steppato nessuna call perché si riusciva facilmente a capire cosa facevano
prestando attenzione soltanto ai valori
pushati e ritornati; stavolta il discorso è diverso.
Non riuscendo a capire che cosa faccia dobbiamo per forza entrare
nella
call e vedere cosa succede:
-
- CODE:0048CDC8 push ebx
...
- CODE:0048CDD2 mov dword ptr [ebx+40h], 67452301h
<--- hm
CODE:0048CDD9 mov dword ptr [ebx+44h], 0EFCDAB89h <-- hmm
CODE:0048CDE0 mov dword ptr [ebx+48h], 98BADCFEh <--- hmmm
CODE:0048CDE7 mov dword ptr [ebx+4Ch], 10325476h <--- hmmmm
CODE:0048CDEE mov dword ptr [ebx+50h], 0C3D2E1F0h <-- hmmmmm... interessante!
...
- CODE:0048CDFA retn
-
- Vi si è accesa la lampadina sopra la testa in stile fumetti? Come
no!?! Non vi ricorda niente l'inizializzazione di questo buffer
con quelle particolarissime 5 dword? Forse abbiamo trovato
l'inizializzazione di una hash. Che sia sha-1? Si fa presto a
verificalo, basta usare un hash-calculator e dargli in pasto la
stringa di input; quale sarà la stringa di input? Hmmm, vediamo:
-
- CODE:00499BAE mov edx, [ebp+var_20] <-- edx
punta al nome espanso
CODE:00499BB1 mov eax, [ebp+var_8]
CODE:00499BB4 call sub_48A6FC
-
- Che dire, appena dopo la
inizializzazione dell'hash viene tirato in ballo il nome espanso,
che sia l'input della hash? La risposta è semplice, basta provare
usando l'hash-calculator. Nel mio caso,
l'sha-1 sulla stringa 'AEDB3456ZAIRONAEBCA386' produce i seguenti
20 byte: 0x1C, 0x49, 0x9B, 0x8D, 0x2B, 0x59, 0x36, 0x97, 0xF4, 0x6F,
0xF0, 0x0E, 0xF8, 0xE9, 0xE6, 0xAD, 0x74, 0xFB.
Vediamo se una delle call successive mi genera la stessa sequenza
di byte:
-
- CODE:00499BB9 lea edx, [ebp+var_FC] <-- edx =
indirizzo del buffer *pulito* dalla call a 499B57, vi ricordate?
CODE:00499BBF mov eax, [ebp+var_8]
CODE:00499BC2 mov ecx, [eax]
CODE:00499BC4 call dword ptr [ecx+44h] <-- mette
nel buffer *pulito* una strana sequenza di byte
-
- Date un'occhiata ai byte contenuti
nel buffer, non notate niente di strano? Sono 20 e già questo
dovrebbe farvi sorridere; in più c'è dell'altro. Provate a
confrontare questi byte con quelli ritornati dall'sha-1...
eccellente, abbiamo
individuato l'sha-1! Semplice? Bhè, direi di si :D
- Questa è un pò la limitazione
offerta da questo tipo di funzioni hash... sono abbastanza
prevedibili e di facile individuazione.
- Vediamo cosa succede nel resto
della routine di protezione:
-
- CODE:00499BF4 mov esi, 14h
<--------- esi = 20
CODE:00499BF9 lea ebx, [ebp-0FCh] <-- ebx
punta ai 20 byte dell'sha-1 applicato al nome espanso
CODE:00499BFF lea ecx, [ebp-114h]
CODE:00499C05 xor eax, eax
CODE:00499C07 mov al, [ebx] <-------- prende il byte
corrente dell'sha-1(nome_espanso)
CODE:00499C09 mov edx, 2
CODE:00499C0E call sub_408C84 <------ divide il byte
in due byte. Se al=1C allora genera i caratteri '1' e 'C'
CODE:00499C13 mov edx, [ebp-114h] <-- edx
punta ai due caratteri appena generati
CODE:00499C19 lea eax, [ebp-24h]
CODE:00499C1C call sub_404A58 <------ concatena i
nuovi caratteri in un nuovo buffer
CODE:00499C21 inc ebx
CODE:00499C22 dec esi
CODE:00499C23 jnz short loc_499BFF <-- ripete
il ciclo finchè non ha spezzato tutti i byte
dell'sha-1(nome_espanso)
-
- Ecco qua un semplice ciclo sul
valore ritornato dall'sha-1. I 20 byte diventano 40, nel mio caso
ottengo:
- - sha-1: 0x1C, 0x49, 0x9B, 0x8D, 0x2B,
0x59, 0x36, 0x97, 0xF4, 0x6F, 0xF0, 0x0E, 0xF8, 0xE9, 0xE6, 0xAD,
0x74, 0xFB
- - stringa generata dal ciclo qua
sopra: "1C499B8D2B593697F46FF0460EF826E9E6AD74FB"
- Non mi sembra ci sia molto altro da
dire. Vediamo la parte finale della routine:
-
- CODE:00499C4C lea ecx, [ebp-14h]
CODE:00499C4F mov eax, [ebp-10h] <--------- eax
punta al
primo carattere del nome inserito
CODE:00499C52 movzx eax, byte ptr [eax] <-- al =
primo carattere del nome inserito, nel mio caso 0x5A (la 'Z')
CODE:00499C55 mov edx, 2
CODE:00499C5A call sub_408C84 <------------ questa
l'abbiamo incontrata nel ciclo precedente (spezza un byte in
due...)
CODE:00499C5F push offset aC499 <---------- pusha il
puntatore alla stringa 'C499'
CODE:00499C64 push [ebp+var_14] <---------- pusha il
puntatore alla stringa formata dai due caratteri ottenuti dal
primo carattere del nome, nel mio caso la stringa è '5A'
CODE:00499C67 lea eax, [ebp+var_118]
CODE:00499C6D push eax <--
---------------- pusha il puntatore ad
un buffer vuoto; il buffer sarà riempito dalla seguente call
CODE:00499C6E mov ecx, 0Eh
CODE:00499C73 mov edx, 1
CODE:00499C78 mov eax, [ebp+var_24] <------ eax
punta all'sha-1(nome_espanso) esteso a 40 byte creata nel ciclo
precedente
CODE:00499C7B call sub_404CA8 <------------
sposta i primi
14 (0x0E) byte puntati da eax in un nuovo buffer
CODE:00499C80 push [ebp+var_118] <--------- pusha il
puntatore al buffer contenente i 14 caratteri
CODE:00499C86 lea eax, [ebp+var_28]
CODE:00499C89 mov edx, 3
CODE:00499C8E call sub_404B10 <------------ anche questa
l'abbiamo già incontrate, vi ricordate cosa fa? Concatena le 3 stringhe
pushate qua sopra: 'C499', '5A' e i primi 14 caratteri
dell'sha-1(nome espanso)
CODE:00499C93 mov edx, [ebp+var_28] <------ edx
punta alla stringa risultante dal concatenamento delle 3 stringhe
CODE:00499C96 mov eax, [ebp+var_1C] <------- eax
punta al
serial inserito
CODE:00499C99 call sub_4089E4 <------------ confronta le
due stringhe e ritorna eax = 0 se sono uguali
CODE:00499C9E test eax, eax <-------------- se eax=0 siamo
registrati...
CODE:00499CA0 setz bl
CODE:00499CA3 mov eax, [ebp+var_4]
CODE:00499CA6 mov [eax+320h], bl
CODE:00499CAC test bl, bl
CODE:00499CAE jz loc_499E49 <-------------- se il serial
è corretto non saltiamo
-
- Tadaa, finito! Come vi avevo preannunciato la routine di
protezione usata dal programma è molto semplice; in questo caso
basterebbe un semplicissimo serial sniffing per trovare il serial
esatto.
Il problema però nasce nel momento in cui uno decide di provare a
scrivere un keygen; sareste stati in grado di scrivere un keygen
in 2 minuti senza sapere che la routine di protezione utilizza
l'sha-1? Credo proprio di no...
- A proposito di keygen, scriviamone subito uno.
-
- Come scrivere un keygen
- Prima di partire col keygen
vero e proprio facciamo un piccolo riassunto sul funzionamento
della routine di protezione prendendo come nome la stringa
'ZaiRoN'. Ecco i passi effettuati dall'algoritmo:
- 1. lettura del nome: 'ZaiRoN'
- 2. conversione in maiuscolo:
'ZAIRON'
- 3. estenzione del nome con le
stringhe
"AEDB3456" e
"AEBCA386": 'AEDB3456ZAIRONAEBCA386'
- 4. applicazione dell'sha-1 al nome
esteso (ottenuto dal passo 3): 0x1C 0x49
0x9B 0x8D 0x2B
0x59 0x36 0x97
0xF4 0x6F 0xF0
0x46 0x0E 0xF8
0x26 0xE9 0xE6
0xAD 0x74 0xFB
- 5. sdoppiamento dei 20 byte
risultanti dall'sha-1: "1C499B8D2B593697F46FF0460EF826E9E6AD74FB"
- 6. inizializzazione del serial che
avviene prendendo la stringa
"C499" e aggiungendoci i due caratteri derivanti dal valore
in hex del primo carattere del nome: 'C4995A'
- 7. costruzione finale del serial
valido appendendo al serial i primi 14 dei 40 caratteri ottenuti dallo sdoppiamento
dell'sha-1 (applicato al nome esteso)
- 8. ...game over...
-
- Nel file allegato troverete il keygen con il codice sorgente in c; il keygen è
soltanto per xp/2000 perché ho usato delle funzioni non
supportate dal buon vecchio w98 (potevo farlo anche per w98 ma...
sono nato di Domenica e come dice mia madre "Sei nato
stanco!" :D). Comunque sia, dato che ultimamente va molto di
moda personalizzare le dialog ho deciso di aggiungere qualche
linea in più al codice per creare una forma non standard e
dare un pò di trasperenza alla dialog. Io sono amante delle cose
classiche per cui una bella finestrella dos è già più che
sufficiente per un keygen ma stavolta farò una eccezione :p. La
creazione di questo tipo di dialog è
decisamente semplice ed offre un discreto effetto quindi perché non
usare un minimo di creatività per sviluppare le forme più
strane? Non fate caso alla mia che è solo di
esempio :D.
- Ecco la dialog sopra una immagine del sito
della UIC:
-
-
- Per far ciò basta utilizzare
poche funzioni, per l'esattezza tre per definire la forma e un
paio per dare la trasparenza. Vediamo come...
-
- Iniziamo
dalla definizione della forma. L'idea è quella di creare varie
regioni e combinarle assieme. Ogni regione viene definita usando
una specifica funzione dipendente dalla forma della regione che si
vuole creare; nel mio keygen ho utilizzato forme rettangolari e la
funzione che definisce questo tipo di regioni si chiama
CreateRectRgn; nella funzione si specificano i punti di
definizione della regione rettangolare:
-
- HRGN CreateRectRgn(
int nLeftRect, // Coordinata
x relativa al punto in alto a sinistra
int nTopRect, //
Coordinata y relativa al punto in alto a sinistra
int nRightRect, // Coordinata x
relativa al punto in basso a destra
int nBottomRect // Coordinata y
relativa al punto in basso a destra
);
-
- Definite semplicemente i due
estremi del rettangolo ed il gioco è fatto. Come potete vedere
dall'immagine qua sopra ho creato due regioni rettangolari, una
grande e una un pò più piccola. Adesso dobbiamo unire le due regioni create
usando la funzione CombineRgn:
-
- int CombineRgn(
HRGN hrgnDest, //
Handle della regione ottenuta dall'unione delle altre due
HRGN hrgnSrc1, //
Handle della prima regione
HRGN hrgnSrc2, //
Handle della seconda regione
int fnCombineMode // Specifica come
combinare le due regioni; ho usato l'OR perché le voglio entrambe
);
-
- Finito così? No, manca un
ultimissimo passo; dobbiamo dire al programma quale regione
visualizzare. Ovviamente la regione da visualizzare sarà quella ottenuta dalla
combine fatta in precedenza:
-
- int SetWindowRgn(
HWND hWnd, // Handle
della finestra
HRGN hRgn, // Handle
della regione da visualizzare
BOOL bRedraw // Opzione di redraw,
TRUE se si desidera fare il redraw dopo la definizione della
regione
);
-
- Ok, adesso è davvero tutto. La
nuova forma è pronta!
-
- Per quanto riguarda la trasparenza
si usano altre due banali funzioncine: SetWindowLong e SetLayeredWindowAttributes.
- - SetWindowLong: è usata per
cambiare gli attributi di una finestra.
-
- LONG SetWindowLong(
HWND hWnd, //
Handle della finestra
int nIndex, // Offset
relativo al valore da settare
LONG dwNewLong // Nuovo valore
);
-
- Tra tutti i valori disponibili è importante settare il
WS_EX_LAYERED perché
necessario per la funzione seguente.
- - SetLayeredWindowAttributes:
definisce il livello di trasparenza della dialog
-
- BOOL SetLayeredWindowAttributes(
HWND hwnd,
// Handle della finestra
destinazione
COLORREF crKey, // Specifica il
colore di trasparenza (in RGB)
BYTE bAlpha, // Valore che
definisce l'opacità della finestra, il livello di trasparenza
DWORD dwFlags // LWA_COLORKEY per
specificare il crKey e LWA_ALPHA per specificare il bAlpha
);
-
- Adesso sapete anche come si creano
dialog trasparenti ;)
-
- Rimane una ultimissima cosa da
fare, muovere la dialog. Come
faccio a muovere una dialog che non ha una title bar? Si usa un banalissimo
trucchetto; quando si riceve un messaggio di tipo WM_LBUTTONDWON
si manda un messaggio di tipo WM_NCLBUTTONDOWN con
wParam HTCAPTION. In questo modo facciamo credere al nostro caro windows che il click sia stato fatto sulla title bar
(che in realtà non è presente...) ed il gioco è fatto.
-
- Non aggiungo altro su queste
funzioni perché sono tutte di facile utilizzo e le specifiche che
trovate alla msdn sono
più che sufficienti come descrizione; usate liberamente il keygen per sperimentarle :D
-
- Conclusioni:
- Anche se il programma presenta una
routine di protezione decisamente banale spero abbiate capito che spesso
le parole crypto/hash/digest non sono così orribili come sembrano;
anzi, spesso è molto più semplice lavorare con queste hash perché di
loro conosciamo praticamente tutto. Con un minimo di conoscenza di
background e con l'utilizzo di tool mirati vedrete che sarà
relativamente semplice confrontarsi con routine che utilizzano questo
tipo di algoritmi. Esistono addirittura dei tool che prendono in pasto
un eseguibile e vi dicono se è presente un qualsiasi tipo di
algoritmo crittografico famoso. Uno dei miei preferiti è il
CryptTool di Christal che trovate qui. Personalmente non preferisco questo tipo di approccio perché
potrebbe darvi indicazioni non corrette (un pò come accade con gli
scanner per determinare il tipo di packer usato) ma può
rappresentare un altra freccia nella vostra faretra ;)
-
- ZaiRoN