Phase 13: Tools & Protocols

Reconstruire et prouver l'égalité: tests différentiels, comparaison en octets et boucle de preuves

Passer vos tests prouve votre accord sur vos tests, donc l'égalité a besoin d'une grille qui atteint chaque bord et octets qui correspond après le masquage de relocalisation.

Type: Build

Languages: Python, C

Prerequisites: Phase 13 · 34 (lifting to pseudocode), Phase 13 · 35 (evidence, confidence, and unknowns)

Time: ~110 minutes

Objectifs d'apprentissage

  • Écrivez une reconstruction de la liste des instructions et nommez le dossier de preuve d'où elle provient.
  • Construisez une grille d'entrée avec une classe par danger: zéro garde, valeurs négatives, division de tronc, débordement de 32 bits et piège de division.
  • Exécutez l'original et la reconstruction en un programme et enregistrez chaque cellule, y compris un piège.
  • Récupérez la reconstruction avec les drapeaux originaux et comparez les octets de fonction après avoir masqué les champs de relocalisation.
  • Lisez les constantes à lesquelles une relocation pointe et comparez-les, de sorte qu'un faux littéral ne peut pas passer.
  • Conduisez le serveur MCP de Rea depuis un petit client de studio et enregistrez chaque étape avec son identifiant de preuve.
  • Mente un dossier de preuve fermé par défaut dont l'identification est un hash sur ses allégations et les liens de preuve.

Le problème

Vous avez demandé à Rea le dossier de fonctionnement de percent_changedans notre ledger-x86_64-O1.oGhidra 12.1.4 a renvoyé un pseudocode, et l'agent a écrit ce C dessus:

cint percent_change(int before, int after) {
    if (before == 0) return 0;
    return (int)(((long)after - before) * 100 / before);
}

Quatre tests passés:(100, 150)donne 50, (200, 100)donne -50, (0, 5)donne 0, (50, 100)L'agent a aussi été reconstruit.gradecomme quatre ifLes tests sont passés, six autres tests ont été confirmés.

Ce rapport ne répond à aucune de ces questions:

  • Qu' est-ce que le texte original revient quand (after - before) * 100ne peut pas être utilisé dans 32 bits ?
  • Qu'est-ce que le résultat original est de 110?
  • La reconstruction se compose-t-elle sur les mêmes 16 octets que l'original ?
  • Lorsque deux fonctions compilées ont les mêmes octets, leurs délocalisations pointent-elles vers les mêmes constantes?
  • De quel dossier provient la reconstruction, et l'agent a-t-il lu l'annonce ou le pseudocode ?
  • Quel est le statut de la preuve sur un hôte sans compilateur C ?

Chaque cible de cette leçon est un programme que nous avons écrit et compilé.

Ce que la preuve dit

La boucle d' agent est en train de bouger .code/main.pyJ'ai fait six appels à réa 6.5.0 sur le studio.code/fixtures/runs/agent-run.jsonLe serveur a répondu .initializeavec la version du protocole 2025-11-25Trois appels ont rapporté des preuves:

StepToolTimeEvidence id
2analyze_function percent_change6.31 sev_e88b10dc0c6400e51bb6f7fdc3eaea8d2c97cc02463f0db3e6afeefc273197cd
3analyze_function grade0.02 sev_67b6d3f992eaf49a2e16de084b6ebfdd9884e8c5c7f75e175b7bb20f4f748ce1
4read_bytes 0x1000b4, 200.00 sev_c26146cfedf4c1ee146d8ff00cfa9a5d3f5335bdd9c97ccf4e1af5f6ea3ed6bc

Le premier appel a été payé pour l'importation et l'analyse automatiques de Ghidra.confidence: "observed"- Je suis là .authority: "shipped-artifact", et le SHA-256 de l'objet,9a4a8082ba5e285a732552981a6935e0c5255725664f380e37c44ebfb04d803fGhidra charge l' objet relocalisable avec .textà0x100000Je suis un homme .percent_changeIl est assis à0x100000et gradeà0x100010- Je suis désolé .

Le pseudocode de percent_changeest où le longprovenant de:

