← Retour aux projets

Projet principal · Cybersécurité · IA appliquée

Comprendre un binaire.
Justifier chaque réponse.

J’ai construit un agent qui inspecte un programme compilé avec Ghidra, choisit ses outils et relie ses conclusions à des preuves vérifiables. Sur les 20 programmes pédagogiques du benchmark V2, l’agent retrouve la réponse attendue dans 19 cas, avec chacun des deux modèles testés.

Python · Ghidra · JavaWindows PE x86-64Version V2.5 figée

Analyse locale, modèles distants. Le binaire fourni n’est jamais exécuté par l’investigation.

Schéma de fonctionnement : fonctions reliées, inspection ciblée et preuve référencée
Reverse Copilot · schéma de fonctionnement, pas une capture Ghidra.

Pourquoi ce projet ?

Quand on analyse un programme sans son code source, il faut retrouver les fonctions utiles, suivre leurs appels et examiner les données qu’elles manipulent. Comprendre une validation demande souvent de passer de l’une à l’autre.

J’ai choisi ce projet pour travailler à la fois sur la cybersécurité et l’IA. Je voulais vérifier si un agent pouvait choisir les bonnes inspections, retrouver la logique d’un programme et appuyer son explication sur ce qu’il avait réellement observé.

J’ai comparé trois façons de donner accès aux informations du binaire. Pour chaque modèle, j’ai mesuré ce que ces approches permettent de résoudre, leur consommation et leurs limites. L’autonomie de l’agent et la fiabilité de son exécution font aussi partie de l’évaluation.

Ghidra, simplement.

Ghidra est un outil de reverse engineering : il aide à examiner un programme compilé lorsque son code source n’est pas disponible. Il repère les fonctions, chaînes de caractères et références, expose les instructions machine et reconstruit du pseudo-C.

Désassembler

Lire les instructions machine sous une forme compréhensible et retrouver les sauts et appels.

Décompiler

Reconstruire un pseudo-code proche du C pour lire plus facilement la logique.

Le pseudo-C n’est pas le code source original. Types, noms et structures peuvent être approximatifs. Le périmètre validé ici est l’analyse statique de binaires Windows PE x86-64.

Comment je l’utilise dans le projet

Ghidra 12.1.4 fonctionne en mode headless, sans interaction graphique nécessaire à la boucle. Des scripts Java exposent les informations à Python. L’interface graphique reste utile pour examiner manuellement fonctions, références et décompilations.

Ce que j’ai construit.

J’ai écrit le programme Python qui fait travailler ensemble Ghidra, les outils d’inspection et les modèles, puis évalue les réponses. Le modèle propose une action sous forme structurée. Python vérifie ses arguments et les budgets disponibles avant de l’exécuter.

  • Extraction structurée et sessions identifiées par empreinte du binaire.
  • Outils pour fonctions, chaînes, références, appels et décompilations. Onze outils en V2.5 avec mémoire bornée, désassemblage et blocs de base.
  • Budgets de temps, tours, fonctions et lectures. Accès natifs du modèle désactivés.
  • Preuves conservées, citations vérifiées, traces et rejeux hors réseau.
  • Corpus avec vérité terrain et barème déterministe, sans juge LLM.

Python standard library · Ghidra · Java · PowerShell · Codex app-server isolé

Ghidra analyse le binaire sur ma machine. Les observations sélectionnées sont ensuite envoyées aux modèles distants via Codex et le routeur : les modèles ne tournent donc pas localement.

Inspecter, puis prouver.

Un binaire et un objectif ouvrent une session Ghidra locale. Le modèle distant propose une action. Python contrôle l’action et appelle un outil autorisé. L’observation revient au modèle, puis les preuves de la conclusion sont vérifiées.

Ouvrir le schéma en grand

Python limite les actions aux outils prévus. Le modèle ne dispose pas d’un accès libre au shell ou aux fichiers.

  1. Préparer le binaire dans une session Ghidra locale.
  2. Envoyer l’objectif et un contexte borné au modèle.
  3. Contrôler sa décision, puis exécuter l’outil autorisé.
  4. Conserver l’observation et mettre à jour l’investigation.
  5. Accepter une conclusion seulement si ses citations sont vérifiables.
Exemple réel : une conclusion reliée au pseudo-C

