Ok, intanto una breve intro. Il mio scopo è di fornire un aiuto a chi vorrebbe
iniziare a giokare kol debugger, principalmente per i wannabecrackers che non
sanno dove sbattere la testa. Se infatti è vero che la Rete offre una vagonata
di testi e tutorialz, questi sono in inglese e soprattutto prevedono ke ki li
legge sia già kapace di fare kuello ke viene spiegato (un po' kome le banke ke
per darti dei soldi pretendono ke tu li abbia già...).
Cerkerò anke di dare qualke dritta sui toolz e su kome usarli, sempre partendo
dal presupposto di skrivere per persone ke di crackin' non sanno (ankora;)
nulla. Io stesso non sono un guru del Reverse Engeneering, sono anni ke non
metto mano al debugger, pratikamente da kuando trovo kuello ke mi serve già
pronto sulla Rete, e sono sikuramente più agile usando il mio vekkio DEBUG.COM
ke i più recenti SoftICE, IDA & C.
Bene, that's all, si komincia.
Innanzitutto crackare un programma (io ho il vizio di dire 'sproteggere') vuol
dire modificarne il comportamento al fine di ottenerne un utile, ovvero per
esempio evitare ke ci kieda kodici di registrazione, eliminare i fastidiosi NAG
screen (quelle skermate ke ci fanno il pistolotto affinkè si metta mano al
portafogli), evitare di dover kopiare tutto un CD pieno zeppo di minkiate
filmati orripilanti ke okkupano inutilmente centianaia di Mb un tempo preziosi e
ora inkredibilmente sprekati); insomma, crakkare! :)
Sfortunatamente non esiste *IL* metodo per crakkare, esistono solo alcune linee-
guida generali. Il vero cracker deve trovare sempre la soluzione migliore kaso
per kaso, a volte mi è bastato cerkare un po' nei files di certi gioki per
trovare qua e là le password e mettere sempre la stessa ovunkue (parlo di kuei
gioki per DOS di svarati anni fa ke kiedevano la tal parola in tale pagina o
amenità simili, vedi MechWarrior, Search for the King e moltissimi altri, era
una protezione molto in voga insieme ai diski kiave).
Il metodo più ovvio comunque rimane quello di mettere mano al codice eseguibile
del programma, e a questo punto occorre necessariamente fare un passo indietro.
Creare un programma vuole dire creare un codice sorgente in un linguaggio ad
'alto' livello (C/C++, Pascal, Basic (blearp)) e poi passarlo ad un compilatore
che crea il codice oggetto, e ankora passare il codice oggetto ad un linker per
creare l'eseguibile finale, che ora come ora possiamo immaginare essere una
sequenza di istruzioni elementari in linguaggio macchina. Il problema nasce dal
fatto che mentre il sorgente è abbastanza chiaro, perlomeno per i nostri scopi
(vi sfido a kapire kualkosa o trovare *UN* kommento nei miei programmi ...), una
volta kompilato e ottenuto l'eseguibile finale (.EXE, .COM o .DLL per WinZozzo)
si perde pratikamente ogni informazione di massima sul programma, ke deve essere
kuindi ispezionato kon un debugger per tracciarne l'esekuzione e rikostruirne il
funzionamento. Diciamo ke la creazione di un file eseguibile è un procedimento
one-way, ovvero che non esiste un metodo per ricostruire esattamente il sorgente
originale partendo dal suo compilato, sempre appunto a kausa della perdita di
informazioni (nomi di variabili, tipi di routines etc. etc.).
Ho fatto kuesta premessa perkè vorrei far kapire ke la diffikoltà più grossa per
un cracker è kuella di farsi un' idea del funzionamento del programma, di kome è
strutturato e di dove andare a cerkare la parte di kodice su kui intervenire.
Veniamo al sodo ora.
Eravamo rimasti al linguaggio makkina, urge spiegazione ;) Il linguaggio makkina
è un codice ke è direttamente eseguibile dal processore, senza ulteriori
trasformazioni. Il metodo per leggere il linguaggio macchina è di utilizzare un
debugger, un altro programma che ci permette di visualizzare in un codice
mnemonico più 'umano' le operazioni che il processore andrà a kompiere ed
eventualmente tracciarne passo passo l'esekuzione. Si tratta ovviamente di
operazioni elementari come somme, shiftamenti, salti & kompagnia bella,
visualizzate kon diciture kriptike come SUB AL,BL (Subtract BL from AL), JNZ
XYZK (Jump if NotZero to XYZK) e altre amenità.
Purtroppo non ho il tempo di fare un korso di l.m. , probabilmente nemmeno le
konoscenze, per questo konsiglio kaldamente a tutti di imparare un minimo di
linguaggio makkina prima di tentare qualsiasi approccio; Forse riuscirete ad
arrivare lo stesso alla fine del tutorial, ma di fronte ad un programma vero
probabilemte vi verrà la voglia di lasciar perdere tutto e fankulo il crackin'.
Nella mia 'karriera' di cracker ho imparato ke le istruzioni più interessanti
sono davvero poke, non okkorre essere dei maniaci dell'assembler e questo di
certo facilita le cose. Una delle istruzioni più incontrate in percentuale
all'interno di un programma è di sicuro quella di kiamata di procedura, una
sorta di GOSUB ke in realtà è kodifikata come CALL. La sua sintassi è CALL
[indirizzo]. Vediamo un esempio stupido per fissare le idee. Immaginiamo per un
momento di essere nel bel mezzo dell'esekuzione di un programma DOS, con una
rappresentazione del kodice del tipo segmento:offset (non kambia kuasi nulla
sotto Win, l'importante è il koncetto) :
SEGM:OFFS CODICE MNEMONICO
1F47:0100 BB1000 MOV BX,0010
1F47:0103 B82000 MOV AX,0020
1F47:0106 E8F71E CALL 2000
1F47:0109 3D0000 CMP AX,0000
1F47:010C 742B JZ 0139
. . .
. . .
Allora, kuesto esempio davvero banale serve ad illustrare alkune istruzioni:
La prima e la sekonda istruzione karicano il valore 0x0010 esadecimale nel
registro BX e 0x0020 esadecimale nel registro AX; i registri del processore sono
una sorta di 'variabili' interne utilizzabili più o meno liberamente per
eseguire dei kalkoli, delle assegnazioni o per tenere l'indirizzo di posizioni
partikolari di memoria, e sono ovviamente più di 2.
La terza istruzione è una kiamata di procedura, kome dicevo una sorta di GOSUB e
RETURN del BASIC. Kuando il processore inkontra una CALL, per prima kosa salva
nello stack il valore korrente dell' offset all'interno del segmento, valore ke
si trova nel registro IP (EIP nella modalità 386 estesa), in modo da potervi
tornare una volta terminata la procedura, dopodikè salta alla posizione
1F47:2000 dove presumibilmente è kontenuto il kodice della procedura. Vediamolo :
SEGM:OFFS CODICE MNEMONICO
1F47:2000 51 PUSH CX
1F47:2001 52 PUSH DX
. . .
[ kodice della routine ]
. . .
1F47:24AE 5A POP DX
1F47:24AF 59 POP CX
1F47:24B0 C3 RET
Le prime due istruzioni salvano il valore dei registri CX e DX nello stack, una
zona di memoria riservata individuata dai registri SS:SP (Stack Segment:Stack
Pointer) ke funziona kome una pila di oggetti, nella kuale il primo oggetto
estratto è kuello ke è stato inserito per ultimo. Per kuesto le due istruzioni
POP finali hanno l'ordine invertito rispetto alle PUSH.
L'istruzione RET ha il kompito (normalmente) di far tornare il programma
all'istruzione immediatamente successiva a kuella della kiamata, nel nostro kaso
a 1F47:0109 CMP AX,0000 , in pratika facendo una sorta di 'POP IP'.
Immaginiamo ora ke la procedura kompresa tra 1F47:2000 e 1F47:24B0 esegua
svariati kompiti, ke kontenga ank'essa a sua volta delle kiamate, tra kui magari
la verifika della registrazione del kodice del programma stesso, e ke subito
prima di ritornare a 1F47:0109 imposti il registro AX a 0 o a 1 , a seconda ke
il programma sia registrato o meno. In kuesto kaso non ci importa un kakkio di
KOME tale verifika venga fatta, a noi importa sapere ke il proseguimento del
programma è influenzato (per kuel ke riguarda la registrazione dello stesso) dal
valore assunto da AX.
l' istruzione 1F47:0109 CMP AX,0000 konfronta il valore (CoMPare) di AX kon 0, e
viene settato un flag sempre del processore (il flag Zero o ZF; in realtà anke
il Carry Flag, CF, se i 2 operandi sono Unsigned, ma a noi non ce ne frega una
cippa :);
Se i due operandi, qui AX e 0 kombaciano, ovvero se AX=0 kuesto benedetto flag
ZF viene posto a 1, tutto qui; l'istruzione successiva, 1F47:010C JZ 0139 (Jump
if Zero to 0139) è un salto kondizionato dal valore del flag ZF : se ZF vale 0
il programma kontinua il suo flusso regolarmente, mentre se ZF vale 1 il
processore 'salta' alla lokazione 1F47:0139. A kuesto punto è kiaro ke tra
1F47:010C e 1F47:0139 si troverà la parte di kodice ke visualizza il NAG screen,
magari una procedura di registrazione o kualke altra rottura di palle, e ke a
1F47:0139 invece inizia il programma vero e proprio. Inutile dire ke in questo
kaso banale e fin troppo inventato basta mettere un salto incondizionato nella
locazione di memoria 1F47:010C ovvero un bel JMP 0139 (JuMP to 0139) ed ottenere :
SEGM:OFFS CODICE MNEMONICO
1F47:0100 BB1000 MOV BX,0010
1F47:0103 B82000 MOV AX,0020
1F47:0106 E8F71E CALL 2000
1F47:0109 3D0000 CMP AX,0000
1F47:010C EB2B JMP 0139 <---- MODIFIKATO
. . .
. . .
L'ultima kosa per questa introduzione è il fatto ke nel modifikare le istruzioni
del programma bisogna assulutamente rispettare gli 'spazi' a disposizione !
Kuesto è MOLTO importante, per non inkasinare il kodice del programma. Se io
avessi voluto ad esempio mettere l'istruzione di salto inkondizionato subito
dopo la kiamata, ovvero al posto del CMP AX,0000 (oramai assolutamente inutile),
poteva succedere una kosa del tipo :
SEGM:OFFS CODICE MNEMONICO
1F47:0100 BB1000 MOV BX,0010
1F47:0103 B82000 MOV AX,0020
1F47:0106 E8F71E CALL 2000
1F47:0109 EB2E JMP 0139 <---- MODIFIKATO
1F47:010B 00742B ADD [SI+2B],DH <---- SPUTTANATO
. . .
. . .
Ke kazz è successo? Semplice, l'istruzione CMP AX,0000 è 'lunga' 3 byte, mentre
JMP 0139 solo 2 byte, e kuindi il terzo byte '00' è stato inkorporato
nell'istruzione successiva, ke il processore ha interpretato kome ADD [SI+2B],DH
, una kosa in kuesto kaso partikolare irrilevante perkè non verrà mai eseguita,
ma decisamente brutta e potenzialmente distruttiva in altre situazioni (kompito
a kasa : tradurre ADD [SI+2B],DH ;-). Ma se sono testardo e voglio a tutti i
kosti mettere il mio JMP nella lokazione 1F47:0109 ?!?! Ekkeppalle , ok ok...
C'è una istruzione dei processori 80X86 ke serve a fare nulla, si kiama NOP e
corrisponde all'esadecimale 0x90 (kuale cracker non lo sa?!?!), lunga un byte e
kon la kuale 'aggiustare' il kodice zoppikante. L'ultima versione potrebbe
essere kuella ottenuta mettendo all ' indirizzo 1F47:010B l'istruzione NOP
ottenendo :
SEGM:OFFS CODICE MNEMONICO
1F47:0100 BB1000 MOV BX,0010
1F47:0103 B82000 MOV AX,0020
1F47:0106 E8F71E CALL 2000
1F47:0109 EB2E JMP 0139 <---- MODIFIKATO
1F47:010B 90 NOP <---- AGGIUNTO
1F47:010c 742B JZ 0139 <---- RIPRISTINATO (ma non viene mai eseguito)
. . .
. . .
Ovviamente si potrebbe NOPpare anke il JZ 0139, ma perkè farlo ? :-) Kosì va
ugualmente, almeno per kuesto esempietto. Al posto dei NOP, se sono tanti e si
sospetta ke il programma in kualke maniera possa kontrollare se il proprio
kodice è stato alterato kontando appunto le okkorrenze di NOP consekutivi si
possono aggiungere istruzioni 'inutili' kome una sequenza pari di DEC AX e INC
AX o tutto kuello ke puo' venirvi in mente ke non alteri il kodice; Per favore
non fate kritike sulla banalità dell'esempio, era voluta e so benissimo ke in
kasi reali le kose non vanno kosì (perlomeno non più; Terminator 2 era un
giokino skifoso almeno kuanto la sua protezione, addirittura era tutto
kontenuto in una CALL, è bastato NOPparla e addio skermata di protezione ;-).
La domanda ke mi aspetterei è : "Ma kome kazzo fai a sapere ke la CALL 2000
esegue le verifike e ke in AX c'è il risultato?!?!?" La risposta ci riporta a
kuello ke dissi nella intro : è tutta questione di kapire kome funziona il
programma, non ci sono skemi fissi. Si prova, si inventa, si tenta, si spera in
una botta di kulo (serve SEMPRE) e prima o poi i programmi cedono. A volte mi
son bastati 10 minuti (vedi Terminator 2) altre volte è stata questione di
svariate ore totali (il Conseal 1.04 ad esempio, ma li' è kolpa dell'
inesperienza kon le Win32 :), altre volte ho rinunciato (si, il tempo è
tiranno...).
Se volete provare ad assemblare l'esempio ke ho riportato, da WIn9X aprite una
finestrella DOS e scrivete 'debug'; vi apparirà un trattino ed il prompt, voi
skrivere A e date invio, dovreste ottenere una kosa del tipo 'XYZK:0100 _ ' :
ora potete digitare le istruzioni (MOV BX,0 etc etc ), e kuando avete finito
date CONTROL+C; date U 100 e invio, se avete fatto tutto bene dovreste trovare
l'esempio, segmento a parte (kuello kambia).
Happy Crackin' a tutti, e andate a rakkattare un buon libro sul linguaggio
makkina degli 80x86. Alla prossima lezione.
T3X
(C) 1998 T3X for RingZer0
_________ ____ ___
########@ a##gg \##B ,##M
########@,#####@ M##C&##" <------------------------=< [ EFnet or IRCnet ]
##@ &#" Q## `#####N
##@ Q#B 7###B GReEtZ GoEs oUt To: Tutti i D4RkSiDErz per le
##@ g#B" ###C kose ke abbiamo saputo fare, ai bros M|kky, Layne,
##@ ###@ ,###@ Slay, Hija, EvilBUU, GOLDRAKE, a Maddevil,
##@ \##a &####a Eko, R4|k3r, Net-Call e tutti quelli ke non
##@ ## ]##J /##@##W ho voglia di menzionare :-)
##@ Q#g_d## @##"Q##g Ovviamente a tutti koloro ke tengono in
##@ `####B" Z##N `##B, piedi il progetto RingZer0
`"""
E' assolutamente VIETATO riprodurre questo testo usando fusilli
Barilla per comporre le lettere nonchè utilizzare le informazioni quivi
contenute per fare colpo sulla compagna di banco strafiga
[tanto non funziona(mai)].