cundefined1  [16] percent_change(int param_1,int param_2,ulong param_3)
{
  long lVar1;
  undefined1 auVar3 [16];
  if (param_1 != 0) {
    lVar1 = (long)((param_2 - param_1) * 100);
    auVar3._0_8_ = lVar1 / (long)param_1 & 0xffffffff;
    auVar3._8_8_ = lVar1 % (long)param_1 & 0xffffffff;
    return auVar3;
  }
  ...
}

Le décompilateur a vu .cltdet idivlécrire les deux edxet eaxet a modélisé une valeur de retour de 16 octets avec une division de 64 bits.

0x100000: TEST EDI, EDI
0x100002: JZ 0x0010000d
0x100004: SUB ESI, EDI
0x100006: IMUL EAX, ESI, 0x64
0x100009: CDQ
0x10000a: IDIV EDI
0x10000c: RET
0x10000d: XOR EAX, EAX
0x10000f: RET

Chaque opération est de 32 bits de large.IMUL EAX, ESI, 0x64multiplié par 32 bits et conserve la moitié basse. CDQ- les signes s' étendenteaxdans edx, et IDIV EDIune réparation qui suit la liste est 100 * (after - before) / beforedans int- Je suis désolé .

Le gradeLe pseudocode nomme une table,*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4), et une expression par défaut illisible construite à partir de 0x66666667La liste montre la division par 10 comme IMUL RAX, RAX, 0x66666667- Je suis là .SAR RCX, 0x22- Je suis là .SHR RAX, 0x3f- Je suis là .ADD ECX, EAX- Alors ...ADD ECX, -0x6- Je suis là .CMP ECX, 0x4, et JAGardez des dizaines de 6 à 10.XOR EAX, EAXavant la comparaison est la valeur par défaut de 0. LEA RCX, [0x1000b4]et MOV EAX, dword ptr [RCX + RAX*0x4]lire une table de cinq entrées de 4 bytes.

Étape 4 lire ces 20 octets: 01000000 02000000 03000000 04000000 04000000La table est là .1, 2, 3, 4, 4Pour dix, 6, 7, 8, 9, 10.switchdans ledger.c, avec case 10et case 9partage de la valeur 4.

Les deux preuves figurant dans les pages de preuve de réa ont commencé à partir de la liste et de read_bytesLe premier pseudocode de DX-Ball étaitFUN_00406400(void)Il n'y a pas d'entrée de pile, et la vue des instructions l'a corrigé.

Écrivez les candidats

Le dossier code/fixtures/candidates/Il contient huit reconstructions, une fonction par fichier. Quatre suivent la liste ou compiler au même code. Quatre sont plausibles et erronés, ou erronés dans la façon qu'un test écrit à la main trouve.

FileSourceWhat it does
percent_change_mul_first.c100 * (after - before) / beforethe listing in C
percent_change_split.c(after * 100 - before * 100) / beforedifferent text, same bytes after clang folds it
percent_change_wide.c(int)(((long)after - before) * 100 / before)the pseudocode's long, divides before it wraps
percent_change_div_first.c(after - before) / before * 100divides first, so rounding differs
grade_switch.cswitch (score / 10) with the five casesthe original source
grade_table.cunsigned slot = score / 10 - 6; slot < 5 ? points[slot] : 0the listing in C
grade_thresholds.cif (score >= 90) return 4; ...no upper bound on the top band
grade_case10_wrong.ccase 10: return 3;one table entry wrong

Chaque fichier est compilé avec la commande de fixation, clang -target x86_64-unknown-linux-gnu -O1 -fno-asynchronous-unwind-tables -cLes objets compilés sont commis à côté des sources de sorte que les tests se déroulent sur un hôte sans clang.

1ère étape: Exécuter et comparer

La définition de McKeeman de 1998 des tests différentiels nécessite "deux systèmes comparables ou plus" alimentés par "cas de test générés mécaniquement", et traite un résultat différent, un accrochage ou un crash comme un candidat à un bug. Il a nommé la partie difficile: " Le plus gros problème de l'essai est d'évaluer le résultat d'un test. " La fonction originale est l'oracle ici. Nous n'avons pas besoin de connaître la bonne réponse. On a besoin de l'original et de la reconstruction pour ne pas être en désaccord nulle part sur une grille qui atteint tous les dangers.

Conception du réseau

input_grid()dans code/main.pyIl construit 92 cellules.percent_changeIl faut chaque paire de dix .beforeLes valeurs et sept afterles valeurs:

