Cracking tutorial part II
Ciao, eccoci alla seconda parte del tutorial sul cracking... ringraziate quel rompipalle di |alexdan| ;)). Questa sezione sara' dedicata solamente all'assembler, lo so che per voi puo' sembrare assolutamente palloso, ma credetemi, è indispensabile. Iniziamo esaminando le istruzioni base, cerchero' di attenermi a cio' che ci servira' per crakkare, non vi voglio annoiare con lunghissime trattazioni filosofiche. Partiamo dai registri, i registri sono locazioni di memoria utilizzate per memorizzare dati e per elaborarli. I registri utilizzati da windows hanno come prefisso una E (ad es: EAX, EBX, ECX, EDX) mentre per programmi dos o windows 3.x che sono sistemi a 16 bit vengono utilizzati registri a 16 bit quindi senza prefisso E (ad es: AX, BX, CX, DX). Nei vecchi sistemi invece, quali z80, 8086, 80286 ecc.. si usano le lettere iniziali senza la x perchè sono registri a 8bit. Inoltriamoci nei registri a 32 bit cioe' quelli che utilizzeremo maggiormente nelle nostre infami azioni crakkistiche. Vi sono 2 tipi di registri, quelli singoli e quelli doppi, i registri singoli sono: EAX, EBX, ECX, EDX, ESI, EDI ecc.. mentre i registri doppi sono: DS:SI e ES:DI. I registri singoli vengono utilizzati per memorizzare i singoli dati da elaborare e gli eventuali risultati, i registri doppi normalmente contengono le stringhe che abbiamo inserito e vengo letti passo passo per importare nei registri singoli le singole parole del nostro username o serial number. I registri possono essere considerati variabili come in altri linguaggi ad alto livello, vi faccio un esempio di un brevissimo codice in basic e uno in assembler cosi' riuscirete a capire meglio:
BASIC:
A=pippo ;Immagazzina in A "pippo"
B=pluto ;Immagazzina in B "pluto"
IF A = B THEN PRINT "Sono Uguali" ;Se sono uguali "Sono Uguali"
ELSE PRINT "Sono Diversi" ;Altrimenti "Sono Diversi"
END IF
ASSEMBLER:
LEA EAX, pippo ;Carica in EAX "pippo"
LEA EBX, pluto ;Carica in EBX "pluto"
CMP EAX, EBX ;Confronta i 2 registri
JNZ XXXX:XXXX ;Salta se sono diversi
Mi sembra abbastanza semplice, immagino facciate fatica a capire cosa sinifica LEA, CMP e JNZ, non preoccupatevi parlero' anche di loro, limitatevi ad osservare i miei commenti affianco al codice. Per poter analizzare queste strane istruzioni dobbiamo conoscere una cosa importantissima cioe' il registro di FLAG. Il FLAG è un registro come quelli di cui ho appena parlato, ma è speciale inquanto non si puo' utilizzare a piacere, ma è predisposto per certe funzioni che ci serviranno moltissimo. I flags sono: o, d, i, s, z, a, p, c Tuttavia quello che interverra' maggiormante nel nostro debugging sara' il flag Z cioe' quel flag che ci dice se la operazioni precedente ha ottenuto come risultato 0 oppure se si è verificata una certa condizione.
Se provate a guardare nel codice assembler che ho scritto prima trovere una istruzione (JNZ XXXX:XXXX) che utilizza questo flag, infatti è un salto condizionato e la condizione è quella che non vi sia il flag di Z attivato. Vi anticipo che JNZ sta per JUMP IF NOT ZERO cioe' tradotto "Salta se l'istruzione precedente non ha attivato il flag 0" infatti vi anticipo anche che l'istruzione CMP cioe' compare o anche in italiano "Confronta" restituisce il flag Z solo se quello che è stato confrontato è identico altrimenti non attiva nessun flag. Ora dovreste riuscire a capire meglio sia l'esempio precedente e l'uso dei flags, ma visto che in assembler capire è sempre una parola molto forzata vi spiego praticamente acosa ci servira' il flag Z e dove possiamo vederlo. Se ad esempio il nostro programma iper protetto controlla tramite un cmp che le lettere del nostro user name siano identiche a quelle preimpostate in memoria dovra' utilizzare un jmp condizionato sul flag Z e noi andremo a cambiare la condizione del jmp in modo opportuno. Ad esempio se come username inseriamo A il programmone carichera' in EAX la lettera A (ATTENZIONE!!! LA LETTERA A NON SARA' NEL REGISTRO IN FORMA ASCII, MA IN FORMA ESADECIMALE!!!) e in EBX mettera' quello che la routine si deve aspettare ad esempio, ci potra' mettere 2 (sempre in esadecimale). A questo punto troveremo il seguente set di istruzioni:
CMP EAX, EBX
JZ XXXX:XXXX
cioe' confronta il nostro username con quello corretto e se sono uguali salta alla procedura che visualizza il mitico messaggio: "Thank You for supporting our product!" Se invece sono diversi saltera' alla procedura che visualizza: "Incorrect Username" porcccc putttt Bastera' semplicemente cambiare il tipo di salto e invece di utilizzare JZ XXXX:XXXX userete JNZ XXXX:XXXX e anche se è errato il programma lo considerera' corretto! Bene bene ora che sapete tutto sui flags (si fa per dire) passiamo all'argomento cruciale cioe' i JMP e tutte le condizioni a cui un JMP puo' essere sottoposto. Di seguito vi fornisco un elenco di tutti i JMP condizionati e cerchero' di spiegarvi il significato di ognuno, inoltre vi faro' vedere come viene utilizzato e come agisce il comando JMP che ovviamente sara' valido per tutti gli altri JMPs.
Lista di Jump condizionati (la consulterete spesso!):
Opcode: Instruction: Description:
77cb JA X8 Jump short if above (CF=0 and ZF=0)
73cb JAE X8 Jump short if above or equal (CF=0)
72cb JB X8 Jump short if below (CF=1)
76cb JBE X8 Jump short if below or equal (CF=1 or ZF=1)
72cb JC X8 Jump short if carry (CF=1)
E3cb JCXZ X8 Jump short if CX register is 0
E3cb JECXZ X8 Jump short if ECX register is 0
74cb JE X8 Jump short if equal (ZF=1)
7Fcb JG X8 Jump short if greater (ZF=0 and SF=OF)
7Dcb JGE X8 Jump short if greater or equal (SF=OF)
7Ccb JL X8 Jump short if less (SFOF)
7Ecb JLE X8 Jump short if less or equal (ZF=1 or SFOF)
76cb JNA X8 Jump short if not above (CF=1 or ZF=1)
72cb JNAE X8 Jump short if not above or equal (CF=1)
73cb JNB X8 Jump short if not below (CF=0)
77cb JNBE X8 Jump short if not below or equal (CF=0 and ZF=0)
73cb JNC X8 Jump short if not carry (CF=0)
75cb JNE X8 Jump short if not equal (ZF=0)
7Ecb JNG X8 Jump short if not greater (ZF=1 or SFOF)
7Ccb JNGE X8 Jump short if not greater or equal (SFOF)
7Dcb JNL X8 Jump short if not less (SF=OF)
7Fcb JNLE X8 Jump short if not less or equal (ZF=0 and SF=OF)
71cb JNO X8 Jump short if not overflow (OF=0)
7Bcb JNP X8 Jump short if not parity (PF=0)
79cb JNS X8 Jump short if not sign (SF=0)
75cb JNZ X8 Jump short if not zero (ZF=0)
70cb JO X8 Jump short if overflow (OF=1)
7Acb JP X8 Jump short if parity (PF=1)
7Acb JPE X8 Jump short if parity even (PF=1)
7Bcb JPO X8 Jump short if parity odd (PF=0)
78cb JS X8 Jump short if sign (SF=1)
74cb JZ X8 Jump short if zero (ZF = 1)
Non vi traduco tutti i singoli jump, vi faccio un piccolo dizionario dei termini utilizzati:
opcode: è il codice esadecimale del jump
instruction: significa istruzione
description: significa descrizione
jump: significa (è scontato) salto
X8: è l'indirizzo del salto
short: significa letteralmente "corto"
if: corrisponde a "se"
above: "circa"
below: "minore"
carry: "riporto"
register: "registro"
is: "è"
equal: "uguale"
greater: "maggiore"
less: "inferiore"
not: "non"
or: "o"
overflow: ehmmm non saprei tradurlo forse... "eccedenza"
parity: "parita'"
even: "pari"
odd: "dispari"
Ecco fatto, qui avete la lista dei jump condizionati, ce ne sarebbe un'altra praticamente identica per i salti a 16/32 bit, ma cambia solo l'opcode e vi evito il supplizio.
Passiamo ad un esempio di JMP che come dicevo puo' essere applicato a tutti gli altri tipi di JMP.
JMP XXXX:XXXXX
JMP XXXX
Queste sono le due forme piu' comuni di JMP, quando il programma incontra l'istruzione JMP XXXX:XXXXX salta al codice contenuto nella locazione specificata al posto di XXXX:XXXX, in termini tecnici il registro PC (program counter) viene settato all'indirizzo XXXX:XXXX.
Questo salto è chiamato "assoluto", cioe' avviene sempre qualsiasi cosa sia successa prima nel codice. I JMP che vi ho elencato precedentemente sono chiamati "condizionali" cioe' avvengono solo se si verifica la "condizione" espressa dall'istruzione. Il JMP puo' essere paragonato all'istruzione GOTO del BASIC. Direi di aver detto tutto cio' che ci interessa per quello che riguarda la sezione jumps e possiamo procedere verso lo stack!. Lo stack è una memoria particolare di tipo LIFO (Last in first out) tradotto in italiano: "Il primo ad entrare è il primo ad uscire", molto spesso lo si paragona ad una pila di piatti in cui l'ultimo ad essere posto sulla cima è il primo ad essere utilizzato.
Basta, basta, basta tanto a voi non ve ne frega un cazzo, passiamo ad analizzare lo stack in modo pratico, cioe' le funzioni di esso che utilizzeremo per crakkare i programmini. Le istruzioni che i programmi utilizzano per interagire (non me ne ero reso conto, ma sono proprio colto.....) con lo stack sono due rispettivamente PUSH e POP fanno un po' ridere, ma si chiamano cosi'! PUSH registra un dato nello stack mentre POP lo preleva. Ma a cosa ci serve questa roba??? Quando un programma invoca la API GetWindowText deve precedentemente memorizzare nello stack la parola che andra' confrontata con quella che inseriremo noi. Aspettate non è cosi' semplice... molto spesso la parola messa nello stack è stata preventivamente cryptata e quindi non è possibile vederla in chiaro, ma questo sara' un argomento che tratteremo piu' avanti nelle prossime parti del tutorial. Vi fornisco un esempio di come funziona PUSH e di come lo utilizzeremo nei crack:
MOV EDI,[ESP+00000220] ; Assume l'Handle del dialog box in EDI
PUSH 00000100 ; PUSH (4) Dimensione massima stringa
PUSH 00406130 ; PUSH (3) Indirizzo buffer testo
PUSH 00000405 ; PUSH (2) Identificatore di controllo
PUSH EDI ; PUSH (1) Handle del dialog box
CALL GetWindowText ; Chiama la funzione
Notate l'istruzione PUSH 00406130 è diretta all'indirizzo del buffer di testo cioe' alla procedura che genera il serial # o il rekey, bastera' analizzarla per ottenere l'algoritmo. L'argomento stack è concluso per quello che ci interessa, di seguito vi elenco alcune delle istruzioni che verranno utilizzate, ma non mi dilunghero' su ognuna, il significato è semplice e in tutti i casi nelle prossime perti del tutorial ci troveremo a contatto con queste istruzioni ed impararete ad utilizzarle.
-Istruzione AND
Esegue l'operazione logica AND tra due ingressi e mette il risultato nel primo fattore dell'operazione
Utilizzo: AND dest,src
Dove dest è il primo fattore e src è il secondo.
-Istruzione CALL
Esegue una funzione all'indirizzo "address"
Utilizzo: CALL address
-Istruzione MOV
Copia il contenuto di src in dest
Utilizzo: MOV dest,src
-Istruzione OR
Esegue l'operazione logica OR tra due ingressi e mette il risultato
nel primo fattore dell'operazione
Utilizzo: OR dest,src
Dove dest è il primo fattore e src è il secondo.
-Istruzione RET
Dice al programma ti ritornare nel punto in cui era stata eseguita
la CALL alla suddetta funzione.
Utilizzo: RET