Principi di reverse engineering

Il reverse engineering è il processo di analisi di un programma compilato, ovvero di un binario eseguibile, per comprendere la sua logica di funzionamento e le sue caratteristiche interne. L'obiettivo può essere variabile: dalla ricerca di vulnerabilità di sicurezza, alla comprensione di algoritmi proprietari, fino alla modifica del software per aggiungere nuove funzionalità o per garantire la compatibilità con altri sistemi.

I due principali approcci al reverse engineering sono

  • approccio statico, spesso con l'ausilio di un disassemblatore o un decompilatore, si tratta di interpretare il codice macchina del programma e provare a capire staticamente il funzionamento,
  • approccio dinamico, ovvero eseguire il programma in un ambiente controllato, con l'ausilio di un debugger, un analizzatore di rete oppure altri strumenti per analizzarne il comportamento.

Vediamo adesso alcuni strumenti di base che ci permettono di fare delle analisi preliminari sui file eseguibili.

Trovare le informazioni di base di un binario

Con lo strumento file possiamo identificare delle proprietà di base di un eseguibile, ad esempio sul binario ls restituisce questo output.

$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=7aa8f52223e371fb77314d9a363129574096093b, for GNU/Linux 4.4.0, stripped

Ci restituisce delle informazioni come il fatto che è un eseguibile x86 a 64 bit, il linking è dinamico e che è stripped, ovvero non contiene informazioni di debug.

Possiamo visualizzare quali librerie sono caricate dinamicamente tramite il comando ldd.

$ ldd /usr/bin/ls
	linux-vdso.so.1 (0x00007022fc02e000)
	libcap.so.2 => /usr/lib/libcap.so.2 (0x00007022fbfc8000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007022fbddc000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007022fc030000)

Trovare le stringhe nei binari

Spesso le costanti stringhe sono inserite nei binari a certi indirizzi, e possono essere trovate tramite lo strumento strings. Ad esempio queste sono alcune stringhe interessanti che si possono trovare nel binario ls.

$ strings /usr/bin/ls
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
__libc_start_main
__cxa_finalize
__cxa_atexit
strcmp
stdout
__overflow
fwrite_unlocked

[...]

cannot access %s
OWNER@
GROUP@
EVERYONE@
cannot read symbolic link %s
cannot open directory %s
reading directory %s
closing directory %s

[...]

Nel primo blocco si possono notare delle stringhe probabilmente usate per la risoluzione dinamica delle funzioni di libreria, mentre nel secondo blocco ci sono delle stringhe di formattazione, probabilmente usate in funzioni printf o simili.

Tracciare le system call

Una system call, o chiamata di sistema, è un meccanismo che permette ai programmi in esecuzione di interagire con il sistema operativo. Quando un'applicazione deve eseguire operazioni che richiedono l'accesso alle risorse hardware o ai servizi di sistema, come la gestione dei file, la comunicazione di rete o la gestione della memoria, utilizza le system call per richiedere tali servizi al kernel del sistema operativo. Sapere quali system call vengono chiamate da un processo può quindi darci delle informazioni su cosa sta facendo un programma.

Tracciare quali system call vengono eseguite da un processo è possibile in ambiente linux tramite lo strumento strace. Ad esempio possiamo provare a lanciarlo sul programma ls.

strace ls

L'output sarà composto da tutte le system call invocate dal programma ls.

$ strace ls    
execve("/usr/bin/ls", ["ls"], 0x7ffc85430f10 /* 52 vars */) = 0

[...]

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "main  main.c\n", 13main  main.c
)          = 13
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

In particolare possiamo notare la prima chiamata execve per eseguire il processo ls, la chiamata a fstat per ottenere informazioni sui file relativi alla cartella corrente, e write per stampare in output i nomi dei file. strace ci mostra anche i parametri delle chiamate e i valori di ritorno. Questo può essere uno strumento molto utile per ottenere una informazione ad alto livello del comportamento del programma.

Esiste un programma molto simile che traccia però le chiamate a funzione a librerie caricate dinamicamente, chiamato ltrace.