Contexte
Au sein d'une équipe de R&D défense, nous devions analyser un volume croissant de protocoles réseau propriétaires afin d'en comprendre la structure, les champs et les données transportées. Un travail essentiel, mais très largement manuel.
Le problème
Réaliser manuellement la rétro-conception de chaque protocole était lent, répétitif et difficilement compatible avec le volume de nouvelles applications à analyser.
- L'analyse manuelle des protocoles propriétaires était longue et répétitive.
- Le volume de nouvelles applications rendait une analyse exhaustive impossible.
- La complexité des protocoles, renforcée par le chiffrement et la compression, conduisait l'analyse manuelle à manquer certains champs ou relations importantes.
Objectifs
- Réduire fortement le temps nécessaire à la rétro-conception d'un protocole.
- Augmenter le nombre d'applications que l'équipe pouvait réellement analyser.
- Identifier de manière fiable les champs et relations structurelles souvent manqués par l'analyse manuelle.
Mon approche
J'ai mené le projet comme une démarche de R&D progressive : valider l'hypothèse sur des cibles réelles avant d'étendre l'outil et d'en généraliser l'usage.
1. Cadrage R&D
Hypothèse de départ : observer une application pendant son exécution permettrait de comprendre comment chaque octet envoyé sur le réseau a été produit.
2. Preuve de concept
Développement d'une preuve de concept avec Valgrind et Intel PIN pour instrumenter les processus cibles et suivre les flux de données.
3. Test et validation
Tests de la preuve de concept sur des protocoles connus, puis comparaison des structures reconstruites avec des résultats de référence.
La solution technique
Observer une application pendant son exécution fournit de nombreuses informations sur les paquets réseau qu'elle émet. J'ai construit un moteur de taint analysis sur Valgrind qui :
- Marque les fonctions et appels système pertinents : lectures de fichiers, entrées/sorties réseau, entrées utilisateur, routines de compression et de chiffrement.
- Associe une étiquette de provenance aux données manipulées par ces fonctions, puis les suit tout au long de l'exécution du processus.
- Lorsqu'un octet est écrit sur le réseau, reconstruit le chemin exact suivi depuis son origine.
Techniquement, Valgrind n'exécute pas directement le programme cible. Il traduit le code machine en VEX, une représentation intermédiaire de type RISC, l'instrumente superbloc par superbloc, puis recompile le code instrumenté. L'outil s'insère dans cette phase d'instrumentation. Il associe une étiquette de provenance à chaque registre invité, temporaire VEX (IRTemp) et octet de mémoire. Cette étiquette encode l'origine de la donnée.
La propagation suit le flux de données dans l'IR. Les opérations Get/Put déplacent les étiquettes entre registres invités et temporaires ; Load/Store les déplacent entre mémoire et temporaires. Les opérations arithmétiques ou logiques propagent ensuite l'union des étiquettes de leurs opérandes vers le résultat. Un octet ainsi marqué conserve son étiquette de provenance à travers les copies, les calculs et les frontières de buffers, dans les registres comme en mémoire.
Les étiquettes sont introduites au niveau des sources de données externes : read, recv, open, time, entrées d'environnement ou de terminal, ainsi que certaines routines choisies par l'utilisateur (exemple : zlib, OpenSSL, etc.). Elles sont ensuite relues au niveau des sorties réseau, comme send, sendto ou write sur une socket. La preuve de concept reposait sur deux backends, Valgrind/VEX et Intel PIN, afin de comparer les résultats et d'élargir la couverture d'instrumentation.
send() : le paquet émis est l'union de toutes les origines suivies.Exemple : pour une application qui lit un fichier, le compresse puis l'envoie sur le réseau, l'outil identifie les octets du paquet sortant issus du fichier et les transformations qu'ils ont traversées.
00 00 04 2A65 9C 3F 8000 00 12 0C9F 2C A8 E1 … 1DAvec cette lecture, la structure du paquet devient directement visible. Le champ à 0x0008 est une longueur, égale à la taille de la payload produite par read() → deflate() → EVP_EncryptUpdate() ; le champ à 0x0004 vient directement du syscall time() ; et la payload à 0x000C est le contenu de /tmp/report.bin après compression zlib et chiffrement AES. Au lieu de déduire manuellement les frontières des champs à partir de dumps hexadécimaux, l'analyste obtient directement la structure et le rôle de chaque champ. Cette représentation exploitable accélère fortement la rétro-conception et peut générer automatiquement un dissecteur Wireshark.
Résultats
- Une preuve de concept fonctionnelle, validée par l'équipe sur des cibles réelles.
- Le temps d'analyse d'un protocole est passé d'environ cinq heures à une trentaine de minutes.
- Une couverture d'analyse plus large, avec identification automatique de champs régulièrement manqués par l'inspection manuelle.
- Les structures reconstruites peuvent être exportées directement en dissecteurs Wireshark, générés automatiquement au lieu d'être écrits à la main.