before: 0, 1, -1, 3, -3, 7, 100, -100, 2147483647, -2147483648
after:  0, 5, -5, 105, 536870911, 2147483647, -2147483648

Pour gradeIl faut 22 points: les deux côtés de chaque bord de bande, négatifs, 0, 110, et les deux intLes limites.classify()le surcoulant a 32 cellules, négative 17, limite 14, plaine 12 et garde zéro 7. le troncage négatif a 5, arrondissement 2, au-delà du commutateur 2, et piège 1.

Chaque classe existe pour une raison.TEST EDI, EDILes cellules négatives vérifient la division troncante:idivronde vers zéro, donc -9 / 10est de 0 et -10 / 10est -1. Les cellules rotatives vérifient que le multiplicateur se produit avant la division, donc (105 - 100) * 100 / 100Les cellules de surcoût produisent(after - before) * 100La cellule de piège unique rend le produit enveloppé égalINT_MINavec beforeégal à -1.

Yang, Chen, Eide et Regehr ont construit Csmith pour générer des programmes sans comportement indéfini. Un désaccord oracle sur un comportement indéfini est un bruit et jamais un rapport de bug. Le débordement signé est indéfini en C. Notre oracle est un objet compilé avec un comportement fixe, donc les cellules de débordement sont dans la portée.

Le conducteur et le piège

code/driver.cLire une cellule par ligne de stdin et imprime les entrées, le résultat original et le résultat de reconstruction. build_harness()compilera le candidat avec -Dpercent_change=cand_percent_change -Dgrade=cand_gradeDans un hôte x86-64 Linux, l'original est l'objet fixe lui-même.ledger.cLes rapports ont été compilés de manière native et les dossiers du rapport ont été enregistrés original_kind: fixture-source- Je suis désolé .

Le conducteur installe un SIGFPEavec sigsetjmpet siglongjmpPour ...before = -1et after = 536870911, la différence est 536870912 et le produit 32 bits se termine à -2147483648.idivLe quotient ne s'inscrit pas dans les 32 bits (Intel SDM Volume 2, IDIV).trap- Je suis désolé .

Sur l'Arche64, sdivLe récit indique lequel a été exécuté.

Résultats

prove_behaviour(PLAUSIBLE)construit le longLes dix cas naïfs passent, la grille échoue sur 28 cellules.

FunctionInputsClassOriginalRebuild
percent_change3, 2147483647overflow-133-1431655899
percent_change100, 2147483647overflow-1012147483547
percent_change2147483647, 5overflow0-99
grade110beyond the switch04
grade2147483647beyond the switch04

Toutes les 26 percent_changeLes défaillances sont des cellules de débordement avec |before| > 1Pour ...beforeLe coefficient de 64 bits est réduit à la valeur enveloppée, de sorte que ces cellules s'accordent par accident.gradeéchec parce que l'interrupteur original n'a pas de raison pour des dizaines de 11 et plus, et 110 / 10C'est 11 ans.

prove_behaviour(EQUAL)construit percent_change_mul_first.cet grade_switch.cToutes les 92 cellules sont d'accord.percent_change_div_first.cIl échoue dans deux des quatre cas naïfs, qui est le seul cas que le test écrit à la main trouve.

oracle_disagreements()Il vérifie une seconde chose.percent_change_model()et grade_model()sont des modèles Python de la liste, avec wrap32()pour le multiplicateur de 32 bits et idiv32()Le rapport compare le modèle avec l'original observé sur chaque cellule. Zéro désaccords signifie que le modèle et le binaire sont d'accord sur chaque cellule. Le modèle est une preuve dérivée et la course est une preuve observée. Le rapport les garde dans des champs distincts.

Deuxième étape: recompiler et comparer

Le deuxième étage demande si la reconstruction, compilée avec le même compilateur et les mêmes drapeaux, produit les mêmes octets. Le projet DX-Ball a rapporté "63 octets correspondants" pour son assistant. Le projet TH04 a rapporté un match de 2,139 octets. Chacun a utilisé le compilateur de sa propre période. réelle elle-même ne peut pas le faire.compare_functionsle texte du fournisseur de dérivés de ligne et attache la limite "L'assemblage est texte opaque du fournisseur; la normalisation de la relocalisation n'est pas disponible".

