3. Strumenti di Reverse Engineering

Quando si sente parlare di ingegneria inversa, o reverse engineering, tutti gli sviluppatori comunemente pensano a qualche individuo con tanto tempo libero e che sappia veramente bene programmare e capire il codice assembly. Ho sentito più volte durante la mia carriera alcuni programmatori dire di essere appassionati al mondo del reverse engineering, ma di non aver mai avuto tempo libero per appassionarsi e andare in profondità. Questi sviluppatori venivano scoraggiati dall’utilizzo del basso livello e da alcune conoscenze di assembly che, ahimé, non sono mai stati approfonditi.

Mai di più falso! Anche durante lo sviluppo, i programmatori utilizzano regolarmente strumenti di reverse engineering per verificare la correttezza del loro programma. Lo strumento più utilizzato è chiaramente il debugger, integrato spesso e volentieri negli ambiente di sviluppo.

I debugger sono il miglior strumento di reverse engineering, poiché consentono l’esecuzione passo passo del programma e l’ispezione delle variabili del programma. L’uso di un debugger per eseguire il debug del codice sorgente o del codice macchina originale dipende dalla disponibilità delle informazioni di debug per il debugger. In genere le informazioni di debug vengono generate dal compilatore tramite un’opzione (come -g per gcc) e vengono passate dall’assembler e dal linker al debugger. Vari standard vengono utilizzati tra compilatore e debugger per questo scopo, e li prenderemo in considerazione quando parleremo di formati di file oggetto, poiché si tratta di informazioni molto utili anche per altri strumenti di reverse engineering.

Mentre un debugger può eseguire analisi in fase di esecuzione o “dinamiche” del programma di destinazione, gli altri strumenti possono eseguire solo analisi “statiche”. Affronteremo principalmente la differenza tra analisi statica e dinamica nel corso di questo capitolo.

Il disassemblaggio è una delle funzionalità dei debugger, ma può anche essere eseguito da uno strumento autonomo, un disassemblatore. Un disassemblatore converte una sequenza di byte fissa che sarà potenzialmente eseguita dal processore, in una serie di righe di testo che rappresentano l’operazione eseguita da quei byte. Questo è un processo molto grezzo ed è soggetto a errori.

Si basa sul presupposto che la sequenza di byte da disassemblare rappresenti un’istruzione del processore (ovvero il codice). Se il compilatore ha inserito dei dati nella sezione di testo, il disassemblatore tenterà di convertirli in istruzioni; peggio ancora, il flusso di istruzioni può diventare desincronizzato, poiché molte istruzioni hanno argomenti che si estendono su più di un byte. Questo metodo è spesso volentieri utilizzato per offuscare le istruzioni e rendere più difficile la decompilazione da parte del programmatore. Riprenderemo in considerazione questo problema, quando parleremo dell’identificazione delle aree di codice e di dati.

I disassemblatori più intelligenti sono in grado di fornire maggiori informazioni su un’istruzione specifica. In particolare, tramite alcune euristiche, il programma riesce a capire se l’istruzione fa riferimento a una variabile globale o chiama una funzione globale. Ricorda che il processore non sa nulla di funzioni e variabili: il processore non ha la stessa nostra visione. Conosce solo gli indirizzi di memoria. Pertanto, un disassemblatore intelligente dovrebbe essere in grado di combinare informazioni da diverse parti del file binario e presentarle in modo significativo. “objdump” è un programma disassemblatore che ha la maggior parte di queste caratteristiche.

Tecnicamente parlando, “objdump” non è un disassemblaggio; è un dumper di oggetti (da cui il nome). Un dumper di oggetti è molto utile in quanto può mostrare l’intero contenuto dei file binari compilati, non solo l’area del codice. In effetti, i file oggetto possono contenere molte più informazioni di quelle che vede effettivamente il processore. Di solito hanno informazioni su dove verrà caricata in memoria un’area del file, nonché un elenco di librerie collegate dinamicamente necessarie per eseguire il programma, nonché il codice necessario per collegare e caricare tali librerie durante l’esecuzione. I file oggetto possono avere anche una serie di nomi per gli indirizzi (simboli globali) e in alcuni casi anche informazioni su variabili e tipi locali.

Anche con la ricchezza di informazioni fornite da un debugger, le informazioni sono ancora a livello di macchina. L’object dumper mostrerà su cosa opera il processore e su che strutture dati agisce il sistema operativo. Solo in casi molto rari puoi vedere il sorgente del tuo programma mescolato con l’output di disassembly (in tal caso non hai bisogno di nessun altro strumento per analizzare il programma, dato che hai già i sorgenti). Ma si trattano di casi rari in cui lo sviluppatore ha forzatamente specificato al compilatore di includere i sorgenti all’interno del programma.

Se si desidera avere una visualizzazione di livello superiore del programma, è necessario convertire la visualizzazione della macchina in una visualizzazione del programmatore. Cioè bisogna alzare il livello di astrazione, introducendo concetti che sono stati utilizzati durante la fase di sviluppo, e che sono stati persi durante la fase di compilazione e collegamento.

Questo è ciò che fanno i decompilatori: cercano di ricostruire l’informazione che è parzialmente persa durante la creazione del programma binario. Questo corso descrive come questo reversing sia fatto e come è possibile. Poiché si tratta di un argomento altamente tecnico, ci si aspetta che il lettore abbia familiarità con almeno un linguaggio assembly e un’architettura del processore.

La maggior parte degli algoritmi è indipendente dal processore, quindi non importa quale processore conosci. Il processore più ampiamente disponibile è x86, quindi la maggior parte degli esempi utilizzerà questa CPU, ma ciò non dovrebbe impedirti di seguire il contenuto, ad eccezione di alcuni casi limite.

Biografia

Sono uno specialista in materia di sicurezza informatica, scrittore, contributore per il progetto Monero, una criptovaluta che è focalizzata nel proteggere le informazioni sulle transazioni. Il libro che ho pubblicato Mastering Monero è diventato una delle migliori risorse per padroneggiare Monero. Più informazioni su di me

Seguimi su Twitter o scrivimi una e-mail. Le donazioni sono molto apprezzate, mi permettono di continuare a lavorare e a scrivere.

Mastering Monero Book