giovedì 17 maggio 2018

Come Crackare Un Programma | Reverse Engineering - Lezione 1

In questa serie ci addentreremo nel mondo del reverse engineering. Inizieremo con una veloce panoramica e una rapida ricapitolazione del cracking e del linguaggio Assembly (non si chiama Assembler come molti pensano!) per poi entrare nel vivo del corso: Reverse Engineering | Come Crackare Un Programma. Buona prima lezione.

Intro

Il reverse engineering è un argomento fondamentale, oserei dire essenziale, nel mondo dell'hacking e della sicurezza informatica. Sul web si trovano molte guide ma poche sono complete, dettagliate e al passo coi tempi. Ho deciso, quindi, di realizzare una serie di articoli e video a riguardo.

Vedremo come applicare il reverse engineering e come crackare un programma. Partiremo con un semplice programma scritto da me in linguaggio C, per poi passare, nelle successive lezioni, a programmi più complessi.

Ci saranno anche dei video per ogni lezione, che puoi trovare nella playlist Reverse Engineering del mio canale YouTube DiventareHacker.

Tuttavia, ti consiglio di vivamente di leggere gli articoli oltre a guardare le video-lezioni poiché in essi saranno approfonditi in maniera più dettagliata i vari argomenti trattati.

Inoltre, se ami il mondo dell'hacking ti consiglio di dare un'occhiata al video-corso completo che ti guiderà passo passo partendo da zero fino a mostrarti tecniche avanzate.

Requisiti

Gli unici due requisiti fondamentali per seguire al meglio questa lezione, ma in generale tutto il corso, sono:
  • Conoscenza base di Assembly
  • Conoscenza base di C o C++
Anche se non sei molto ferrato con l'Assembly, tranquillo, faremo una veloce ricapitolazione in questa prima lezione ripetendo alcune delle istruzioni principali Assembly. Ti basta, quindi, soltanto una conoscenza di base, sommaria, di tale linguaggio.

Occorre anche qualche nozione di C (o C++) visto che talvolta cercheremo di ricostruire il codice (in C) del programma che staremo reverse ingegnerizzando. Oppure, creare dapprima un programma in C per poi cercare di crackarlo.

Cos'è Un Programma

Forse sarò troppo banale o scontato, ma voglio partire proprio dalla base dando una definizione di programma.

Un programma (o applicazione software) è una porzione di codice che può essere eseguita da un processore.

E fin qui, tutto ok. Ora, però, occorre capire anche cos'è un eseguibile (file con estensione .exe per intenderci). Esso infatti non è un programma nel senso stretto del termine.

Un eseguibile (file EXE per Windows) è un file che contiene del codice eseguibile e varie altre informazioni utili per il sistema operativo.

La struttura di un file eseguibile è la seguente (su Windows, per Linux e MacOS è simile):


Come puoi notare, un file eseguibile non è solo codice. Esso contiene, oltre al codice del programma, anche tante informazioni utili al sistema operativo per avviarlo e farlo girare.

È bene capire questa non tanto sottile differenza tra programma ed eseguibile fin dall'inizio in modo da seguire tutto il corso senza problemi.

Com'è Fatto Il Codice Di Un Programma

L’hard disk (così come la memoria RAM) può memorizzare solo informazioni di tipo binario, quindi sequenze composte da 0 e 1 (ad esempio: 0100101001110).

Un eseguibile viene memorizzato sull'hard disk e quando viene eseguito viene caricato in memoria RAM.

Quindi un eseguibile è composto da 0 e 1 (formato binario).

