Rétro-concevoir sans méthode, c’est se noyer. Un binaire de quelques mégaoctets recèle plus de code qu’on ne peut en lire, et l’essentiel du travail consiste moins à comprendre tout qu’à décider où regarder. Je consigne ici la démarche que j’applique, de la définition de l’objectif à la simplification progressive du code.
Partir de l’objectif
Ce qu’on cherche détermine par où l’on commence. Trois grandes intentions structurent l’analyse :
- Analyser un malware — établir sa malveillance, identifier ses méthodes d’obfuscation et ses vecteurs d’infection. Restent hors de ce cadre ses mécanismes de propagation et de communication, qui relèvent d’une autre étude.
- Tester une vulnérabilité — traquer les motifs dangereux (
eval(), débordements de tampon, entrées utilisateur non validées), les fuites de données, les conditions de course (TOCTOU). - Rétro-concevoir un protocole — suivre les appels d’API, les sockets, pour reconstituer une communication.
NOTE
La partie métier d’un logiciel est généralement la moins soignée, et les protocoles inhabituels ou bizarres donnent bien souvent lieu à la découverte effective de failles. C’est là qu’il faut porter le regard en priorité.
Une compréhension globale du système précède toujours l’examen fin ; et une approche dichotomique — circonscrire clairement le champ des possibles avant de trancher — se révèle presque toujours la plus productive.
Choisir ses outils
L’analyse commence par l’identification du format et du système d’exploitation cible, dont découle le choix de l’outillage. On combine typiquement :
- l’analyse statique — Ghidra, IDA, radare2 / Cutter — pour lire sans exécuter ;
- l’analyse dynamique et le débogage, pour observer le comportement réel ;
- des décompilateurs spécialisés selon la cible (JAD, JD pour Java) ;
- l’analyse réseau — Wireshark — pour le trafic ;
- des désobfuscateurs (
js-beautifyet consorts) pour le code délibérément rendu illisible.
Réduire la surface
L’enjeu est de rentabiliser le temps d’analyse en priorisant les zones à plus fort potentiel. Cela passe presque toujours par une phase de prise de notes et de réorganisation du projet de rétro-ingénierie, destinée à simplifier la compréhension ultérieure. On concentre l’attention sur :
- les fonctions sensibles et le code caché ;
- les chaînes parlantes —
password,https,ftp,api,admin; - les appels système.
Décomposer, puis simplifier
Deux principes complémentaires gouvernent la lecture proprement dite.
La décomposition réduit la complexité en analysant le code par petites unités : on identifie les fonctions, boucles et conditions via le désassembleur, puis on s’en construit une vue structurée — fonctions, blocs, relations. On obtient ainsi une carte claire des grandes parties du programme.
La simplification clarifie et organise ce qu’on a recueilli : traduire l’assembleur en pseudo-code intelligible, et prendre des notes assez détaillées pour cerner l’ensemble. C’est là que le programme cesse d’être une masse d’instructions pour redevenir une intention.
Un cas d’école : le diffing de patch
L’illustration la plus nette de cette démarche est la recherche d’une vulnérabilité 0-day dans une bibliothèque fraîchement corrigée, avant sa divulgation publique. On décompile l’archive .jar avant le correctif, puis après, on en fait un diff visuel multi-fichiers (avec meld, par exemple), et l’on concentre l’analyse sur les seules modifications apportées par le patch. Le correctif, en désignant ce qu’il répare, trahit la faille qu’il colmate — toute la surface se réduit alors à quelques lignes.