Lire l'objet

ElfObjectdans code/main.pyest un objet ELF64 relocalisable avec struct.unpack_fromIl vérifie la magie, la classe de 64 bits, les données de petite enjeu,e_machine62, et e_type1. Puis il passe les en-têtes de section, la table de chaînes, .symtab, et chaque .relaDans le fichier,percent_changecommence à.textdécompensé 0 avec la taille 16 et gradeà0x10avec la taille 46. totalcommence à0x40avec la taille 31 et grade_labelà0x60avec la taille 63.

Les 16 octets de percent_changesont85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3La fonction n'a pas de délocalisation.gradea un: décompensé 0x26à l'intérieur de la fonction, type R_X86_64_PC32, symbole .rodata, additionnel0x10Il appartient au 7 bytes .leaqà l' équilibre 0x23, dont les quatre derniers octets sont le champ de déplacement.

Masque, puis compare les dossiers

Dans un objet RELA, l'addend vit dans le dossier de relocalisation, et les quatre octets de champ sont zéro.grade_switch.oUn simple byte comparer passe alors que les deux délocalisations nomment des lieux différents. mask_relocations()Le champ est nul de tous les deux côtés, donc la même méthode s'applique aux objets REL et à Mach-O, où le champ porte l'addend.

Alors ...prove_bytes()Comparer les enregistrements de délocalisation par section de compensation, type et cible..rodata+0x10et le candidat dit:.rodata-0x4L' addendum diffère parce que l' objet candidat n' a pas delabels.relUne addition différente est un décalage différent, pas une fonction différente.

Lisez les constantes les points de relocalisation à

Pour un champ relatif au PC qui termine son instruction, le processeur ajoute l'adresse de l'instruction suivante, de sorte que la cible est le symbole plus addend plus 4..rodata+0x14, et dans le candidat c' est.rodata+0x0- Je suis là .read_table()Il y a cinq entrées de 4 bytes.1, 2, 3, 4, 4- Je suis désolé .

Le nombre cinq vient de la liste:CMP ECX, 0x4avec JAadmettant les indices 0 à 4, et [RCX + RAX*0x4]lisent 4 octets par index. CLAIMSdans code/main.pyenregistrement de cette forme pour gradeet enregistrent zéro déménagement pour percent_change- Je suis désolé .

Cette étape existe en raison d'un bug documenté par le projet TH08. Sa comparaison plus ancienne "a reproduit l'adresse cible dans le champ de relocation de l'instruction, puis a comparé les octets d'instruction sans vérifier les données littérales flottantes à cette adresse. source 0.0Il pourrait donc paraître exact contre l' objectif 128.0." Le correcteur a vérifié 1 548 références littérales flottantes et a corrigé douze autres. grade_case10_wrong.cSon code de 46 octets équivaut à l'original, son enregistrement de relocation a la même forme, et sa table est1, 2, 3, 4, 3Seul le contrôle constant échoue.

Résultats

CandidateSizeMasked bytesRelocation recordsTableStatus
percent_change_mul_first16 vs 16equalnonenonepass
percent_change_split16 vs 16equalnonenonepass
percent_change_wide16 vs 26size differsnonenonefail
percent_change_div_first16 vs 18size differsnonenonefail
grade_switch46 vs 46equalsame shape1,2,3,4,4 bothpass
grade_table46 vs 46equalsame shape1,2,3,4,4 bothpass
grade_thresholds46 vs 39size differs1 vs 0nonefail
grade_case10_wrong46 vs 46equalsame shape1,2,3,4,4 vs 1,2,3,4,3fail

Deux résultats méritent un deuxième regard.percent_change_split.ca un texte source différent et des octets identiques, car clang traite le débordement signé comme indéfini et se replie after * 100 - before * 100en un multiplier. grade_table.cIl correspond également octet par octet, de sorte que le compilateur a atteint la même table à partir d'un index d'un tableau et d'un switch. Une correspondance en octets prouve que ce compilateur, avec ces drapeaux, a produit le même code du candidat.

La taille est un diagnostic et non une acceptation. Les pages de preuve de rea enregistrent un candidat de 1 825 octets avec la bonne longueur et 467 octets différents.widereconstruire est de 26 octets parce que movslq- Je suis là .imulq- Je suis là .cqto, et idivqLa liste nous avait dit la largeur avant toute compilation.

