Copy Link
Add to Bookmark
Report
Guida per principianti al cracking delle password dei programmi
Gli ____ _ ___ ___ __ ______ ____30-09-98
/ __/__ (_) _ \/ _ \___ / / ___ /_ __/__ ____/ _/ ASCII
_\ \/ _ \/ / ___/ ___/ _ \/ /_/ _ `// / / _ \/ __// / by
/___/ .__/_/_/ /_/ \___/____|_,_//_/ \___/_/ /___/RigoR MorteM
/_/
Presentano:
Guida per principianti al cracking delle password dei programmi
By Flamer - flamer@tuttocitta.it
Questo documento vorrebbe essere una guida per chi non ha mai preso in
considerazione l'idea di crackare un programma. Una guida per cominciare perche',
per essere dei veri cracker, la strada e' molto lunga, e soprattutto e'
necessario fare molta esperienza, provando e riprovando.
COSA SERVE
Strumento fondamentale per il vero cracker e' il DEBUGGER. Ne va bene uno
qualsiasi, ma il migliore sul mercato e' il Numega Soft-Ice 3.2.
Il debugger vi permette di eseguire un programma passo-passo, cosa fondamentale
per capire come funzionano le procedure di protezione, e quindi come modificare
il programma per eluderle.
Altra cosa molto importante e' un Hex editor. Ne va bene uno qualsiasi, basta che
vi permetta di cercare all'interno di un file una sequenza di byte. Talvolta
potrebbe essere utile anche avere la possibilita' di confrontare due o piu' file
per trovare le eventuali differenze.
L'ASSEMBLER, QUESTO SCONOSCIUTO...
Importante (ma non fondamentale) e' una buona conoscenza del linguaggio
assembler.
Le funzioni che un cracker deve assolutamente conoscere pero' sono poche, e vale
la pena fare una piccola digressione.
Prima pero' e' importante affrontare alcuni argomenti basilari, che sono la
gestione della memoria ed i registri.
La memoria di un PC e' organizzata in segmenti. Ogni singolo byte di memoria e'
caratterizzato da un indirizzo che e' dato nella forma SSSS:OOOO, dove SSSS e'
il numero del segmento, mentre OOOO e' il numero dell' offset.
Un esempio di indirizzo di memoria e' 1ADD:0F50; in queso caso 1ADD e' il
segmento, mentre 0F50 e' l'offset. La divisione della memoria in segmenti e'
molto comoda, nel senso che tutti i byte appartenenti allo stesso segmento hanno
la stessa origine e funzione.
Nei Pentium a 32 bit l'offset e' composto da 8 cifre anziche' 4.
NB: Se non sapete che cos'e' un numero esadecimale potete lasciare perdere il
resto di questa guida.
Le variabili dell'assembler sono dette registri. A differenza dei linguaggi di
programmazione piu' avanzati come il C o il Pascal, nell'assembler i registri
sono ben definiti e NON MODIFICABILI se non nel valore dal programma.
Mi spiego: non e' possibile definire nuovi registri o rinominare quelli
esistenti.
I registri piu' importanti sono:
AX , BX , CX , DX : Questi sono registri di varia utilita'. Ognuno di essi e'
formato da due byte. AH,AL,BH,BL,CH,CL,DH,DL sono i byte che formano questi
registri, ad esempio se AX=2EF1, allora AH=2E e AL=F1. La stessa cosa per BX,CX
e DX. Dopo un'istruzione MOV AL,00 (che azzera il valore di AL) si avrebbe
AX=2E00. Chiaro?
Per quanto riguarda i Pentium a 32 bit, i registri AX,BX,CX e DX in realta' sono
solo la parte inferiore dei registri EAX,EBX,ECX,EDX composti da 4 byte.
CS , IP : Questi due registri contengono il segmento ed l'offset del codice. Per
cui l'indirizzo di memoria CS:IP e' l'indirizzo del comando assembler che sta per
essere eseguito.
SS , SP : Contengono il segmento e l' offset dello stack. Nello stack ci vanno
tutte le variabili momentanee.
DS , SI : Contengono il segmento e l'offset dei dati. Nelle protezioni meno
sofisticate, spesso nel segmento di dati si puo' trovare la password corretta,
oltre all'eco dell'input che viene dato alla procedura di controllo. Ma di questo
parleremo piu' avanti.
ES , DI : Segmento e offset di varia utilita'.
Flag : Questo registro contiene delle variabili booleane (cioe' che possono
assumere solo due valori: vero o falso) che vengono aggiornate dopo ogni
operazione, a seconda del risultato. Ad esempio l'operazione OR AX,AX (mette in
AX il valore AX OR AX) che sembrerebbe inutile, in realta' modifica il flag ZERO,
ponendolo vero se AX e' zero, falso in caso contrario.
Il comando CMP (compare) viene usato per modificare i flag.
Vi sono altri registri, ma per crackare un programma e' piu' che sufficiente
conoscere quelli che ho elencato.
Ora veniamo ai comandi principali (e piu' utili per il cracker) dell'assembler.
Il comando MOV permette di assegnare un valore ad un registro o ad un indirizzo
di memoria; facciamo alcuni esempi:
MOV AX,BX Mette in AX il valore di BX
MOV AL,[BP] Mette in AL il byte contenuto nell'indirizzo di
memoria DS:BP (NB Quando non e' specificato alcun
segmento viene usato DS)
MOV CX,0012 Pone CX uguale a 0012
MOV BX,ES:[0128+CX] Mette in BX il valore all'indirizzo ES:[0128+CX]
MOV [0A31],AX Scrive nell'indirizzo di memoria DS:0A31 il valore
di AX
Il comando JMP serve per "saltare" lungo il codice, cioe' un comando JMP 021A
mandera' il computer ad eseguire l'istruzione contenuta nell'indirizzo di memoria
CS:021A e le successive.
Il comando CMP, come abbiamo gia' visto serve ad aggiornare i flag, e la sintassi
e' molto simile a quella del comando MOV, nel senso che richiede due valori.
Spesso viene seguito da un JMP condizionato, ad esempio:
CMP AX,BX <- Compara AX con BX e aggiorna i flag
JLE 021A <- Jump if Less or Equal, cioe' se uno dei flag LESS o EQUAL sono
veri, salta all'istruzione 21A
In questo caso se AX e' minore o uguale a BX, il salto avviene, altrimenti il
Jump viene ignorato.
Alcuni dei JMP condizionati sono:
JL Salta se minore
JLE Salta se minore o uguale
JE Salta se uguale
JGE Salta se maggiore o uguale
JG Salta se maggiore
JZ Salta se zero
JNZ Salta se non zero
JCXZ Salta se CX e' zero
In realta' ce ne sono molti altri, ma questi sono i piu' usati, ed inoltre per
crackare programmi non serve conoscere a memoria tutti i tipi di jump, per cui
non mi soffermero' oltre su questo argomento.
Il comando INT serve a chiamare un interrupt (gli interrupt sono le procedure
standard e basilari presenti nel computer).
Ad esempio:
+-> MOV AH,0B <- Funzione 0B, controlla lo stato della tastiera
| INT 21 <- Esegue l'interrupt 21, funzione 0B
| CMP AL,FF <- Se AL vale FF in uscita dall'interrupt, vuol dire che
| e' stato premuto un tasto
| JE A035 <- Vai a leggere il tasto
+---JMP 9E0F <- Ricomincia il ciclo
Il comando CALL funziona un po' come il comando JMP, con queste differenze:
1) Non esistono CALL condizionate
2) Si puo' saltare ad un segmento di codice differente, mentre un JMP non
permette di accedere ad un altro segmento
3) Viene memorizzato l'indirizzo da cui e' partita la CALL, e l'istruzione RET
permette di ritornarci
---------------------------------------------------------------------Finalmente!
Ora veniamo alla parte piu' interessante...
Il cracking vero e proprio!
Con cosa cominciare? Be', io ho allegato a questa guida un programma che ho
scritto che chiede una password (crackme.exe) e che spieghero' come crackare.
La protezione di questo programma e' veramente semplice, cominciare con questo
e' l'ideale per imparare.
Prima di passare al cracking delle password e' consigliabile anche provare a fare
qualche cheat su dei vecchi giochi (per cheat intendo modificare il codice in
modo da avere delle facilitazioni nel gioco, ad esempio, che so, vite infinite).
Il vantaggio delle cheat e' che le aree di codice che andate a studiare e
modificare in genere sono prive di alcuna protezione, quindi questo tipo di
"cracking" e' abbastanza semplice.
NB: Prima di cominciare a crackare "pesantemente" (cioe' modificando il codice)
un programma, siate sicuri che NON C'E' ALTRO MODO!! MOLTE PROTEZIONI SONO PIU'
STUPIDE DI QUELLO CHE SEMBRANO! Ad esempio, su una rivista di astronomia tempo
fa distribuivano un CD con alcuni shareware, tra cui il demo di un gioco che si
chiamava Crazygravity. Il demo permetteva di giocare solo i primi tre livelli,
poi chiedeva di registrare il gioco. In realta' era una protezione pressoche'
inutile, poiche' bastava rinominare il file corrispondente al livello che
volevate giocare (ad es. "level09.cgl") e dargli il nome del primo
("level01.cgl"). Cominciando una nuova partita il programma non si accorgeva
della modifica e vi permetteva di giocare qualsiasi livello come se fosse il
primo!!
I passi fondamentali per crackare una password sono questi:
1) Trovare dove il programma memorizza l'input alla richiesta della password
2) Mettere dei breakpoint sull'area di memoria in cui si trova la mia digitazione
3) Trovare la procedura che confronta cio' che ho scritto con la password
corretta
4) Scoprire e modificare il JUMP CRITICO della procedura
Il jump critico e' spesso preceduto da un compare che ha la funzione di
verificare se la password inserita e' corretta. Il risultato del compare viene
memorizzato nei flag, e il jump successivo si comporta di conseguenza, ad esempio
una classica procedura di controllo potrebbe essere questa:
CMP carattere_password_corretta, carattere_input_utente
JNZ Ha!_questo_carattere_non_corrisponde!cacciamo_fuori_questo_bastardo!
In questo caso questi due comandi controllano UN CARATTERE della password, e nel
programma devono essere ripetuti per tutta la lunghezza della password. Vi sono
molti modi per crackare una routine come questa, ad esempio:
- Sostituire al JNZ:
JMP Istruzione_successiva
In questo caso anche se il compare da' un risultato sbagliato, il programma
prosegue come se niente fosse!
- Sostituire al CMP:
CMP carattere_password_corretta, carattere_password_corretta
Qui gli facciamo confrontare la password corretta con se stessa! E' ovvio che
risultera' uguale!
PROVIAMO ORA A CRACKARE INSIEME IL FILE CRACKME.EXE
NB: Gli indirizzi di memoria che il programma utilizza sono relativi al MIO
computer, quindi potrebbero essere diversi!
Prima di tutto eseguitelo normalmente per rendervi conto di quello che fa. Fatto?
Bene.
Ora caricate il vostro debugger (che considerero' il softice, d'ora in poi, ma
esistono comandi analoghi anche per gli altri debugger). Se usate il softice
dovrete metterlo in memoria PRIMA di far partire windows, aggiungendo
nell'autoexec il programma winice.exe.
Ora, bisogna prima di tutto scoprire dove il programma mette tutti i dati (il
segmento dati - DS). Se eseguendo il programma si entra nel debugger (CTRL+D),
si finira' per trovarsi impelagati nelle procedure del BIOS (per forza! Il
programma sta aspettando un input!), che a noi non interessano; e inoltre i
valori dei registri vengono modificati quando si entra in queste procedure,
quindi DS NON E' il segmento di dati che ci interessa!
Le possibilita' sono due, a questo punto:
1) Si mette un breakpoint su una chiamata ad interrupt - ad esempio col comando
BPINT 21 - In questo modo se il breakpoint viene attivato ci si trovera' nel
codice che ci interessa.
2) Si carica il programma mediante il file Ldr.exe (per i programmi DOS dovrete
usare il file ldr.exe di una versione DOS del softice). In questo modo ci si
trovera' dentro al softice all'inizio del codice del programma.
Utilizzando il primo metodo, proviamo ad eseguire il file, e appena viene
richiesta la password andiamo nel softice.
Inseriamo i comandi:
BPINT 21
BPINT 16
Cosi' facendo abbiamo messo dei breakpoint sui principali interrupt che si usano
per leggere l'input da tastiera. Ora torniamo al nostro programma (comando X), e
non appena premiamo un tasto, ecco che riappare il softice!
Ora siamo dentro al codice di crackme.exe, per cui diamo un'occhiata ai valori
dei registri per vedere se troviamo qualcosa di interessante...
D DS:0000
Dando un'occhiata nella fiestra della memoria possiamo vedere... TUTTE LE
PASSWORD!! Questo programma ha una procedura di protezione parecchio obsoleta. Le
password sono li', non criptate e facilmente visibili.
Ora segnamoci su un foglio il valore di DS, togliamo i breakpoint che abbiamo
messo e torniamo al programma.
BC *
X
Scriviamo come input della password una cosa qualsiasi, tipo "stronzata" E NON
PREMIAMO INVIO. Premaimo invece CTRL+D per tornare al softice. Scriviamo:
S $xxxx:0000 L FFFF 'stronzata'
Mettendo al posto di xxxx il valore del segmento dati che avevamo trovato prima.
Cosi' facendo abbiamo cercato nel segmento dati la stringa che avevamo appena
scritto nell'input. Ammesso che il risultato sia xxxx:1234, scriviamo:
S $xxxx:1235 L FFFF 'stronzata'
Per essere sicuri che l'input non venga copiato anche in altre locazioni.
Fatto cio' mettiamo un breakpoint sull'indirizzo xxxx:1234 (sul primo carattere
dell'input):
BPM xxxx:1234
Torniamo al programma e diamo invio. Se avete fatto tutto bene dovrebbe
riapparire il softice. Infatti per confrontare la password a quella corretta il
programma ha bsogno di leggere la memoria all'indirizzo xxxx:1234 (e successivi).
Facendo cio' scatta il nostro breakpoint, che fa riapparire il softice.
Dando un'occhiata al codice scopriamo che l'operazione che ha fatto scattare il
breakpoint e':
MOV AL, ES:[BX+DI]
Quindi il valore del primo carattere dell'input e' stato messo in AL. Segue un
paio di istruzioni piu' tardi un RET, che vogliamo tracciare per sapere cosa
succede ad AL. Premiamo F8 fino ad arrivare al RET.
F8 ancora ci porta ad una routine di questo tipo:
0693 CMP AL,0D
0695 JZ 06A5
0697 CMP AL,1A
0699 JZ 06A5
069B INC BX
069C INC CX
069D INC SI
069E MOV [SI],AL
L'ultima istruzione mette il valore di AL, in DS:SI. Quello che fa questa routine
e' copiare carattere per carattere l'input in un'altra locazione di memoria!
Mettiamo quindi un breakpoint anche sulla nuova locazione per tenere d'occhio
anche quella:
BPM DS:SI+1
...E andiamo avanti!
X
Il softice riappare nuovamente. Questa volta e' scattato il breakpoint sulla
nuova locazione di memoria. Se non l'avessimo messo, avremmo perso questo ed i
successivi break!
Diamo uno sguardo al codice:
0123 INC BYTE PTR [0185] <- Incrementa la locazione DS:0185
0127 MOV AL,[0185] <- Mette in AL il byte DS:0185...
012A XOR AH,AH <- ...e azzera AH
012C MOV DI,AX <- Copia AX in DI
012E MOV AL,[DI+0188] <- **** Questa istruzione genera un breakpoint!
0132 PUSH AX
0133 CALL XXXX:XXXX <- Capire questa call per capire la routine!
0138 MOV DL,AL <- Il risultato della call (che evidentemente stava
in AL) viene messo in DL...
013A MOV AL,[0185] <- ...mentre in AL ci va il solto byte DS:0185
013D XOR AH,AH
013F MOV DI,AX <- DS:0185 viene messo anche in DI
0141 MOV [DI+0188], DL <- **** Anche questa istruzione genera un breakpoint!
0145 MOV AL,[0185]
0148 CMP AL,[BP-01] <- Se DS:0185 e' diverso da [BP-01]...
014B JNZ 0123 <- Ricomincia il giro!
Per capire che questa routine non c'entra con la protezione, basta steppare (F10)
un po', dando un'occhiata a come viene modificata l'area di memoria contenente
l'input della password. Quello che fa questa routine e' sostituire ad ogni
carattere il suo upcase, trasformando il nostro input "stronzata" in "STRONZATA"!
Il byte DS:0185 contiene un "contatore" che indica su quale carattere la routine
sta lavorando, e ad ogni ciclo viene confrontato con DS:[BP-01], che
probabilmente contiene la lunghezza dell'input.
La call all'indirizzo CS:0133 restituisce l'upcase del carattere in AL, e
l'istruzione CS:0141 riscrive l'input in maiuscolo.
E' ovvio che era necessaria una routine di questo genere nel programma, poiche'
se avesse confrontato un input in minuscolo con la password corretta (in
maiuscolo, come abbiamo visto) sarebbe COMUNQUE risultata diversa!
Quindi, visto che questa routine non ci iteressa, andiamo avanti:
X
ed ecco che riappare il softice:
0B88 REPZ CMPSB <- **** Breakpoint generato da questa istruzione
0B8A JNZ 0B8E
0B8C CMP AL,AH
0B8E MOV DS,DX
0BC0 RETF
Questo sembra molto interessante! Seguiamo il return...
0025 JNZ 002C
0027 MOV BYTE PTR [0288],01
002C POP BP
002D RET
Tracciamo il codice ancora un po'...
0150 CMP BYTE PTR [0288],01
0155 JNZ 0175
Ecco finalmente quello che volevamo trovare! Notate l'istruzione REPZ CMPSB.
Questa e' un'istruzione chiave in molte procedre che devono controllare una
password. In pratica confronta le stringhe agli indirizzi DS:SI e ES:DI.
La vera chiave della procedura di protezione, e' la locazione di memoria DS:0288!
Infatti essa (che originariamente e' uguale a zero) viene posta uguale a 1
nell'istruzione 0027 SOLO NEL CASO IN CUI IL JUMP 0025 NON VENGA ATTIVATO, cioe'
solo se la password inserita e' uguale a quella richiesta!
Il successivo compare che avviene dopo il return, fa scattare il jump 0155, che
manda il programma a scrivere "password non corretta" anziche' "Esatto!!!".
Ora che abbiamo trovato la procedura di controllo, come crackarla? Be', ci sono
tanti modi, e piu' o meno uno vale l'altro.
Vediamone qualcuno:
1) NOPpare il jump dell'inidirizzo 0025, cioe' sostituirlo con due NOP (comando
che significa Nessuna OPerazione, e non fa assolutamente nulla!).
2) Sostituire sempre al jump dell'indirizzo 0025 il comando JNZ 0027. Cosi' anche
in caso di password sbagliata non verra' saltato il comando 0027.
3) Sostituire al compare dell'indirizzo 0150 l'istruzione:
CMP BYTE PTR [0288],00. In questo caso, il programma vedra' sbagliata una
password corretta e corrette tutte le altre!
4) Sostituire al jump dell'indirizzo 0155, l'istruzione JZ 0175, cambiando il
"Jump if NOT Zero" con "Jump if Zero". Il risultato e' lo stesso del punto
precedente.
NB: Bisogna sostituire un comando con uno DELLA STESSA LUNGHEZZA, altrimenti il
resto del codice verra' orribilmente sfigurato!! Provate a sostituire un jump (2
byte) con UN SOLO NOP (1 byte), e date un'occhiata al codice successivo!
Proviamo a vedere se il terzo crack puo' funzionare... mettete un breakpoint
all'istruzione 0B88:
BC *
BPX CS:0B88
EXIT
...E riavviate il programma daccapo.
Quando avete inserito la password, il softice riappare in seguito al breakpoint
appena messo.
A questo punto andiamo a vedere nella finestra del codice l'istruzione 0150.
CODE ON
Con questo comando nella finestra del codice viene messa la sequeza di byte che
corrisponde a ciascuna operazione.
Dovremo vedere qualcosa di questo tipo:
XXXX:0148 3A 46 FF CMP AL,[BP-01]
XXXX:014B 75 D6 JNZ 0123
XXXX:014D E8 B0 FE CALL 0000
XXXX:0150 80 3E 88 02 01 CMP BYTE PTR[0288],01
XXXX:0155 75 1E JNZ 0175
XXXX:0157 BF 9E 03 MOV DI,039E
SCRIVETEVI LE SEQUENZE DEI BYTE da alcune istruzioni prima ad alcune istruzioni
dopo quella che vogliamo modificare.
Ora modifichiamo il compare:
A CS:0150
XXXX:00000150 CMP BYTE PTR [0288],00
XXXX:00000155 [Invio]
Annotatevi anche la sequenza di byte del codice cosi' modificato. In questo caso
il cambiamento e' di questo tipo:
80 3E 88 02 01 -> 80 3E 88 02 00
Torniamo al programma:
X
e... magia! Il programma ci dice che la password che abbiamo inserito e' corretta
(amenoche' non abbiate messo proprio quella giusta...)!
Ora, visto che il crack funziona, vogliamo modificare il file crackme.exe in modo
che ogni volta che lo carichiamo sia gia' crackato senza bisogno di usare il
softice. In realta', infatti, quello che abbiamo appena modificato, era solo
l'area di memoria in cui era stato caricato il contenuto del file crackme.exe!
Se noi lo eseguiamo daccapo, il compare sara' ancora come prima!
Per modificare il file eseguibile dobbiamo armarci del nostro hex editor.
Apriamo quindi crackme.exe con l'hex editor, e cerchiamo la stringa
(esadecimale):
E8 B0 FE 80 3E 88 02 01
Appena la funzione di ricerca ci da' il risultato controlliamo i byte precedenti
e successivi, per verificare che corrispondano, e cerchiamo ancora per verificare
che non vi sia un'altra serie di byte uguale a questa. Fatto cio' sostituiamo 01
con 00, salviamo, ed eseguiamo ancora crackme.exe.
Tutto funziona alla perfezione! Complimenti, avete portato a termine il vostro
primo crack!
Le procedure di protezione di alcuni programmi, pero', sono piu' complicate
rispetto a quella che abbiamo visto insieme. Quasi sicuramente programmi piu'
complessi utilizzano piu' di un segmento dati, e non vi sara' cosi' semplice
scovare l'eco della password in memoria. Talvolta le procedure di protezione si
trovano in aree di codice che vengono create mentre il programma e' in
esecuzione, e quindi non possono essere trovate da un hex editor!
L'importante e non perdersi d'animo di fronte alle prime difficolta', ma
continuare a sperimentare, poiche' nessuna guida si puo' sostituire
all'esperienza diretta.
Con questo concludo, sperando di poter esservi stato di aiuto nell'aprirvi le
porte del fantastico mondo del cracking!
Un grazie a tutti gli SpiPPoLaTorI per il lavoro che stanno facendo per il
nostro sito, in particolare a RigoR MorteM per il logo lassu' in cima, e a Stefy
per la sua preziosissima opera di coordinamento!
---------------------------------------------------Flamer per il club SpPPoLaTorI