Un fuzzer qui ne trouve rien, est-ce une bonne nouvelle ? Presque jamais. Lors de BarbHack 2026, un chercheur en sécurité venu du monde de l’énergie est parti d’un constat gênant : le fuzzing de binaires tel qu’on le pratique couramment détecte très bien les crashs spectaculaires, et rate presque tout ce qui fait une vulnérabilité exploitable. Sa réponse tient en un outil, un ensemble de scripts Python baptisé Hippie, et en une idée simple : si le programme ne veut pas crasher tout seul, on va l’y forcer.

Quatrième volet de notre couverture de BarbHack, et sans doute le plus proche du métier de l’audit offensif. Voici le mécanisme, son coût, et ce qu’il faut en retenir même si vous n’écrivez jamais de harness.

Le décor : fuzzer un binaire dont on n’a pas le code

Le fuzzing consiste à envoyer à un programme des entrées aléatoires ou mutées, puis à observer son comportement. Deux briques structurent la démarche :

  • AFL (American Fuzzy Lop) trace le graphe d’exécution pour chaque entrée. Si une entrée emprunte un chemin d’exécution nouveau, elle est conservée dans le corpus et sert de base à de nouvelles mutations. C’est ce qui distingue le fuzzing guidé du tirage au sort.
  • Unicorn Engine, émulateur de CPU fondé sur QEMU, prend le relais quand le code source n’est pas disponible et qu’une compilation instrumentée est donc hors de portée. C’est le cas classique de l’audit : un firmware, un binaire fourni, rien d’autre.

La combinaison AFL++ et Unicorn permet alors de simuler soit un processus complet, soit une fonction précise, et de la soumettre à mutation. Sur le papier, le problème est résolu. Dans les faits, non.

Le problème : trouver des crashs n’est pas trouver des bugs

Le fuzzing conventionnel repère vite les crashs francs : débordement de pile, copie mémoire volumineuse, déréférence grossière. Sauf qu’un simple déni de service n’est pas l’objectif d’un audit sérieux. L’objectif, c’est la corruption mémoire exploitable. Et sur ce terrain, des familles entières de failles restent parfaitement invisibles pour AFL et Unicorn :

  • les fuites mémoire et les dépassements légers, en excès comme en défaut ;
  • les hors-limites discrets : une écriture ou une lecture de quelques octets au-delà d’un tampon, qui n’écrase aucune zone critique, ne provoque aucun crash et passe donc inaperçue ;
  • la double libération et l’utilisation après libération (use-after-free) : si le contenu de la mémoire n’a pas changé après le free, le programme poursuit son exécution comme si de rien n’était.
Le fuzzer ne voit que ce que le programme veut bien lui montrer. Un débordement de trois octets dans une zone anodine ne fait pas tomber le binaire aujourd’hui, mais c’est exactement le genre de primitive sur laquelle un attaquant construit une chaîne d’exploitation demain.

La correction : réécrire malloc et free sous émulation

Impossible de recompiler avec les protections habituelles, puisqu’il n’y a pas de sources. L’intervenant a donc déplacé le problème : plutôt que d’instrumenter le programme, il instrumente la mémoire. Hippie intercepte les appels système et surveille la mémoire vive pendant l’émulation, en réimplémentant les primitives d’allocation.

Allocation : chaque objet dans un couloir piégé

À chaque malloc, l’outil alloue une page de 4096 octets et place systématiquement les données à la fin de la page. Deux pages adjacentes, privées de tout droit d’accès en lecture comme en écriture, encadrent l’allocation. Conséquence : le dépassement d’un seul octet touche la page interdite et déclenche un crash immédiat. Le bug silencieux devient un bug bruyant, donc un bug que le fuzzer sait enregistrer.

Libération : la page devient un champ de mines

À chaque free, la page libérée est dépouillée de tous ses droits. Toute lecture ou écriture ultérieure provoque un crash instantané, ce qui capture automatiquement les use-after-free. L’outil conserve en parallèle un historique des pointeurs libérés, de quoi détecter une double libération dès qu’elle se produit.

Métadonnées : vérifier ce que les fonctions de bloc manipulent

Hippie suit la taille des zones allouées et pose des hooks sur les fonctions de manipulation de bloc, comme memcpy ou memset, afin de valider les pointeurs source et destination avant l’opération. On ne se contente plus d’attendre le crash : on interroge la cohérence de chaque mouvement de mémoire.

Le prix à payer : dix exécutions par seconde

L’émulation coûte cher. Là où un binaire compilé nativement encaisse 300 à 500 exécutions par seconde, l’ensemble Unicorn plus instrumentation tombe à une dizaine. Un facteur trente à cinquante, assumé : on échange du débit contre de la visibilité. Fuzzer très vite en ne voyant que les crashs francs, ou fuzzer lentement en voyant les corruptions qui comptent, c’est un arbitrage, pas une régression.

Deux fonctions d’outillage rendent ce compromis tenable :

  • un mode TRACE qui affiche chaque instruction exécutée l’une après l’autre, indispensable pour mettre au point le harness ;
  • un mini-débogueur intégré qui, en cas de crash, extrait l’état des registres et de la mémoire au plus bas niveau pour remonter à la cause exacte. Sans lui, un crash reste un crash : avec lui, il devient un diagnostic.

Monter un harness : trois fichiers, deux jours