Challenge pédagogique « mot de passe », V2. La réponse retrouve banana42 et cite la décompilation de check_password à l’adresse 0x140001490, preuve E003.

iVar1 = strcmp(param_1,"banana42");
  return iVar1 == 0;

Cet extrait vient du résultat historique. Les identifiants privés de l’exécution ne sont pas publiés. Vérifier une citation permet de retrouver son origine. Le barème vérifie séparément si l’explication répond à l’objectif.

Trois approches.
Un même corpus.

La V2 conserve 120 résultats : 20 challenges × 3 approches × 2 modèles. Chaque case garde son résultat, y compris les échecs. Les objectifs et critères de résolution sont communs. Les contraintes interactives propres à l’agent sont évaluées séparément.

Décompilation complète

Le modèle reçoit les faits et le pseudo-C de toutes les fonctions internes avec un corps, runtime inclus.

Résumé statique

Une sélection fixe d’au plus dix fonctions, depuis main ou le point d’entrée, en suivant les appels.

Agent autonome

Le modèle choisit progressivement recherches, références et décompilations dans ses budgets.

Les résultats se lisent séparément pour chaque modèle. Ils permettent de comparer les approches sur ce corpus, sans établir qu’un modèle serait meilleur en général.

Résolution V2 : pour chaque modèle, décompilation complète 19 sur 20, résumé statique 18 sur 20 et agent 19 sur 20
Résolution. L’agent et la décompilation complète atteignent le même score sur ce corpus. Le résumé fixe manque un cas supplémentaire.
Tableau accessible : résolution
Résolution sur le corpus V2
Modèle · approcheConclusions validesRéponses correctes
GPT 6.1 Sol · Décompilation complète20/2019/20
GPT 6.1 Sol · Résumé statique20/2018/20
GPT 6.1 Sol · Agent autonome20/2019/20
Muse Spark 1.3 · Décompilation complète20/2019/20
Muse Spark 1.3 · Résumé statique20/2018/20
Muse Spark 1.3 · Agent autonome20/2019/20
Consommation de tokens comparée sur les mêmes challenges complets, séparément pour GPT et Muse Spark 1.3. Valeurs exactes dans le tableau suivant.
Consommation. Avec GPT, l’agent consomme moins que la décompilation complète. Avec Muse Spark 1.3, il en consomme davantage. Les appels et corrections des cas échoués sont inclus.

Un token est un fragment de texte traité par le modèle, par exemple un mot ou une partie de mot. Les compteurs incluent le texte reçu et généré. Intersection commune : 20/20 cas GPT, 19/20 cas Muse Spark 1.3. Tokens rapportés uniquement. Transport et fournisseur jamais additionnés. Cache inclus dans l’entrée, raisonnement inclus dans la sortie. Aucun coût monétaire déduit.

Tableau accessible : consommation et sources
Tokens rapportés sur l’intersection commune
Modèle · approcheChallenges communsTokens totauxSource
GPT 6.1 Sol · Décompilation complète20/202 893 524codex_events
GPT 6.1 Sol · Résumé statique20/201 989 868codex_events
GPT 6.1 Sol · Agent autonome20/202 049 190codex_events
Muse Spark 1.3 · Décompilation complète19/201 516 208router_provider_ledger
Muse Spark 1.3 · Résumé statique19/201 091 872router_provider_ledger
Muse Spark 1.3 · Agent autonome19/208 251 795router_provider_ledger
Courbes cumulées des durées des trois approches, séparées par modèle. Une courbe plus à gauche atteint une proportion donnée de cas plus rapidement.
Durées. À chaque seuil en secondes, la courbe donne la proportion des 20 cas terminés dans ce délai. Ce n’est pas une évolution chronologique ni une mesure d’inférence pure.

Préparation exclue. Les références incluent validation et corrections. L’investigation agent inclut outils et attente modèle. Tous les cas sont conservés. Les campagnes historiques n’ont pas été exécutées simultanément.