L' agent est en train de se détourner

65 des outils MCP de rea n'ont pas de commande CLI, et verify_reconstructionC'est l'un d'entre eux.McpStdioClientdans code/main.pyC'est le plus petit client qui les atteint.npx -y rea-agents@6.5.0 mcp, écrit une requête JSON-RPC par ligne à stdin, et lit un objet JSON par ligne de stdout sur un fil.initializeavec la version du protocole 2025-11-25Alors ...notifications/initializedAlors ...tools/call. La date limite par défaut par appel est de 330 s, le même nombre réal permet un démarrage à froid Ghidra.

agent_loop()Il fait les six appels et enregistre chacun:

json{
  "step": 2,
  "tool": "analyze_function",
  "arguments": {"procedure": "percent_change"},
  "seconds": 6.31,
  "is_error": false,
  "evidence_id": "ev_e88b10dc0c6400e51bb6f7fdc3eaea8d2c97cc02463f0db3e6afeefc273197cd",
  "confidence": "observed",
  "authority": "shipped-artifact",
  "limitations": ["Resolved reference metadata identifies computed, indirect, external, call, jump, and data edges; unresolved targetless flows remain unknown, and function classifications distinguish thunks and externals.", "..."],
  "result_sha256": "..."
}

Les dossiers complets des preuves sont stockés sous leur identité dans le même fichier.table_address()trouve le LEAL'opérand dans le gradel' énumération et l' alimentation read_bytes- Je suis là .open_binary- Je suis là .get_evidence_bundle, et close_binaryne renvoient pas de dossier de preuve et sont enregistrés avec un identifiant nul.

run_agent(live=True)réa lorsque npxIl est sur le chemin et GHIDRA_INSTALL_DIRIl est possible de modifier le nom d'un répertoire, sinon il reproduit le fichier et dit ça sur stderr.code/tests/fake_rea_server.pyest un serveur alternatif qui répond à chaque tools/callLe client et la boucle s'y opposent à chaque test, sans réa, Ghidra ou Java installés.

Ce que le vérificateur de réa peut et ne peut pas dire

ré's verify_reconstructionLes données de référence sont les données de référence de la fonction de référence de la fonction de référence de la fonction de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référence de référencecompare_functionsUne affirmation comportementale compare deux captures de processus enregistrées à partir de compare_process_captures. Lorsqu'un enregistrement source ne couvre pas complètement les appels, une réclamation concernant les appels est unknownRien à l'intérieur de réa ne compile une fonction reconstruite ou l'exécute sur votre grille d'entrée.

Le registre des obligations de rea s'attend à un dossier de preuve avec predicate_type: "rea.reconstruction-proof"- Je suis là .operation: "verify_reconstruction_obligations"- Je suis là .confidence: "observed", et normalized_result.passed === trueAucun outil public ne l'émet.build_proof()dans code/main.pyle mélange:

json{
  "predicate_type": "rea.reconstruction-proof",
  "operation": "verify_reconstruction_obligations",
  "confidence": "observed",
  "authority": "controlled-replay",
  "normalized_result": {
    "passed": true,
    "status": "pass",
    "claims": [
      {"claim_id": "percent_change.behaviour", "kind": "behavioral", "status": "pass", "evidence_ids": ["ev_e88b10dc..."]},
      {"claim_id": "grade.behaviour", "kind": "behavioral", "status": "pass", "evidence_ids": ["ev_67b6d3f9..."]},
      {"claim_id": "percent_change.bytes", "kind": "structural-function", "status": "pass", "evidence_ids": ["ev_e88b10dc..."]},
      {"claim_id": "grade.bytes", "kind": "structural-function", "status": "pass", "evidence_ids": ["ev_67b6d3f9..."]},
      {"claim_id": "grade.referenced-constants", "kind": "structural-function", "status": "pass", "evidence_ids": ["ev_67b6d3f9..."]}
    ]
  },
  "evidence_links": ["ev_e88b10dc...", "ev_67b6d3f9..."],
  "evidence_id": "ev_..."
}