La mise en place d’un test repose sur trois éléments, et c’est là que se joue l’essentiel du travail humain :

  • Settings : les paramètres du binaire, la taille du code, la mémoire BSS.
  • Harness : le script d’entrée, qui charge les fichiers de corpus et lance AFL++.
  • Setup : l’initialisation de l’émulateur Unicorn, à savoir l’architecture, le CPU, la cartographie mémoire, les registres et les hooks sur malloc, free, memcpy et memset. Une table simplifiée permet de mapper les adresses de fonctions spécifiques, ou de simuler la bibliothèque C quand elle manque.

Le cas de démonstration n’a rien d’académique : un démon PLC issu d’un firmware Modbus, en x86-64. Autrement dit, de l’automate industriel, le genre d’équipement qu’on ne redémarre pas pour tester et dont on n’a jamais les sources. Le temps de rédaction du harness : environ deux jours. C’est l’ordre de grandeur à retenir quand on planifie ce type de mission.

Le passage sur l’IA, qui mérite d’être cité

Interrogé sur sa méthode de développement, l’intervenant a été direct : il a d’abord tenté de faire générer le code du projet par des agents d’intelligence artificielle. Résultat désorganisé, truffé de duplications, inexploitable. Il a donc réécrit l’intégralité du code à la main, puis réutilisé l’IA uniquement comme outil de relecture, pour le style, l’indentation et le nommage des variables.

Cette honnêteté tranche avec le discours ambiant, et elle est instructive. Sur un projet qui touche à la sémantique fine de la mémoire, l’IA est un excellent correcteur et un mauvais architecte. Un constat qui rejoint, par un autre chemin, ce qu’on retenait de la conférence sur les serveurs MCP appliqués au pentest : l’outillage compte plus que la puissance brute.

La suite du projet

Le code est hébergé sur GitHub et sera rendu public une fois quelques chantiers refermés :

  • nettoyage avant ouverture du dépôt, notamment une option de tainting restée incomplète ;
  • portage ARM32 (v7), en plus du x86-64 déjà supporté, ce qui ouvrirait la porte à une grande partie de l’embarqué ;
  • enrichissement du débogueur, avec modification de mémoire et injection de consignes à la volée ;
  • extension de la mini-libc intégrée, pour couvrir automatiquement les fonctions courantes et réduire le temps de création des harness.

Une question de la salle mérite d’être rapportée : pourquoi Unicorn plutôt que Qiling, qui embarque déjà des fonctions de la glibc et des en-têtes utiles ? Réponse assumée : une meilleure maîtrise d’Unicorn au démarrage du projet, et la conviction qu’il est plus simple de partir d’une base légère pour y ajouter une couche spécifique que de plier un framework très intégré à son besoin. Une version fondée sur Qiling n’est pas exclue.

Ce qu’on en retient chez Humanix

  • « Aucun crash » n’est pas un résultat d’audit. Un rapport qui conclut à l’absence de vulnérabilité après quelques heures de fuzzing ne dit rien de la robustesse du binaire, seulement de la sensibilité de l’instrumentation employée. La bonne question à poser à un prestataire n’est pas « combien d’exécutions », mais « qu’est-ce que votre montage était capable de voir ».
  • La lenteur peut être une décision d’ingénierie. Diviser le débit par trente pour rendre visible le dépassement d’un octet est un arbitrage rationnel dès lors que l’objectif est l’exploitabilité et non le volume de crashs.
  • L’industriel et l’OT sont concernés au premier chef. Un démon Modbus sans sources, sur un équipement qu’on ne peut ni redémarrer ni recompiler, c’est le quotidien des environnements industriels. Ce type d’outillage y est plus qu’utile : il est souvent le seul moyen d’objectiver un risque.
  • Démontrer plutôt que décréter. Dire à un exploitant que son automate « présente un risque mémoire » ne produit aucune décision. Lui montrer le crash reproductible, l’état des registres et le chemin d’exécution qui y mène, si. C’est exactement notre façon de travailler : la preuve avant le verdict.

À retenir

  • AFL guide le fuzzing par la découverte de nouveaux chemins d’exécution ; Unicorn permet de fuzzer un binaire dont on n’a pas les sources.
  • Cette combinaison trouve les crashs francs mais rate les hors-limites de quelques octets, les use-after-free silencieux, les doubles libérations et les fuites mémoire.
  • Hippie réimplémente malloc et free sous émulation : allocation en fin de page, encadrée de deux pages sans droits, page libérée rendue inaccessible, hooks sur memcpy et memset. Le moindre octet de débordement devient un crash.
  • Coût : environ 10 exécutions par seconde contre 300 à 500 en natif. Deux jours pour écrire le harness d’un démon PLC Modbus en x86-64.
  • Le code généré par IA s’est révélé inexploitable et a été entièrement réécrit à la main, l’IA n’étant conservée que pour la relecture de style.

Note. Compte-rendu, à visée pédagogique, d’une conférence technique de BarbHack 2026 consacrée au fuzzing de binaires et à la détection de corruptions mémoire. Les outils cités (AFL++, Unicorn Engine, Qiling) sont des projets publics ; l’outil présenté n’était pas encore publié au moment de la conférence. Quatrième volet de notre couverture de l’événement. Un binaire, un firmware ou un automate à mettre à l’épreuve ? Contactez-nous.