Tableau accessible : durées et 120 valeurs exactes
Durées des 20 cas par approche, échecs inclus
Modèle · approcheMinimum sMédiane sMaximum s
GPT 6.1 Sol · Décompilation complète91.61101.91119.80
GPT 6.1 Sol · Résumé statique116.61127.03145.56
GPT 6.1 Sol · Agent autonome51.8381.87142.67
Muse Spark 1.3 · Décompilation complète31.4146.0989.45
Muse Spark 1.3 · Résumé statique25.3147.22113.64
Muse Spark 1.3 · Agent autonome81.39123.17352.55
Valeurs exactes des 120 cas
Durées utilisées dans les courbes
Challenge · modèle · approcheDurée s
arithmetic · GPT 6.1 Sol · Décompilation complète109.53099999998813
bit_masks · GPT 6.1 Sol · Décompilation complète98.25
byte_shift · GPT 6.1 Sol · Décompilation complète98.51500000001397
call_chain · GPT 6.1 Sol · Décompilation complète106.40599999998813
character_conditions · GPT 6.1 Sol · Décompilation complète99.18799999999464
checksums · GPT 6.1 Sol · Décompilation complète116.32800000000861
decoy_paths · GPT 6.1 Sol · Décompilation complète96.96799999999348
global_data · GPT 6.1 Sol · Décompilation complète96.73399999999674
hello · GPT 6.1 Sol · Décompilation complète106.73400000002584
hex_decode · GPT 6.1 Sol · Décompilation complète101.93700000000536
linear_number · GPT 6.1 Sol · Décompilation complète119.79699999999139
modular_number · GPT 6.1 Sol · Décompilation complète101.875
multifunction · GPT 6.1 Sol · Décompilation complète102.23399999999674
password · GPT 6.1 Sol · Décompilation complète91.60899999999674
permutation · GPT 6.1 Sol · Décompilation complète110.96900000001187
reverse_text · GPT 6.1 Sol · Décompilation complète97.07800000000861
segments · GPT 6.1 Sol · Décompilation complète117.68799999999464
staged_validation · GPT 6.1 Sol · Décompilation complète98.67199999999139
switch_branches · GPT 6.1 Sol · Décompilation complète94.76500000001397
xor · GPT 6.1 Sol · Décompilation complète109.54699999999139
arithmetic · GPT 6.1 Sol · Résumé statique121.76600000000326
bit_masks · GPT 6.1 Sol · Résumé statique122.98399999999674
byte_shift · GPT 6.1 Sol · Résumé statique126.39100000000326
call_chain · GPT 6.1 Sol · Résumé statique139.6720000000205
character_conditions · GPT 6.1 Sol · Résumé statique122.45300000000861
checksums · GPT 6.1 Sol · Résumé statique145.56299999999464
decoy_paths · GPT 6.1 Sol · Résumé statique131.84299999999348
global_data · GPT 6.1 Sol · Résumé statique127.67199999999139
hello · GPT 6.1 Sol · Résumé statique131.125
hex_decode · GPT 6.1 Sol · Résumé statique124.96799999999348
linear_number · GPT 6.1 Sol · Résumé statique129.34399999998277
modular_number · GPT 6.1 Sol · Résumé statique123.64100000000326
multifunction · GPT 6.1 Sol · Résumé statique122.45299999997951
password · GPT 6.1 Sol · Résumé statique116.61000000001513
permutation · GPT 6.1 Sol · Résumé statique144.73399999999674
reverse_text · GPT 6.1 Sol · Résumé statique125.29699999999139
segments · GPT 6.1 Sol · Résumé statique137.65600000001723
staged_validation · GPT 6.1 Sol · Résumé statique129.86000000001513
switch_branches · GPT 6.1 Sol · Résumé statique134.60900000002584
xor · GPT 6.1 Sol · Résumé statique120.35899999999674
arithmetic · GPT 6.1 Sol · Agent autonome74.60900000000038
bit_masks · GPT 6.1 Sol · Agent autonome99.82800000000134
byte_shift · GPT 6.1 Sol · Agent autonome66.95299999999952
call_chain · GPT 6.1 Sol · Agent autonome141.375
character_conditions · GPT 6.1 Sol · Agent autonome71.07799999999952
checksums · GPT 6.1 Sol · Agent autonome81.18800000000192
decoy_paths · GPT 6.1 Sol · Agent autonome70.09300000000076
global_data · GPT 6.1 Sol · Agent autonome82.54700000000594
hello · GPT 6.1 Sol · Agent autonome51.82800000000134
hex_decode · GPT 6.1 Sol · Agent autonome129.5460000000021
linear_number · GPT 6.1 Sol · Agent autonome100.98400000000402
modular_number · GPT 6.1 Sol · Agent autonome113.38999999999942
multifunction · GPT 6.1 Sol · Agent autonome108.10899999999856
password · GPT 6.1 Sol · Agent autonome64.63999999999942
permutation · GPT 6.1 Sol · Agent autonome122.85900000000402
reverse_text · GPT 6.1 Sol · Agent autonome72.3119999999999
segments · GPT 6.1 Sol · Agent autonome112.125
staged_validation · GPT 6.1 Sol · Agent autonome142.67200000000594
switch_branches · GPT 6.1 Sol · Agent autonome74.70300000000134
xor · GPT 6.1 Sol · Agent autonome67.01599999999962
arithmetic · Muse Spark 1.3 · Décompilation complète52.562999999994645
bit_masks · Muse Spark 1.3 · Décompilation complète44.844000000011874
byte_shift · Muse Spark 1.3 · Décompilation complète34.89000000001397
call_chain · Muse Spark 1.3 · Décompilation complète44.610000000015134
character_conditions · Muse Spark 1.3 · Décompilation complète82.07800000000861
checksums · Muse Spark 1.3 · Décompilation complète47.32799999997951
decoy_paths · Muse Spark 1.3 · Décompilation complète49.75
global_data · Muse Spark 1.3 · Décompilation complète43.15600000001723
hello · Muse Spark 1.3 · Décompilation complète32.90700000000652
hex_decode · Muse Spark 1.3 · Décompilation complète37.59299999999348
linear_number · Muse Spark 1.3 · Décompilation complète31.405999999988126
modular_number · Muse Spark 1.3 · Décompilation complète38.21899999998277
multifunction · Muse Spark 1.3 · Décompilation complète37.01600000000326
password · Muse Spark 1.3 · Décompilation complète36.125
permutation · Muse Spark 1.3 · Décompilation complète59.328000000008615
reverse_text · Muse Spark 1.3 · Décompilation complète50.10999999998603
segments · Muse Spark 1.3 · Décompilation complète48.655999999988126
staged_validation · Muse Spark 1.3 · Décompilation complète89.45300000000861
switch_branches · Muse Spark 1.3 · Décompilation complète65.625
xor · Muse Spark 1.3 · Décompilation complète53.421999999991385
arithmetic · Muse Spark 1.3 · Résumé statique26.453000000008615
bit_masks · Muse Spark 1.3 · Résumé statique52.296999999991385
byte_shift · Muse Spark 1.3 · Résumé statique68.70300000000861
call_chain · Muse Spark 1.3 · Résumé statique43.296000000002095
character_conditions · Muse Spark 1.3 · Résumé statique53.578999999997905
checksums · Muse Spark 1.3 · Résumé statique47.780999999988126
decoy_paths · Muse Spark 1.3 · Résumé statique55.812000000005355
global_data · Muse Spark 1.3 · Résumé statique34.01600000000326
hello · Muse Spark 1.3 · Résumé statique25.312999999994645
hex_decode · Muse Spark 1.3 · Résumé statique28.828999999997905
linear_number · Muse Spark 1.3 · Résumé statique45.514999999984866
modular_number · Muse Spark 1.3 · Résumé statique48.765999999974156
multifunction · Muse Spark 1.3 · Résumé statique113.64100000000326
password · Muse Spark 1.3 · Résumé statique33.64100000000326
permutation · Muse Spark 1.3 · Résumé statique47.78100000001723
reverse_text · Muse Spark 1.3 · Résumé statique46.65700000000652
segments · Muse Spark 1.3 · Résumé statique61.64100000000326
staged_validation · Muse Spark 1.3 · Résumé statique75.60899999999674
switch_branches · Muse Spark 1.3 · Résumé statique39.421999999991385
xor · Muse Spark 1.3 · Résumé statique32.5
arithmetic · Muse Spark 1.3 · Agent autonome109.95399999999972
bit_masks · Muse Spark 1.3 · Agent autonome177.39099999999598
byte_shift · Muse Spark 1.3 · Agent autonome100.5630000000001
call_chain · Muse Spark 1.3 · Agent autonome174.81300000000192
character_conditions · Muse Spark 1.3 · Agent autonome81.38999999999942
checksums · Muse Spark 1.3 · Agent autonome90.2190000000046
decoy_paths · Muse Spark 1.3 · Agent autonome135.03199999999924
global_data · Muse Spark 1.3 · Agent autonome100.51599999999598
hello · Muse Spark 1.3 · Agent autonome97.11000000000058
hex_decode · Muse Spark 1.3 · Agent autonome124.53099999999904
linear_number · Muse Spark 1.3 · Agent autonome128.04699999999866
modular_number · Muse Spark 1.3 · Agent autonome121.11000000000058
multifunction · Muse Spark 1.3 · Agent autonome185.9380000000001
password · Muse Spark 1.3 · Agent autonome121.8119999999999
permutation · Muse Spark 1.3 · Agent autonome352.54699999999866
reverse_text · Muse Spark 1.3 · Agent autonome125.98499999999876
segments · Muse Spark 1.3 · Agent autonome147.71899999999914
staged_validation · Muse Spark 1.3 · Agent autonome152.11000000000058
switch_branches · Muse Spark 1.3 · Agent autonome101.09399999999732
xor · Muse Spark 1.3 · Agent autonome113.79700000000048
Méthode, données et reproduction