L' identité est ev_Plus le SHA-256 du disque sans l'identifiant, sur JSON compact à clé triée. aggregate()est fermé en cas de défaillance: failIl gagne, alors.unknown, et une liste de demandes est vide unknownSur un hôte sans compilateur C , les deux revendications de comportement sont unknown, le dossier ditunknownmême si les deux demandes de octets passent. authorityest controlled-replay, parce que la preuve vient de notre propre harnais et pas du seul fichier objet.

Ce que la recherche dit

Liu, Raff et Micinski ont mesuré huit systèmes de décompilation dans neuf configurations en 2026. Les candidats qui ont passé tous les tests que l'indice de référence inclut sont toujours divergents sur les entrées fuzzed. Le taux était de 4,9% dans l'ensemble et de 13% pour un système. Sur 300 fonctions de bibliothèque GitHub et 287 fonctions basées sur CVE, le modèle de raffinement le plus fort a augmenté le taux de création de Ghidra de 75% à 90%. Sur les mêmes fonctions, le taux de correspondance comportementale est passé de 74% à 62%. Ils ont repéré la cause à des champs, des types, des calées et des gardes inventés qui ont remplacé les inconnus visibles qu'un décompilateur traditionnel laisse derrière lui.

Notre .percent_change_wide.cLe pseudocode offrait unelongLe groupe de recherche a été créé par le groupe de recherche de Ghidra, qui a réussi à obtenir des résultats de recherche de la recherche de la recherche de la recherche de l'information sur la technologie de l'information et de l'information sur la technologie de l'information.

DecompileBench a mesuré 23 400 fonctions à partir de 130 programmes réels. Il a trouvé que les méthodes LLM étaient 52,2% moins précises que les décompilateurs industriels, avec des scores de compréhensibilité plus élevés.

Les trois preuves que le champ utilise sont les trois pattes que vous pouvez construire. Phoenix et DREAM recompilent la sortie et exécutent les tests du programme d'origine. McKeeman et Csmith exécutent des entrées générées par rapport à l'original avec des entrées conservées à l'intérieur du comportement défini. Ramos et Engler vérifient deux fonctions C avec une exécution symbolique limitée, ce qui prouve l'égalité sur tous les chemins explorés jusqu'à une taille d'entrée. Cette leçon construit les deux premières. Le troisième est le laboratoire de pratique dans le registre des preuves de la leçon 35 quand une grille est trop petite pour être fiable.

Faites-le

  • Je vous ai donné le harnais de la bibliothèque standard.
bashcd phases/13-tools-and-protocols/36-rebuild-and-prove-equal
python3 code/main.py

Avec le clang sur le chemin, la démo compile les deux paires de candidats et exécute 92 cellules pour chacun. Il recompile huit candidats, reproduit l'agent exécuté, et remplit le dossier de preuve. Sans clang il lit les objets engagés et rapporte les réclamations de comportement commeunknown- Je suis désolé .

Forme prévue sur un arm64 Mac:

json{
  "host": "aarch64 Darwin",
  "originalKind": "fixture-source",
  "gridCells": 92,
  "plausible": {"naivePassed": true, "gridMismatches": 28, "oracleDisagreements": 0},
  "equal": {"naivePassed": true, "gridMismatches": 0, "oracleDisagreements": 0},
  "legTwo": {
    "percent_change_mul_first": {"status": "pass", "reasons": []},
    "percent_change_wide": {"status": "fail", "reasons": ["size 16 vs 26 bytes"]},
    "grade_case10_wrong": {"status": "fail", "reasons": ["referenced constants differ: [1, 2, 3, 4, 4] vs [1, 2, 3, 4, 3]"]}
  },
  "agentRun": {"mode": "replayed fixture", "server": {"name": "rea", "version": "6.5.0", "protocolVersion": "2025-11-25"}},
  "proof": {"status": "pass", "evidence_id": "ev_..."}
}

Lire code/main.pydans cet ordre:

  1. wrap32()- Je suis là .c_div()- Je suis là .idiv32(), et les deux *_model()Les fonctions contiennent la sémantique entière de la liste.
  2. input_grid()et classify()construire et marquer les cellules 92.
  3. build_harness()et execute()compiler un programme à partir de l'original, les candidats renommés, et driver.c, puis faire fonctionner les cellules.
  4. prove_behaviour()renvoie le rapport de première étape avec des désaccords et des désaccords de modèle.
  5. ElfObjectIl lit les en-têtes, les symboles et .relaLes dossiers.mask_relocations()et read_table()préparer la comparaison.
  6. prove_bytes()renvoie le rapport de la jambe à deux avec des tailles, des intervalles masqués, la forme de la relocalisation et des tableaux.
  7. McpStdioClientet agent_loop()réalisez et enregistrez chaque étape.
  8. build_proof()et aggregate()- le dossier de défaillance.

