
Fuzzing des systèmes embarqués à l'aide de points d'arrêt matériels
Ceci est le code compagnon pour l'article : « Fuzzing Embedded Systems using Debugger Interfaces ». Une prépublication de l'article peut être trouvée ici https://publications.cispa.saarland/3950/. Le code permet aux utilisateurs de reproduire et d'étendre les résultats rapportés dans l'article. Veuillez citer l'article ci-dessus lorsque vous rapporterez, reproduirez ou étendrez les résultats.
.
├── benchmark # Scripts pour construire la suite de tests de fuzzer de Google et exécuter les expériences
├── dependencies # Contient un Makefile pour installer les dépendances de GDBFuzz
├── evaluation # Données brutes d'expérience, présentées dans l'article
├── example_firmware # Exemples d'applications embarquées, utilisées pour l'évaluation
├── example_programs # Contient un exemple de programme compilé et des configurations pour tester GDBFuzz
├── src # Contient l'implémentation de GDBFuzz
├── Dockerfile # Pour créer une image Docker avec toutes les dépendances GDBFuzz installées
├── LICENSE # Licence
├── Makefile # Makefile pour créer l'image docker ou installer GDBFuzz localement
└── README.md # Ce fichier README
L'idée de GDBFuzz est d'exploiter les points d'arrêt matériels des microcontrôleurs comme retour pour le fuzzing guidé par couverture. Ainsi, GDB est utilisé comme interface générique pour permettre une large applicabilité. Pour l'analyse binaire du firmware, Ghidra est utilisé. Le code contient une configuration de benchmark pour évaluer la méthode. De plus, des fichiers de firmware d'exemple sont inclus.
GDBFuzz permet le fuzzing guidé par couverture pour les systèmes embarqués, mais - à des fins d'évaluation - peut aussi fuzzer des applications utilisateur arbitraires. Pour le fuzzing sur microcontrôleurs, nous recommandons une installation locale de GDBFuzz afin de pouvoir envoyer les données de fuzz au dispositif testé sans problème.
GDBFuzz a été testé sur Ubuntu 20.04 LTS et Raspberry Pi OS 32 bits. Les prérequis sont Java et Python 3. Tout d'abord, créez un nouvel environnement virtuel et installez toutes les dépendances.
virtualenv .venv
source .venv/bin/activate
make
chmod a+x ./src/GDBFuzz/main.py
GDBFuzz lit les paramètres à partir d'un fichier de configuration avec les clés suivantes.
[SUT]
# Chemin vers le fichier binaire du SUT.
# Cela peut être, par exemple, un fichier .elf ou .bin.
binary_file_path = <chemin>
# Adresse du nœud racine du CFG.
# Les points d'arrêt sont placés aux nœuds de ce CFG.
# ex. 'LLVMFuzzerTestOneInput' ou 'main'
entrypoint = <point d'entrée>
# Nombre d'entrées qui doivent être exécutées sans qu'un point d'arrêt ne soit déclenché avant
# que les points d'arrêt soient tournés.
until_rotate_breakpoints = <nombre>
# Nombre maximum de points d'arrêt pouvant être placés à tout moment.
max_breakpoints = <nombre>
# Liste noire des fonctions à ignorer.
# ignore_functions est une liste de noms de fonctions séparés par des espaces, ex. 'malloc free'.
ignore_functions = <liste séparée par des espaces>
# Un de {Hardware, QEMU, SUTRunsOnHost}
# Hardware : Un composant externe démarre un serveur gdb et GDBFuzz peut s'y connecter.
# QEMU : GDBFuzz démarre QEMU. QEMU émule binary_file_path et démarre gdbserver.
# SUTRunsOnHost : GDBFuzz démarre le programme cible dans GDB.
target_mode = <mode>
# Mettez ceci à False si vous voulez démarrer ghidra, analyser le SUT,
# et démarrer le pont ghidra bridge manuellement.
start_ghidra = True
# Liste séparée par des espaces des adresses où les points d'arrêt logiciels (pour le code de
# gestion des erreurs) sont placés. L'exécution de ceux-ci est considérée comme un crash.
# Exemple : software_breakpoint_addresses = 0x123 0x432
software_breakpoint_addresses =
# Indique si tous les points d'arrêt logiciels déclenchés sont considérés comme des crashs
consider_sw_breakpoint_as_error = False
[SUTConnection]
# La classe 'SUT_connection_class' dans le fichier 'SUT_connection_path' implémente
# comment les entrées sont envoyées au SUT.
# Les entrées peuvent, par exemple, être envoyées via Wi-Fi, Série, Bluetooth, ...
# Cette classe doit hériter de ./connections/SUTConnection.py.
# Voir ./connections/SUTConnection.py pour plus d'informations.
SUT_connection_file = FIFOConnection.py
[GDB]
path_to_gdb = gdb-multiarch
# Écrit sous forme adresse:port
gdb_server_address = localhost:4242
[Fuzzer]
# En octets
maximum_input_length = 100000
# En secondes
single_run_timeout = 20
# En secondes
total_runtime = 3600
# Optionnel
# Chemin vers un répertoire où chaque fichier contient une seed. Si vous ne souhaitez pas
# utiliser de seeds, laissez la valeur vide.
seeds_directory =
[BreakpointStrategy]
# Les stratégies pour choisir les blocs de base se trouvent dans
# 'src/GDBFuzz/breakpoint_strategies/'
# Pour l'article, nous utilisons les stratégies suivantes
# 'RandomBasicBlockStrategy.py' - Choisir aléatoirement des blocs de base non atteints
# 'RandomBasicBlockNoDomStrategy.py' - Comme la précédente, mais n'utilise pas les relations de dominance pour déduire les nœuds transitivement atteints.
# 'RandomBasicBlockNoCorpusStrategy.py' - Comme la première, mais empêche la croissance du corpus d'entrées et se comporte donc comme du fuzzing boîte noire avec mesure de couverture.
# 'BlackboxStrategy.py', - Ne place aucun point d'arrêt
breakpoint_strategy_file = RandomBasicBlockStrategy.py
[Dependencies]
path_to_qemu = dependencies/qemu/build/x86_64-linux-user/qemu-x86_64
path_to_ghidra = dependencies/ghidra
[LogsAndVisualizations]
# Un de {DEBUG, INFO, WARNING, ERROR, CRITICAL}
loglevel = INFO
# Chemin vers un répertoire où les fichiers de sortie (ex. graphiques, fichiers journaux) sont stockés.
output_directory = ./output
# Si mis à True, un client MQTT envoie des éléments d'interface utilisateur (ex. graphiques)
enable_UI = False
Un exemple de fichier de configuration se trouve dans ./example_programs/ accompagné d'un exemple de programme compilé à l'aide de notre harnais de fuzzing dans benchmark/benchSUTs/GDBFuzz_wrapper/common/.
Lancez le fuzzing pendant une heure avec la commande suivante.
chmod a+x ./example_programs/json-2017-02-12
./src/GDBFuzz/main.py --config ./example_programs/fuzz_json.cfg
Nous voyons d'abord la sortie de Ghidra analysant l'exécutable binaire, puis des messages lorsque les points d'arrêt sont relocalisés ou déclenchés.
Selon le output_directory spécifié dans le fichier de configuration, il devrait maintenant y avoir un dossier trial-0 avec la structure suivante
.
├── corpus # Un dossier qui contient le corpus d'entrées.
├── crashes # Un dossier qui contient les entrées ayant causé un crash - le cas échéant.
├── cfg # Le graphe de flot de contrôle sous forme de liste d'adjacence.
├── fuzzer_stats # Statistiques de la campagne de fuzzing.
├── plot_data # Tableau montrant à quel moment relatif dans la campagne de fuzzing quel bloc de base a été atteint.
├── reverse_cfg # Le graphe de flot de contrôle inversé.
En définissant start_ghidra = False dans le fichier de configuration, GDBFuzz se connecte à une instance Ghidra fonctionnant en mode GUI. Pour cela, le plugin ghidra_bridge doit être démarré manuellement depuis le gestionnaire de scripts. Pendant le fuzzing, les blocs de programme atteints sont surlignés en vert.