Un file eseguibile contiene istruzioni macchina, codificate in un formato comprensibile per un processore o un interprete, e può essere eseguito direttamente dal sistema operativo.
Ci sono diversi formati di file eseguibili: il loro formato dipende dal sistema operativo sul quale sono utilizzati. Alcuni esempi comuni includono PE (Portable Executable), utilizzato principalmente in ambienti Windows per gli eseguibili (ad esempio gli EXE) e le librerie DLL (Dynamic Link Libraries). Citiamo però, ad esempio, anche il formato ELF (Executable and Linkable Format), comune nei sistemi operativi Unix e Unix-like, come Linux; Mach-O (Mach Object), utilizzato su macOS; il formato Java Archive (JAR), che ospita applicazioni Java eseguibili tramite la Java Virtual Machine (JVM).
Cos’è un file eseguibile
In un altro articolo abbiamo visto le differenze tra compilazione ed interpretazione: durante la compilazione, il codice sorgente è tradotto in linguaggio macchina o in un linguaggio intermedio da un programma chiamato compilatore. In questo caso il sistema operativo può gestire direttamente l’eseguibile o il codice intermedio senza la necessità di ricompilare il codice sorgente ogni volta. Essendo già tradotto e disponibile in linguaggio macchina, il codice compilato tende ad essere più veloce nell’esecuzione rispetto a un codice interpretato.
Durante l’interpretazione, il codice sorgente è eseguito da un interprete, che legge ed esegue istruzione per istruzione senza la necessità di generare un eseguibile separato. Il codice interpretato è di solito più lento rispetto al codice compilato perché ogni istruzione deve essere interpretata ad ogni esecuzione. Tuttavia, lo stesso codice sorgente funziona allo stesso modo su qualunque dispositivo e piattaforma, a patto di utilizzare un interprete adatto.
Java, ad esempio, utilizza un approccio ibrido. Il codice sorgente è compilato in un bytecode che può essere eseguito da una macchina virtuale (come la JVM). La JVM interpreta il bytecode, ma può anche eseguire ottimizzazioni durante l’esecuzione, con un approccio intermedio tra compilazione e interpretazione. In generale, il bytecode descrive le operazioni che costituiscono un programma riducendo la dipendenza dalla piattaforma hardware.
Come riconoscere il compilatore utilizzato per creare un file eseguibile
I file eseguibili possono essere generati dai compilatori dei diversi linguaggi di programmazione come C, C++, Java, Python e molti altri. La forma specifica e il contenuto del file eseguibile dipendono dal compilatore e dal linguaggio di programmazione utilizzati.
Ciascun file eseguibile può contenere il codice sorgente compilato in linguaggio macchina, librerie di funzioni, dati, risorse e altre informazioni necessarie per l’esecuzione del programma. Quando si avvia un file eseguibile, il sistema operativo o la macchina virtuale elaborano le istruzioni contenute nel file e le eseguono gestendo il flusso del programma corrispondente.
Dependency Walker
All’interno di un file eseguibile è presenta un'”impronta” che permette di risalire al compilatore utilizzato per generare l’oggetto software. Un’applicazione come Dependency Walker, è in grado di esaminare il contenuto dell’eseguibile ed estrarre le funzioni da esso utilizzate. Una chiara struttura ad albero restituisce la lista delle dipendenze individuate a valle dell’ispezione sul file eseguibile.
TrID
Un utile strumento per ottenere l’identikit di un file eseguibile è l’utilità TrID: scaricandola quindi salvando nella stessa cartella anche il file delle definizioni più aggiornato (TrIDDefs.TRD package), è possibile impartire il comando trid
seguito dal percorso completo del file da controllare per avere un’indicazione sul compilatore utilizzato.
Come si vede, nell’esempio in figura, TrID indica che il file eseguibile risulta precedentemente compilato con Microsoft Visual C++.
Exeinfo PE
Un’ulteriore valida alternativa è Exeinfo PE: per utilizzarla, basta estrarre l’intero contenuto del suo archivio compresso in una cartella di propria scelta quindi fare doppio clic sul suo eseguibile. Per ottenere indicazioni su un file eseguibili è quindi sufficiente trascinare l’elemento da controllare (dalla finestra di Esplora file) nella finestra di Exeinfo PE.
Il compilatore utilizzato per generare il file eseguibile è chiaramente riportato nella penultimo campo che figura nella finestra principale dell’applicazione.
Un altro eccellente strumento da utilizzare per le stesse finalità è Detect It Easy, scaricabile nella versione più recente anche dal repository GitHub per tutti i vari sistemi operativi.
Differenza tra disassembler e decompiler
Sia il disassembler che il decompiler sono strumenti utilizzati nell’analisi del software e nelle attività di reverse engineering. Un disassembler converte il codice binario (linguaggio macchina) di un programma in linguaggio Assembly, una rappresentazione certamente più comprensibile per gli essere umani anche se ancora piuttosto vicina al linguaggio macchina.
I disassembler permettono di esaminare la struttura dei file eseguibili, compresi i componenti da cui dipende il loro funzionamento (ad esempio le librerie).
Un decompiler svolge invece un compito più avanzato rispetto al disassembler: converte il codice macchina in codice sorgente ad un livello più alto, cercando di ricreare il codice sorgente originale o qualcosa che gli si possa in qualche modo avvicinare.
Si tratta di strumenti spesso utilizzati per comprendere più da vicino il funzionamento del codice sorgente di un certo programma, con la possibilità quindi di esaminarne il comportamento ed applicare eventuali modifiche. Soprattutto nelle situazioni in cui il codice sorgente originale non risulta disponibile. In generale, quindi, il decompiler fornisce un’astrazione più elevata rispetto al disassembler.
Quali decompilatori utilizzare
Il progetto Dogbolt (Decompiler Explorer) offre la possibilità di inviare all’applicazione Web un file eseguibile di dimensioni non superiori a 2 MB. In questo modo si può esaminare il comportamento dei vari decompilatori supportati dal progetto e cercare informazioni rilevanti sul comportamento di ciascun eseguibile.
DotPeek per gli eseguibili .NET
Non ricompreso in Dogbolt, uno dei migliori decompilatori per i programmi .NET è senza dubbio dotPeek. Sviluppato da JetBrains, la stessa azienda che ha creato popolari ambienti di sviluppo integrati (IDE) come IntelliJ IDEA, ReSharper, PyCharm e altri, dotPeek è progettato per aiutare gli sviluppatori .NET a esaminare il codice sorgente delle applicazioni a partire da assembly compilati, senza la disponibilità di alcune informazione utile.
dotPeek può generare una rappresentazione in C# del codice sorgente originale, rendendo più comprensibile il funzionamento di qualunque applicazione .NET (.NET Framework, .NET Core e .NET 5/6/7). L’interfaccia del programma JetBrains consente agli utenti di visualizzare classi, metodi, proprietà e altri elementi del codice. Inoltre, supporta funzionalità di navigazione avanzate, ad esempio quelle per la ricerca di riferimenti, la navigazione tra le definizioni e l’accesso rapido alle dichiarazioni.
dotPeek è disponibile come strumento gratuito e autonomo, che può essere scaricato e utilizzato indipendentemente da altri strumenti JetBrains. Per utilizzare dotPeek, è sufficiente scaricarlo dal sito Web ufficiale, installarlo quindi aprire il file EXE o DLL che si desidera esaminare.
Snowman per i file eseguibili in linguaggio C/C++
Snowman è un disassembler e un decompiler open source (è anche “portabile” non necessitando di installazione) progettato per convertire il codice assembly in C. Il suo obiettivo principale è quello di fornire una rappresentazione più leggibile del codice sorgente a partire dall’assembly.
I formati binari supportati da Snowman sono PE, ELF e Mach-O. In termini di architetture Snowman supporta x86, x64 e ARM. L’interfaccia consente di visualizzare il codice assembly, il sorgente C/C++, le sezioni del programma, i simboli.
Relyze, per interagire direttamente con il codice binario
Disassebler e decompilatore disponibile in due versioni, una professionale e una gratuita, Relyze consente di esaminare e interagire con il codice binario, fornendo strumenti per comprendere e manipolare l’eseguibile.
Un’apposita sezione dell’applicazione consente di sfogliare il codice sorgente utilizzando diverse modalità di visualizzazione, tra cui quella basata sullo pseudocodice. Nella parte inferiore della finestra, ci sono i riferimenti alle stringhe, alle funzioni, alle dipendenze importate e così via.
Relyze è utile per decompilare file binari per Windows e Linux, su architetture x86, x64 e ARM a 32 e 64 bit.
Hex-Rays IDA (Interactive DisAssembler)
IDA Pro è uno dei più potenti e diffusi strumenti di reverse engineering. Hex-Rays IDA è un plugin per IDA Pro che aggiunge una funzionalità di decompilazione avanzata, convertendo il codice assembly in codice C.
Il programma è in grado di eseguire l’analisi statica di un file binario, identificare il flusso di controllo e fornire una rappresentazione visuale del codice.
La versione gratuita di Hex-Rays risulta fortemente limitata, mancando il supporto per molti processori (ad esempio i SoC ARM). Inoltre, il decompilatore è basato sul cloud. È comunque in ogni caso possibile disassemblare e decompilare eseguibili x86 e x64 (Windows, Linux e macOS).
Ghidra, framework di reverse engineering open source
Nonostante gli scandali che in passato hanno riguardato da vicino la National Security Agency (NSA) statunitense, la stessa Agenzia ha rilasciato nel 2019 uno strumento software open source. Si tratta appunto di Ghidra, programma per disassemblare e decompilare eseguibili.
Ghidra, posto sotto la lente da tanti esperti, non evidenzia caratteristiche sospette ed anzi si è rilevato un tool potente per analizzare eseguibili, eseguirne la decompilazione e visualizzarne il sorgente.
È un po’ più lento rispetto ad altri software della sua stessa categoria, ma è intuitivo e integra funzionalità evolute come una serie di strumenti grafici per il blocco dell’esecuzione, l’analisi del codice e la gestione del flusso delle chiamate.
Il download di Ghidra è effettuabile dal repository GitHub ufficiale. Il programma supporta eseguibili x86, x64 e ARM (Windows, Linux e macOS).
Credit immagine in apertura: iStock.com/ATHVisions