Utilisez-le

Pour prouver que la nouvelle fonction est égale, répétez ces étapes:

  1. Apportez-le .analyze_functionPour la fonction, et enregistrer l'identifiant de la preuve.
  2. Écrivez le candidat à partir des largeurs d'opération de la liste.
  3. Lisez chaque fois que vous multipliez, ajoutez, soustraisez et divisez dans la liste. Ajoutez des entrées de grille qui enveloppent chacune à sa largeur.
  4. Ajoutez les deux côtés de chaque constante de comparaison dans la liste.gradeCe sont 6 et 4 après le soustrait.
  5. Ajoutez tous les diviseurs qui peuvent piéger sur la cible, et enregistrer quelle architecture a exécuté la grille.
  6. Faites-moi courir les deux jambes et lisez les raisons avant le statut.
  7. Gardez le dossier à côté du candidat.evidence_linksindiquer à un lecteur sur lequel repose la demande.

Récouter les deux pattes après une mise à jour du compilateur.

Laboratoire interactif

Laboratoire A: le modèle et la course

De la coderépertoire, ouverture Python:

bashcd phases/13-tools-and-protocols/36-rebuild-and-prove-equal/code
python3 -q

Je vais courir .

pythonfrom main import *
percent_change_model(3, INT_MAX)
percent_change_model(-1, 536870911, "x86_64")
percent_change_model(-1, 536870911, "aarch64")
report = prove_behaviour(PLAUSIBLE)
report.naive_passed, len(report.mismatches), report.mismatches[0]

Le premier appel donne -133, le produit enveloppé divisé par 3. Le second donne 'trap'Le rapport montre que dix cas naïfs ont été passés et 28 cellules grille ont échoué.report.oracle_disagreementsavec une liste vide.

Laboratoire B: lire l'objet

pythonobj = ElfObject(ORIGINAL_OBJECT)
grade = obj.function("grade")
grade.size, grade.data[0x26:0x2a].hex(), grade.relocations
read_table(obj, grade.relocations[0], 5, 4)

Les octets de champ sont 00000000- Les noms des délocalisations.rodataavec ajout 0x10, et la table est[1, 2, 3, 4, 4]- Je suis désolé .

Laboratoire C: briser une constante

pythonreport = prove_bytes("grade", CANDIDATES / "grade_case10_wrong.o")
report.masked_equal, report.relocations_match, report.tables_equal, report.reasons()

Les octets correspondent, les enregistrements correspondent, et les tables ne le font pas.grade_case10_wrong.cAlors ...case 10retourne 4 encore, le recompiler avec la commande de fixation, et regarder le changement d'état à pass- Je suis désolé .

Laboratoire D: réaction

Avec réa 6.5.0 et Ghidra 12.1.4 installés:

bashGHIDRA_INSTALL_DIR=/opt/homebrew/Cellar/ghidra/12.1.4/libexec \
JAVA_HOME=/opt/homebrew/opt/openjdk@21 \
python3 code/main.py --live

La première .analyze_functionIl faut quelques secondes pendant que Ghidra importe l'objet. Comparer les nouveaux identifiants de preuve avec la mise en œuvre engagée. Les mêmes octets sous le même profil d'analyse donnent les mêmes identifiants, car l'id est un hash de contenu qui exclut le chemin du fichier.

Laboratoire de pratique

Étendre les deux jambes à totalet grade_labeldu même objet.

Les exigences:

  • Pour total, l'entrée est un tableau et un compte. Construisez des cellules avec un compte 0, un compte négatif, un élément, et des sumes qui se terminent à 32 bits.
  • Pour grade_label, la fonction appelle gradeen ligne et se lit labels.rel, une table de cinq compensations relatives de chaînes avec leurs propres délocalisations dans .rela.rodata- Je suis désolé .
  • Ajouter un CLAIMSentrée pour grade_labelavec deux relocalisations et une table de cinq entrées de 4 bytes, et comparer les chaînes auxquelles les entrées pointent.
  • Enregistrez les cellules sur lesquelles le modèle et la course ne sont pas d'accord, le cas échéant, et expliquez chacune de ces cellules à partir de la liste.
  • Ajouter une réclamation par étape par fonction au dossier de preuve et garder l'ensemble de défaillance fermé.

