La decompilazione è una tecnica di ingegneria inversa che ricostruisce il codice sorgente a partire dal suo file eseguibile, costituito dal linguaggio macchina.
Esistono diversi software che attraverso tecniche euristiche sono in grado di ricostruire una bozza di codice sorgente a partire dal file eseguibile. Uno dei più famosi, Ghidra, è stato sviluppato dall'agenzia americana NSA ed è gratuitamente scaricabile al link.L'ingegneria inversa di un software è un processo assai complesso e arduo perché un file binario è un'espressione "compatta" della logica che il programmatore originale ha ideato, e di solito vengono applicate ottimizzazioni che rendono il file binario poco comprensibile, non solo nel flusso di istruzioni, ma nella logica stessa. Inoltre, a meno di un programma compilato a fini di "debug" è raro che un software commerciale mantenga i simboli associati a ciascuna funzione. Ciò significa che le funzioni non hanno un nome e si è costretti a lavorare alla cieca. E' comprensibilmente molto più facile capire cosa fa una funzione se il suo nome è multiply(uint, uint) piuttosto che un placeholder FUN_827462a9(undefined, undefined) (seguendo lo stile dei nomi di Ghidra)
Prendiamo ad esempio un piccolo esempio di codice in C e il suo equivalente in x86 assembly:
int multiply_and_sub_by_two(int a, int b){
return a*b - 2;
}
int main(){
int a=4,b=6;
int res = multiplyi_and_sub_by_two(a,b);
}
viene compilato in:
multiply_and_sub_by_two(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
sub eax, 2
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 4
mov DWORD PTR [rbp-8], 6
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call multiply_and_sub_by_two(int, int)
mov DWORD PTR [rbp-12], eax
mov eax, 0
leave
ret
Il risultato assemblato in GCC formato ELF è il seguente:
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0003 003e 0001 0000 1020 0000 0000 0000
0000020 0040 0000 0000 0000 3128 0000 0000 0000
0000030 0000 0000 0040 0038 000e 0040 001a 0019
0000040 0006 0000 0004 0000 0040 0000 0000 0000
0000050 0040 0000 0000 0000 0040 0000 0000 0000
0000060 0310 0000 0000 0000 0310 0000 0000 0000
0000070 0008 0000 0000 0000 0003 0000 0004 0000
0000080 03b4 0000 0000 0000 03b4 0000 0000 0000
0000090 03b4 0000 0000 0000 001c 0000 0000 0000
00000a0 001c 0000 0000 0000 0001 0000 0000 0000
00000b0 0001 0000 0004 0000 0000 0000 0000 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0608 0000 0000 0000 0608 0000 0000 0000
00000e0 1000 0000 0000 0000 0001 0000 0005 0000
...
...
...
0003730 0030 0000 0000 0000 0000 0000 0000 0000
0003740 3010 0000 0000 0000 001b 0000 0000 0000
0003750 0000 0000 0000 0000 0001 0000 0000 0000
0003760 0001 0000 0000 0000 0001 0000 0003 0000
0003770 0000 0000 0000 0000 0000 0000 0000 0000
0003780 302b 0000 0000 0000 00fc 0000 0000 0000
0003790 0000 0000 0000 0000 0001 0000 0000 0000
00037a0 0000 0000 0000 0000
00037a8
Usando Ghidra, partendo dal file binario, ricaviamo:
undefined8 FUN_0010112f(void)
{
FUN_00101119(4,6);
return 0;
}
int FUN_00101119(int param_1,int param_2)
{
return param_1 * param_2 + -2;
}
Nel caso del nostro semplicissimo programma, il software è stato in grado di ricostruire fedelmente il codice, a meno di perdite dei nomi di variabili e funzioni. Queste possono essere "ricostruite" dal programmatore fornendo dei nomi sensati alle funzioni e terminando così il lavoro di ingegneria inversa.
Nel nostro caso "capiamo" che all'offset 0x0010112f comincia la funzione main, che chiama la nostra funzione all'offset 0x00101119.
In programmi più complessi non è detto che il risultato sia così fedele. Infinite combinazioni di codice possono produrre altrettanti risultati e il software di decompilazione può non fare assunzioni precise su allineamenti, lunghezze delle variabili e altre informazioni utili, pertanto potrebbero esserci casi di codice errato. Se il programmatore, dopo un'attenta analisi riesce ad recuperare i dati mancanti, il software può correggere il tiro e fornire una struttura molto più vicina al codice originale. Un lavoro di decompilazione può durare da pochi giorni con un singolo appassionato fino a svariati anni con un nutrito gruppo di programmatori esperti. Un celebre esempio di successo di decompilazione è quella svolta sul codice di Lego Island, reperibile al link.Un mio personale progetto di reverse engineering è il videogioco Wii Sports, al fine di creare una mod di allenamento per il gioco del golf, in cui verrà mostrata nella minimappa una traiettoria dipendente dal vento.