
Fuzzing di dispositivi IoT usando il router TL-WR902AC come esempio
Questa è la versione HTML della mia tesina, che può essere scaricata come PDF qui.
Il fuzzing è diventato "uno dei modi più efficaci" per trovare bug nel software. Con questa o simili affermazioni iniziano molti articoli recenti sul fuzzing [google-scholar]. L'obiettivo principale della nostra precedente tesina sul tema "Internet of Vulnerable Things" era trovare un bug legato alla memoria e poi scrivere un exploit per questa vulnerabilità. Siamo riusciti a trovare una vulnerabilità facendo reverse engineering del firmware, ma non è stato trovato alcun bug legato alla memoria. Trovare un buffer overflow facendo reverse engineering di un binario a mano richiede non solo molto tempo, ma anche molta esperienza. Il fuzzing, allo stesso tempo, mira a essere il "modo più efficace" per trovare tali vulnerabilità legate alla memoria. Google, ad esempio, ha introdotto OSS-Fuzz, che fuzza continuamente software open source e ha già trovato oltre 10.000 vulnerabilità in 1.000 progetti [oss-fuzz].
L'obiettivo di questa tesina è ancora una volta trovare una vulnerabilità legata alla memoria, ma questa volta usando il fuzzing. La vulnerabilità obiettivo dovrebbe essere sfruttabile tramite la rete senza conoscere le credenziali di amministratore. Questo documento descrive il percorso per raggiungere tale obiettivo. A tal fine, la tesina è separata in due parti. La prima parte si concentra su come trovare un bersaglio valido, quali strumenti possono essere usati e come dovrebbe essere composto un buon bersaglio di fuzzing. La seconda parte descrive invece come sviluppare e fare debug di un harness in grado di fuzzare una funzione specifica di un binario. Successivamente, l'harness sviluppato viene usato da AFL++ per fuzzare la funzione obiettivo. Di seguito viene fornito un breve background e qual è lo stato dell'arte attuale per quanto riguarda il fuzzing di dispositivi IoT.
Tutti i file creati nell'ambito di questa tesina sono pubblicati integralmente anche su GitHub e possono essere accessibili tramite il seguente URL: otsmr/blackbox-fuzzing.
Il fuzzing di dispositivi IoT non è facile come fuzzare un progetto open source. Spesso il codice sorgente è proprietario, il che rende impossibile il fuzzing gray-box, che strumenta il codice sorgente per ottenere le migliori prestazioni di fuzzing [afl-persistent]. Inoltre, l'architettura della CPU spesso non è supportata nativamente dai fuzzer, il che richiede un emulatore come QEMU [qemu], che rallenta anche la velocità di fuzzing [afl-persistent]. Un altro problema sono le periferiche hardware, che complicano lo sviluppo di un approccio generale. L'articolo "Embedded Fuzzing: A Review of Challenges, Tools, and Solutions" [embedded-fuzzing] fornisce una panoramica delle diverse strategie di fuzzing, come l'embedded fuzzing basato su hardware. La maggior parte di queste strategie richiede il codice sorgente del programma target, ad esempio quando si porta il codice sorgente dei fuzzer, come AFL, su dispositivi IoT basati su ARM per eseguire il fuzzer sull'hardware IoT. Eseguire il fuzzer sull'hardware del dispositivo presenta anche problemi di prestazioni, perché spesso hanno CPU di fascia bassa, più lente delle normali CPU desktop. Un altro approccio presentato in questo articolo è l'embedded fuzzing basato su emulazione, in cui viene eseguito in un emulatore un singolo programma target per effettuare un fuzzing guidato dalla copertura, oppure l'intero sistema.
Gli approcci sopra menzionati prendono tutti di mira direttamente un binario utilizzando un emulatore o strumentando
il codice sorgente. Questi approcci richiedono una configurazione di fuzzing che spesso deve essere creata appositamente
per un singolo dispositivo IoT e sono difficili da generalizzare. Per questo, i ricercatori hanno creato un programma
IoTFuzzer che si propone come framework di fuzzing automatizzato con l'obiettivo di "trovare vulnerabilità di corruzione
della memoria senza accesso alle relative immagini del firmware
[iotfuzzer]." IoTFuzzer si basa
sull'osservazione che la maggior parte dei dispositivi IoT ha un'app mobile per controllarli, e queste app
contengono informazioni sul protocollo usato per comunicare con il dispositivo. Il programma identifica e riutilizza
quindi la logica specifica del programma per mutare i casi di test e testare efficacemente i target
IoT [iotfuzzer].
Un harness descrive una sequenza di chiamate API che elaborano gli input forniti dal fuzzer. A differenza di una normale applicazione, che spesso non necessita di un harness, una libreria che implementa funzioni riutilizzabili deve essere chiamata con i parametri corretti e anche nella sequenza giusta, così da poter gestire lo stato tra più chiamate a funzioni condivise. Fuzzare casualmente la libreria senza costruire la macchina a stati difficilmente avrà successo e, al contrario, creerà molti crash falsi positivi quando le dipendenze della libreria non vengono rispettate. Questo può accadere, ad esempio, quando un controllo sulla dimensione del buffer viene saltato dal fuzzer, provocando un buffer overflow spurio.
In questo documento verranno fuzzate applicazioni normali, ma a causa delle dipendenze hardware legate all'uso di socket e del multi-threading, dobbiamo creare un harness anche per loro. L'harness viene caricato nel contesto del binario e può chiamare funzioni interne del programma target, come mostrato nel Codice 10.
Il termine "corpus" descrive campioni di input validi o casi di test e funge da riferimento di base per generare nuovi dati di input durante il processo di fuzzing. Nel Codice 10 potrebbe essere, ad esempio, una richiesta HTTP. I fuzzer sfruttano quindi questo corpus per creare casi di test mutati o diversificati, aiutando nell'individuazione di vulnerabilità software attraverso l'esplorazione di vari scenari di input.
La parte più dispendiosa in termini di tempo del black box fuzzing è trovare una funzione
potenzialmente vulnerabile nel firmware. Il primo passo è trovare
binari interessanti che, ad esempio, siano accessibili tramite la rete,
utilizzino funzioni non sicure o non abbiano funzionalità di sicurezza come lo stack canary
abilitato, che costituisce una protezione contro i buffer overflow. Il nostro precedente articolo
([iovt]) ha già descritto come estrarre il firmware
dal router preso di mira e come trovare un binario potenzialmente pericoloso.
Per questo è stato utilizzato lo strumento EMBA [emba]. EMBA classifica tutti i
binari trovati nel firmware in base al numero di funzioni non sicure come
strcpy, all'accesso di rete e alle protezioni di sicurezza come lo stack canary o
il NX-Bit, che diventano interessanti quando si sfrutta un buffer overflow,
come si può vedere nel Codice 1.