Un programma (che è contenuto nell'eseguibile) è memorizzato in linguaggio macchina, quindi anch'esso è in formato binario.

Per facilitare la vita ai reverse engineer (ma anche ai programmatori) esistono i debugger, ovvero dei software che, dato in input un eseguibile, ne prendono il codice del programma e lo convertono da linguaggio macchina a linguaggio Assembly. God bless debuggers!

In questo corso, faremo largo uso dei debugger già dalla prima lezione. Per questo motivo, è di fondamentale importanza avere quantomeno delle basi di Assembly.

Se sei un po' arrugginito riguardo questo linguaggio, continua a leggere perché faremo una veloce ripetizione.

Quick Recap: Linguaggio Assembly

Ci basta fare una ricapitolazione solo di alcune delle principali istruzioni Assembly.

MOV

La prima è l'istruzione MOV (move, cioè muovi). Essa è strutturata nel seguente modo:
MOV operando1, operando2
Ma facciamo un esempio per capire al meglio questa istruzione:
MOV esp, 0x34
Cosa fa questa istruzione? Mette il valore dell'operando2, in questo caso 0x34 che è un numero scritto in base esadecimale, infatti c'è il "0x" come prefisso, nell'operando2 ovvero nel registro esp.

Aspetta... Cosa? Registro? Cos'è?

Partiamo dal fatto che già dovresti sapere cosa sia un registro perché, ripeto per la 100esima volta, per seguire questo corso già devi avere delle basi di Assembly.

Tuttavia, oggi mi sento magnanimo, quindi te lo rispiego: un registro possiamo vederlo come una piccola (molto piccola, infatti è di 64 o 128 bit) memoria contenuta nel processore stesso, quindi velocemente e facilmente accessibile da quest'ultimo.

I principali registri di un processore che abbiamo a disposizione per memorizzare determinati valori: EAX, EBX, ECX, EDX, EBP, ESP.

In questo caso abbiamo utilizzato il registro ESP. Grazie alla MOV, abbiamo messo il valore 0x34 nel registro ESP.

Ora vediamo una "variante" dell'istruzione MOV:
MOV [eax], 0x34
Questa istruzione mette il valore 0x34 non nel registro EAX ma nell'indirizzo di memoria contenuto in EAX.

Mi spiego meglio con un esempio: supponiamo nel registro EAX ci sia il valore 0x80c40f (si ho messo delle lettere all'interno di un numero, se sei confuso corri a leggere cosa sia il sistema numerico esadecimale). Quando nella MOV utilizziamo le parentesi quadre, stiamo dicendo di mettere il valore 0x34 non in EAX ma all'indirizzo di memoria RAM 0x80c40f.

Ultimo esempio e poi smetto. Giuro.
MOV [eax], ebx
Prende il valore di ebx e lo mette nell'inidirizzo di memoria contenuto in eax.

Stop. Sono di parola.

Tuttavia... abbiamo anche altre istruzioni da rivedere e il tempo è poco. Quindi meglio sbrigarsi.

JMP

Questa è facile, possiamo liquidarla velocemente.

JMP sta per jump, che in inglese vuol dire salta. Ma tu già lo sapevi vero? Da buon hacker quale sei, conosci alla perfezione l'inglese.

Esempio:
JMP 0x8008f0
Non fa altro che saltare all'istruzione che si trova all'indirizzo di memoria 0x8008f0 ovvero riprende l'esecuzione del programma da 0x8008f0.

Pensate a un if:
if (a == 2) {
    printf("Ciao");
} else {
    printf("Buona sera");
}
Se la variabile a è 2 allora salta al printf("Ciao"), altrimenti salta al printf("Buona sera").

CMP

CMP sta per compare, cioè compara, metti al confronto.

Essa è strutturata in questo modo:
CMP operando1, operando2
Essa non fa altro che comparare l'operando1 con l'operando2. In base al risultato della comparazione (cioè se i due operandi sono uguali, diversi, uno è maggiore dell'altro ecc...) vengono settati dei flag, cioè dei bit all'interno di registri "speciali".

Esempio:
CMP eax, ebx
Fa il confronto del valore che si trova nel registro EAX col valore che si trova nel registro EBX.

JE e JNE

JE (Jump if Equal, salta se uguali) e JNE (Jump if Not Equal, salta se non uguali) sono alcune delle "varianti" dell'istruzione JMP.

Esse compiono il salto solo se sono settati determinati flag nei registri "speciali" di cui parlavamo poc'anzi.

Esempio pratico:
CMP eax, 0x2c
JE 0x800400
In questo caso il salto all'istruzione nell'indirizzo 0x800400 viene compiuto solo se il valore in EAX e il valore 0x2c sono uguali. Ecco perché si chiama Jump if Equal.

Inversamente:
CMP eax, 0x2c
JNE 0x800400
Fa il salto all'istruzione in 0x800400 solo se il valore in EAX e il valore 0x2c sono diversi, cioè non uguali. Da cui: Jump if Not Equal.

Note finali riguardo l'Assembly

Da notare che ho nella maggior parte delle sintassi Assembly se scrivi un'istruzione o il nome di un registro in maiuscolo o minuscolo non cambia niente. Infatti avrei potuto scrivere tranquillamente:
mov eax, 0x500
Oppure:
MOV EAX, 0x500
È soltanto una questione di convenzione.

Bene, la ricapitolazione dell'Assembly è ufficialmente finita! Yeah!

Cos'è Il Reverse Engineering

Definizione da Wikipedia:
Il processo di reverse engineering (anche chiamato in italiano ingegneria inversa) consiste nell'analisi dettagliata del funzionamento, progettazione e sviluppo di un oggetto (dispositivo, componente elettrico, meccanismo, software, ecc.) al fine di produrre un nuovo dispositivo o programma che abbia un funzionamento analogo. [Fonte: Wikipedia]
Definizione mia a prova di stupido:
Fare reverse engineering di un programma significa capire il suo funzionamenti nel dettaglio (anche se non tutto il suo funzionamento, ma solo la parte che ci interessa) in modo da capire dove "mettere mano" per modificarlo e fargli fare ciò che vogliamo. [Fonte: My brain]
In questa prima lezione vedremo come fare reverse engineering di un programma grazie ad un tool molto utile: OllyDBG. Esso altro non è che un debugger con alcune funzionalità aggiunte: tipo possiamo modificare il codice Assembly e riassemblarlo in modo da ottenere un nuovo programma.

In questo corso utilizziamo la versione 2.0 di OllyDBG che, pur non essendo stabile ma beta, sembra avere meno bug e più funzionalità utili. Puoi scaricarlo da qui: OllyDBG 2.0 (è una pagina molto lunga e con molta scritta, quindi per trovare il link del download di consiglio di fare Ctrl+F e cercare download). 

Cosa Vuol Dire Crackare Un Programma

Con cracking si intende la modifica di un software per rimuovere la protezione dalla copia, oppure per ottenere accesso ad un'area altrimenti riservata. La distribuzione di software così reso privo di protezione (detto warez) è generalmente un'azione illegale a causa della violazione di un copyright. Il crack viene spesso ottenuto tramite il reverse engineering, tecnica che permette di capire la logica del software analizzando il suo funzionamento e le risposte a determinati input. [Fonte: Wikipedia]
Non do la definizione mia, perché quella di Wikipedia è già a prova di stupido.

Voglio soffermarmi sul fatto che la distribuzione di software crackato è illegale. Quindi non farlo anche se alla fine di questo corso saprai come crackare.

Queste guide, infatti, sono puramente a scopo informativo.

Detto questo, quindi, si capisce dalla definizione di Wikipedia che crackare un software vuol dire studiarne il funzionamento e grazie a varie tecniche bypassare la protezione/licenza.

Come Crackare Un Programma

Finalmente la parte pratica! Questo video, oltre a fare una ricapitolazione di tutto quello che hai letto fin'ora ti spiegherà passo passo come crackare un programma (molto banale direi) scritto da me in linguaggio C. Puoi scaricarlo da questo link (Dropbox) per poterti esercitare a crackarlo. Consideralo come un sacco da boxe cui puoi dare calci, pugni, farci quello che vuoi per esercitarti per il vero match.


In pratica abbiamo due modi per crackare un programma.

1. Cambiare il flusso di esecuzione
Ad esempio, dopo aver fatto reverse engineering, siamo arrivati alla conclusione che il programma ha un funzionamento del genere:
int codice;
int codice_corretto = [non sappiamo, per il momento, il suo valore];
printf("Inserisci il codice:");
scanf("%d", &codice);
if (codice == codice_corretto) {
    printf("Codice corretto!");
} else {
    printf("Codice errato!");
}
In pratica vogliamo cambiare il flusso di esecuzione, cioè vogliamo dirottare il programma a dire "Codice corretto!" anche se inseriamo un codice errato.

Per fare ciò ci basta fare un if inverso ovvero sostituire
if (codice == codice_corretto)
con
if (codice != codice_corretto)
oppure
if (!(codice == codice_corretto))
che è la stessa cosa.

Per fare ciò dobbiamo disassemblare il programma (con OllyDBG o qualsiasi altro debugger) e cercare l'istruzione dove vengono messi a confronto codice con codice_corretto. Dovrebbe essere un'istruzione del genere:
CMP codice, codice_corretto
JE 0x800400
Ovviamente, invece di codice e codice_corretto ci saranno gli indirizzi di memoria in cui essi sono contenuti (ad esempio [esp+0x1c], ovvero prendi il valore contenuto in esp, sommagli 0x1c e così ottieni il valore in cui è memorizzato codice).

Per cambiare (o dirottare) il flusso di esecuzione di questo programma ci basta sostituire JE con JNE in modo che il programma salterà all'istruzione all'indirizzo 0x800400 (che si suppone essere quella che fa printf("Codice corretto!")) solo se il codice è sbagliato.

Quindi, possiamo inserire tutti i codice tranne che quello corretto per poter bypassare questo controllo di codice ed ottenere la scritta "Codice corretto!".

Ecco un chart flow che riassume com'è stato cambiato (dirottato) il flusso di esecuzione di questo programma:



2. Scoprire il valore corretto
Anche se questo approccio è generalmente più complicato del primo, per programmi banali può essere quello più immediato.

Nel programma di esempio del video, il codice Assembly del programma da crackare è di questo tipo (mostro solo la parte che ci interessa):
CMP [esp+0x1c], 0x190
JE 0x800400
Ho usato un'indirizzo di istruzione a cui saltare a caso (0x800400), ma tanto non è questo ciò su cui dobbiamo focalizzarci.

Ciò che dobbiamo notare, da buon reverse engineer, è che il valore all'indirizzo ESP+0x1c (che si suppone sia il valore dov'è memorizzato il codice preso in input) viene messo a confronto col valore 0x190. Quindi, in teoria, tale valore dovrebbe essere il valore corretto. Infatti è così. 0x190 in valore decimale corrisponde a 400. Se scriviamo 400 quando il programma ci chiede il codice, notiamo che ci dice "Codice corretto!", quindi il codice è questo. Trovato!

Se il codice fosse stato leggermente diverso, ad esempio:
CMP [esp+0x1c], [esp]
JE 0x800400
In tal caso avremmo dovuto controllare il valore contenuto nell'indirizzo di memoria ESP e all'indirizzo ESP+0x1c. Uno dei due valori sarebbe dovuto essere il codice corretto, mentre l'altro il codice inserito da noi. Conoscendo il valore inserito in input (dato che lo abbiamo scritto noi) sapremmo quale sarebbe stato il codice corretto.

Ci sono anche tecniche più avanzate per scoprire il codice/seriale corretto ma per il momento ci fermiamo qui.

Questi sono i due modi principali per crackare un software.

Considerazioni finali

Questa prima lezione è stata ricca di teoria. Nelle seguenti lezioni entreremo nel vivo del corso e vedremo come crackare software un po' più complessi e capiremo le varie tecniche che le software house utilizzano per evitare la pirateria.

Occorre sempre tenere in considerazione, come già detto, che tutto ciò che leggi in questo corso (così come tutto ciò che trovi nei video) è a scopo puramente informativo e didattico.

Ti invito ad iscriverti al canale Youtube: DiventareHacker. Puoi trovare la playlist sul reverse engineering: Reverse Engineering.

0 commenti:

Posta un commento