Le succès signifie que le dossier de preuve énumère quatre fonctions, chaque réclamation porte un identifiant de preuve, et grade_labeléchoue lorsqu'une chaîne d'étiquette est différente.

Les compétences en matière de sortie

Cette leçon produit outputs/skill-rebuild-and-prove-equal.md. Utilisez-le pour transformer la preuve de fonction et la reconstruction d'un candidat en une preuve à deux pattes et un enregistrement de preuve.

Vérifiez

Exécutez la démo et la suite de test:

bashcd phases/13-tools-and-protocols/36-rebuild-and-prove-equal
python3 code/main.py
python3 -m unittest discover -s code/tests -v

Les 35 tests prouvent:

  • c_div()se tronque vers le zéro et wrap32()suit le complément de deux
  • Le modèle donne -133 pour (3, INT_MAX)- Je suis là .trappour la cellule de piège sur x86-64 et -2147483648 sur AArch64
  • La grille a 92 cellules et chaque classe de danger, et les cas naïfs évitent chaque classe dure
  • le lecteur ELF trouve quatre fonctions aux compensations et tailles enregistrées
  • percent_changeest de 16 octets sans délocalisation et gradeIl en a un .R_X86_64_PC32à0x26
  • les octets du champ de relocation sont zéro et le masquage ne change rien dans cet objet RELA
  • les huit candidats engagés atteignent le statut attendu, avec les raisons indiquées
  • grade_case10_wrongpasse des octets masqués et ne parvient que à vérifier la constante
  • Les courses enregistrées de la jambe 1 montrent dix passes naïves, 28 désaccords et zéro désaccord de modèle
  • avec un compilateur, les deux paires reconstruisent et redémarrent avec le même résultat
  • l'agent est dirigé vers l'objet engagé par SHA-256, dans l'ordre suivant:ev_Les identifiants de chaque étape de la preuve
  • le read_bytesrecord est égal à la table que le lecteur ELF trouve
  • le client exploite le faux serveur et enregistre les mêmes identifiants de preuve que la vraie course
  • un résultat d'erreur du serveur entre dans le dossier d'exécution
  • aggregate()est fermé en défaut et l'identifiant de preuve recompte à partir de son contenu

Mode de défaillance de la production

FailureWhat the weak report saysWhat the harness must prove
Rebuild keeps the decompiler's long"ten tests pass"26 overflow cells differ from the original
Thresholds replace a switch"every grade band passes"110 and larger return 0 in the original
Divide before multiply"formula is algebraically equal"rounding cells differ, naive cases fail
Trap cell skipped"no crashes"x86-64 raises #DE, AArch64 returns INT_MIN, the report names the host
Byte compare without masking"bytes equal"relocation records name different offsets
Masking without reading constants"bytes equal after masking"1,2,3,4,3 differs from 1,2,3,4,4
Size taken as acceptance"same length"differing bytes inside the same length
Different compiler or flags"bytes differ, rebuild wrong"the same source gives different bytes, so the compare is void
Model treated as observation"model agrees"the run is observed, the model is derived, disagreements are listed
Claim without evidence id"verified"every claim links an ev_ record of the function it covers
No compiler on the CI host"passed"behaviour claims are unknown and the aggregate is unknown
Pseudocode read as source"decompiler says long"the listing shows IMUL EAX, CDQ, IDIV EDI

Règlement d'exploitation

Écrivez la reconstruction de la liste et nommez le registre d'où elle est venue. Appliquez-la contre l'original sur une grille qui enveloppe chaque intermédiaire et traverse chaque constante de comparaison. Remplissez-la avec les drapeaux originaux, masquez les champs de relocalisation, comparez les enregistrements et lisez les constantes qu'ils pointent vers. Enregistrez chaque réclamation avec son identifiant de preuve, et laissez une réclamation non vérifiée rester inconnue.

Pour en savoir plus

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.