Les figures sont générées avec Python standard library depuis les résultats V2 validés. L’export vérifie couverture, empreintes des sources, scores et audits enregistrés. Les courbes sont des distributions cumulées empiriques. Les consommations portent sur l’intersection des trois approches.

Export limité aux champs publics utiles : aucun journal brut, chemin local, prompt ni identifiant de requête. Les artefacts privés d’origine ne sont pas inclus dans le site.

Les ajouts de la V2.5.

Après la V2, j’ai ajouté la lecture mémoire bornée, le désassemblage et les blocs de base. Pour maîtriser le budget, les validations V2.5 utilisent uniquement Muse Spark 1.3 avec un effort high.

Des outils supplémentaires

Accéder aux données et instructions que le seul pseudo-C ne montre pas. Le passage à high et les versions différentes empêchent d’attribuer les changements aux seuls outils.

Un corpus plus difficile

Binaires optimisés, symboles retirés et logiques plus indirectes. Les résultats de ces campagnes restent séparés du benchmark V2.

Un barème corrigé

Reconnaître des formulations équivalentes, avec contre-exemples et réévaluation hors réseau, sans assouplir les preuves.

Un transport instrumenté

Relier requêtes, délais et événements du garde pour distinguer un incident technique d’une erreur de résolution.

Dernière validation ciblée : contrôle court réussi, puis MODEL_RESPONSE_INVALID après quatre tours et un outil réussi. Aucun timeout sur cet essai, aucune conclusion évaluable. Les 397 tests déterministes passent. Audits et rejeux sont conformes.

Aucun nouveau garde n’a déclenché dans ce dernier essai : les mesures amont à son déclenchement restent validées par simulation uniquement. Les échecs historiques ne sont pas effacés. La version est figée avec ces limites explicites.

Limites observées.

  • Données manquantes : en V2, permutation expose la logique mais pas les octets du secret. Des outils trop limités peuvent bloquer une résolution correcte.
  • Sélection incomplète : call_chain place un helper au-delà des dix fonctions du résumé fixe.
  • Mesures partielles : un compteur Muse Spark 1.3 incomplet est exclu du calcul des tokens, pas des scores. Versions Codex, plafonds de contexte et préparations diffèrent historiquement.
  • Transport et protocole : délais, retries et sorties JSON invalides ne démontrent pas une incapacité à résoudre le challenge. La cause exacte du dernier incident reste indéterminée.
  • Portée du benchmark : corpus pédagogique avec symboles en V2, une réponse retenue par case, PE x86-64 uniquement. Aucune garantie statistique ou généralisation à tous les binaires réels.

La préparation froide historique contient une mesure exceptionnellement longue dont la cause est inconnue. Elle n’est ni assimilée à du temps CPU ni mélangée aux courbes de réponse.

Ce projet m’a appris à contrôler les actions d’un agent et à vérifier ses preuves. J’ai aussi dû distinguer une réponse incorrecte d’un défaut du barème ou d’un incident technique, pour que les mesures reflètent ce qui s’était réellement passé.