Phase 13: Tools & Protocols

إعادة بناء وتثبت المساواة: اختبارات التفاوت، مقارنة البايت، ومركز الأدلة

اجتيازك في الاختبارات يثبت توافقك على الاختبارات، لذا المساواة تحتاج إلى شبكة تصل إلى كل حافة وتتطابق بتاتها بعد تغيير الاختبارات.

Type: Build

Languages: Python, C

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

Time: ~110 minutes

أهداف التعلم

  • اكتب إعادة بناء من قائمة التعليمات و اسم سجل الدليل الذي جاء منه.
  • بناء شبكة مدخلة مع فئة واحدة لكل خطر: صفر الحماية، القيم السلبية، تقسيم القص، 32 بتات إفراط، وفرق فخ.
  • إشغلي الأصلي وإعادة بناءه في برنامج واحد و سجل كل خلية، بما في ذلك فخ
  • إعادة بناء الإعادة مع العلامات الأصلية ومقارنة البايتات الوظيفة بعد غطاء حقل التنقل.
  • اقرأ المستمرات التي يُحتوى عليها نقطة التنقل و قارنها، حتى لا يمكن أن يمر حرف خاطئ.
  • إشغلي خادم MCP ريا من عميل استديو صغير و سجل كل خطوة مع هويته دليل.
  • صممت سجل إثبات مغلق غير مُصادر، والذي هو هاشي على مزاعمه و روابط الأدلة.

المشكلة

لقد طلبت من ريا ملف الوظائفpercent_changeفي مجالناledger-x86_64-O1.oغيدرا 12.1.4 أعادت رمزًا مزيفًا، وكتب العميل هذا "سي" منه:

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

أربعة اختبارات تمر:(100, 150)يعطي 50(200, 100)يعطي -50(0, 5)يعطي 0(50, 100)ويعطي 100 دولار، ويعيد بناء العميل أيضاًgradeأربعةifستة اختبارات أخرى تمت، ويقول التقرير "محقق"

هذا التقرير لا يجيب على أي من هذه الأسئلة:

  • ما الذي يعود به الأصلي عندما(after - before) * 100لا يتناسب في 32 بت؟
  • ما هو العودة الأصلية لدرجة 110؟
  • هل يقوم الإعادة بتجميعها إلى نفس 16 بايت كما كان الأصلي؟
  • عندما تكون الوظائف المجمعة تحتوي على نفس البايت، هل توضع إعادة توزيعها في نفس الثابتات؟
  • من أي دليل جاءت عملية إعادة البناء، وهل قرأ العميل الإدراج أو الرمز السري؟
  • ما هو الوضع الذي يحصل عليه الدليل على مضيف بدون مُحامِل C؟

كل هدف في هذا الدروس هو برنامج كتبنا و مرتبنا.

ما تقول الأدلة

حلقة العميل فيcode/main.pyلقد قمت بـ 6 مكالمات إلى 6.5.0 عبر الاستديوcode/fixtures/runs/agent-run.json.الخادم أجابinitializeمع نسخة بروتوكول 2025-11-25ثلاث مكالمات أعادت سجلات الأدلة

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

الدعوة الأولى دفعت لإستيراد "غيدرا" وتحليل ذاتيconfidence: "observed"،authority: "shipped-artifact"، و SHA-256 من الكائن ،9a4a8082ba5e285a732552981a6935e0c5255725664f380e37c44ebfb04d803fغيدرا تحميل الكائن الذي يمكن نقله.textفي0x100000، لذاpercent_changeيجلس في0x100000وgradeفي0x100010. . .

الرمز السوداني لـpercent_changeهو حيث longجاء من:

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;
  }
  ...
}

المتحلل رأىcltdوidivlاكتب كليهماedxوeaxووضع نموذج قيمة عائدة 16 بايت مع قسم 64 بايت. القائمة في نفس السجل تقول شيئا أكثر وضوحا:

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

كل عملية عرضها 32 بتIMUL EAX, ESI, 0x64يضاعف في 32 بت ويحافظ على النصف المنخفضCDQإشارات التوسعeaxفيedxوIDIV EDIيُقسم زوج 64 بت بـ 32 بت. إعادة بناء تلي القائمة هي 100 * (after - before) / beforeفيint. . .

  • نعمgradeالسجل لديه نفس الشكل. الرمز السيدو يسمي جدول،*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4)، و تعبير افتراضي لا يمكن قراءته مبني من0x66666667. الإدراج يظهر الانقسام بـ 10IMUL RAX, RAX, 0x66666667،SAR RCX, 0x22،SHR RAX, 0x3f،ADD ECX, EAX- إذنADD ECX, -0x6،CMP ECX, 0x4وJAأبقي عشرات من 6 إلى 10.XOR EAX, EAXقبل مقارنة هو الاختيار الافتراضي من 0. LEA RCX, [0x1000b4]وMOV EAX, dword ptr [RCX + RAX*0x4]اقرأ جدول من خمسة إدخالات 4 بايت.

الخطوة 4 قراءة تلك 20 بايت: 01000000 02000000 03000000 04000000 04000000الطاولة هي1, 2, 3, 4, 4على عشرات 6، 7، 8، 9، 10 هذا هو switchفيledger.c، معcase 10وcase 9تقاسم القيمة 4.

كلتا الأدلة في صفحات الأدلة الخاصة بـ "ريا" بدأت من القائمة و منread_bytesأول رمز مزيف لـ "DX-Ball" كانFUN_00406400(void)بدون إدخال كومة، و عرض التعليمات تصحيحه. اقرأ القائمة أولا. ذكر رقم تسجيل، ثم اكتب C.

اكتبوا المرشحين

المجلدcode/fixtures/candidates/تحتوي على ثمانية إعادة بناء، وظيفة واحدة لكل ملف. أربعة تتبع القائمة أو تجميع إلى نفس الرمز. أربعة معقولة وغير صحيحة، أو خاطئة في الطريقة التي يجد اختبار مكتوب يدويا.

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

كل ملف يتم تجميعه مع أمر الإعدادات ،clang -target x86_64-unknown-linux-gnu -O1 -fno-asynchronous-unwind-tables -c. تم تعيين الأشياء المجمعة بجوار المصادر بحيث يتم إجراء الاختبارات على مضيف دون كلانغ

الساق الأول: تنفيذ ومقارنة

تعريف ماكيمن لعام 1998 للتحقيق التفاضلي يحتاج إلى "نظامين أو أكثر مقارنة" تغذية "حالات الاختبار التي تم إنشاؤها بشكل ميكانيكي"، ويعامل نتيجة مختلفة، أو تعليق، أو حادث كمرشح حشيش. لقد سمى الجزء الصعب: "أسوأ مشكلة في الاختبار هو تقييم نتيجة الاختبار". نحن لا نحتاج إلى معرفة الإجابة الصحيحة. نحتاج إلى الأصلي والإعادة بناء لا يختلفون في أي مكان على شبكة التي تصل إلى كل خطر.

تصميم الشبكة

input_grid()فيcode/main.pyيُبني 92 خلية.percent_changeإنه يأخذ كل زوج من عشرةbeforeالقيم والسبعة afterالقيم:

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

لأجلgradeيتطلب 22 نقطة: كلا الجانبين من كل حافة الفرقة، السلبيات، 0، 110، والثنين intالحدودclassify()علامات كل خلية. التدفق الزائد لديه 32 خلية، سلبي 17، الحدود 14، 12 عادية، و حماية صفر 7. التخفيض السلبي لديه 5، التجويب 2، خارج المفتاح 2، والطاردة 1.

كل فئة موجودة لسبب واحد الخلايا الحارس الصفر تفتيشTEST EDI, EDIالفرع الخلايا السلبية تحقق تقسيم القصبة:idivحول نحو الصفر، لذلك -9 / 10هو 0 و -10 / 10هي -1، الخلايا الدائرة تحقق أن الضربة تحدث قبل الانقسام، لذلك (105 - 100) * 100 / 1005 وليس 0. تخطي الخلايا الزائدة(after - before) * 100يتجاوز 32 بت. خلية واحدة تعادل المنتج الملفوفINT_MINمعbeforeيساوي -1.

كانغ، تشين، إيد، و ريجر بنيت Csmith لتوليد البرامج بدون سلوك غير محدد. الخلاف في النبيذ حول سلوك غير محدد هو الضوضاء ولا تقرير عن حوادث. لا يتم تعريف التفاوض الموقعة في C. النبيذ هو كائن مرتب مع سلوك ثابت واحد، لذلك الخلايا المتفاوضة في نطاق. الدروس هي معرفة الخلايا التي تعتمد على المعيار والتي تعتمد على البايتات المجمعة.

السائق والفخ

code/driver.cيقرأ خلية واحدة لكل سطر من stdin ويقوم بطبع المدخلات والنتيجة الأصلية ونتيجة إعادة البناء. build_harness()يجمع المرشح مع -Dpercent_change=cand_percent_change -Dgrade=cand_gradeفي المضيف لينكس x86-64 هو الأصلي هو الكائن الثابت نفسه. في أي مضيف آخر هوledger.cتم تجميعها بشكل أصلي، وتسجيلات التقرير original_kind: fixture-source. . .

السائق يثبت جهازSIGFPEالمدير معsigsetjmpوsiglongjmp- لأجلbefore = -1وafter = 536870911، الفرق هو 536870912 والنتج 32 بت تغلف إلى -2147483648 . على x86-64 ، idivمع هذا الفائدة والقسم من -1 يزيد من استثناء خطأ القسم. النسبة لا تناسب في 32 بت (Intel SDM Volume 2, IDIV).trap. . .

على "آرك 64"sdivلا يوجد فخ ويعود -2147483648 لنفس الأوبرانديات. لقد سجلت تشغيلنا على جهاز Arm64 Mac بالضبط ذلك.

النتائج

prove_behaviour(PLAUSIBLE)يُبنيlongإعادة بناء، وإعادة بناء العدوان، تمرّت الحالات البرية العشر، فشلت الشبكة في 28 خلية:

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

جميع 26 percent_changeالفشل هو تفريغ الخلايا مع |before| > 1- لأجلbeforeيساوي 1 أو -1، يختصر النسبة 64 بت إلى القيمة الملفوفة، لذلك تتفق هذه الخلايا عن طريق الخطأ.gradeفشل لأن المفتاح الأصلي لا يحمل أي قضية لعشرات من 11 و فوق، و 110 / 10هو 11

prove_behaviour(EQUAL)يُبنيpercent_change_mul_first.cوgrade_switch.cجميع الخلايا 92 توافق.percent_change_div_first.cفشلت في اثنين من الحالات البرية الأربعة، وهي الحالة الوحيدة التي يكتشفها اختبار مكتوب يدوياً.

oracle_disagreements()يُحقق من شيء آخر.percent_change_model()وgrade_model()هي نماذج Python في القائمة، مع wrap32()للـ 32 بت مضاعفة و idiv32()للقاعدة الهندسة المعمارية. تقارن التقرير النموذج مع الأصلي الملاحظ على كل خلية. صفر الخلافات يعني أن النموذج والبائنري يتفقون على كل خلية. النموذج هو دليل مشتق والجري دليل الملاحظ. يحتفظ التقرير بهم في مجالات منفصلة.

الساق الثانية: إعادة التجميع ومقارنة

سلوك على شبكة هي ساق واحدة. يسأل الجانب الثاني ما إذا كان إعادة البناء ، التي تم تجميعها بنفس المكوّن والعلامات ، تنتج نفس البايتات. أفاد مشروع DX-Ball "63 بايت متطابقة" لمساعدته. أفاد مشروع TH04 بمطابقة 2,139 بايت. كل واحد استخدم المكوّن من فترة خاصته. لا يمكن أن تفعل ذلك نفسها.compare_functionsنص مزود التفرقات الخطية ورفق القيود "التجميع هو نص مزود غير شفاف؛ تعاديل التنقل غير متوفر".

اقرأ الموضوع

ElfObjectفيcode/main.pyيقرأ كائن ELF64 قابل للتنقل مع struct.unpack_fromإنه يُحقق من السحر، من الفئة ذات الـ64 بت، من البيانات الصغيرة،e_machine62، و e_typeثم يمر الرأس القسم، طاولة الخيوط،.symtabوكل.relaفي القسم. في الجهاز،percent_changeيبدأ في.textتعويض 0 مع الحجم 16 و gradeفي0x10بحجم 46totalيبدأ في0x40مع الحجم 31 و grade_labelفي0x60مع الحجم 63

الـ16 بايت من percent_changeهل هي85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3. لا يوجد أي إعادة التنقلgradeلديه واحد: تعويض0x26داخل الوظيفة، النمط R_X86_64_PC32الرمز.rodata، إضافة0x10إنه ينتمي إلى 7 بايتleaqفي التكافؤ0x23، والتي تكون أربعة بايت الأخيرة حقل التنقل

القناع، ثم مقارنة السجلات

في كائن RELA يعيش الإضافة في سجل الإنتقال، والحقل الأربعة بايتات هي صفر.grade_switch.oيحتفظ أيضاً بالصفر هناك. يمر بايت موازنة بسيطة بينما يتم نقل المواقع إلى أماكن مختلفة. mask_relocations()يُصفر الحقل على جانبي الأمر على أي حال، لذا فإن نفس الطريقة تنطبق على كائنات REL وبالنسبة إلى ماخ-أو، حيث يحمل الحقل الإضافة.

إذنprove_bytes()يُقارن سجلات التنقل حسب قسم التعويض والطراز والقسم المستهدف..rodata+0x10ويقول المرشح.rodata-0x4. الاضافة تختلف لأن الموضوع المرشح لا يحتوي علىlabels.relطاولة أمام طاولة المفتاح. إضافة مختلفة هي تعويض مختلف، وليس وظيفة مختلفة.

اقرأ المستمرات نقاط التنقل في

بالنسبة لحقل ذي علاقة بالجهاز الذي ينتهي إرشاداته، يضيف المعالج عنوان الإرشاد التالي، لذلك الهدف هو الرمز بالإضافة إلى الإضافة بالإضافة إلى 4..rodata+0x14، وفي المرشح هو.rodata+0x0. .read_table()يقرأ خمسة إدخالات 4 بايت هناك. كلاهما يحمل1, 2, 3, 4, 4. . .

العد الخامس يأتي من القائمة:CMP ECX, 0x4معJAيسمح بالمعايير 0 إلى 4، و [RCX + RAX*0x4]يقرأ 4 بايت لكل مؤشرCLAIMSفيcode/main.pyسجلات تشكيل gradeو سجل صفر عمليات نقلpercent_change. . .

هذه الخطوة موجودة بسبب خطأ تم توثيقه في مشروع TH08. تم تقييمها في وقت سابق "استعادة عنوان الهدف إلى حقل نقل التعليمات ، ثم مقارنة بايت التعليمات دون التحقق من البيانات اللاتبية العائمة في هذا العنوان. مصدر 0.0ويمكن أن يبدو بالضبط ضد الهدف128.0." تمت مراجعة 1548 مرجعية حرفية عائمة وتصحيح اثني عشر آخرين. grade_case10_wrong.cهو نسختنا من هذا الخطأ. 46 بايت رمزها يساوي الأصلي، سجل إعادة التنظيم له نفس الشكل، و جدولها هو1, 2, 3, 4, 3فقط التحقق المستمر يفشل

النتائج

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

نتيجة تستحق النظر ثانيةpercent_change_split.cيحتوي على نص مصدر مختلف ومقاطع متطابقة ، لأن Clang يعالج التخطيط الموقع على أنه غير محدد ويقوم بتثبيت after * 100 - before * 100إلى واحد مضاعفة.grade_table.cيطابق أيضاً بايت بـ بايت، لذا وصل المُجمّع إلى نفس الجدول من مؤشر صفوف و من switch. يثبت مطابقة البايت أن هذا المجمع، مع هذه العلامات، أنتج نفس الشفرة من المرشح.

الحجم هو تشخيص وليس قبول. صفحات دليل ريا تسجل 1825 بايت مرشح مع طول صحيح و 467 بايت مختلفة.wideإعادة البناء هو 26 بايت لأن movslq،imulq،cqtoوidivqلقد تم تحديد عرضها قبل أي تجميع

العميل يُحلق فوق الواقع

خمسة وستين من أدوات MCP rea لا تمتلك أي أمر CLI، و verify_reconstructionهو واحد منهم.McpStdioClientفيcode/main.pyهو أصغر عميل يصل إليهم.npx -y rea-agents@6.5.0 mcp، يكتب طلب JSON-RPC واحد لكل سطر إلى stdin ، ويقرأ كائن JSON واحد لكل سطر من stdout على حلقة.initializeمع نسخة بروتوكول 2025-11-25، ثمnotifications/initialized، ثمtools/call. الموعد النهائي الافتراضي لكل مكالمة هو 330 ثانية، نفس الرقم الذي يسمح بدء غيدرا بارد.

agent_loop()يقوم بالست مكالمات وتسجيل كل واحدة:

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": "..."
}

يتم تخزين سجلات الأدلة الكاملة تحت هوياتهم في نفس الملف.table_address()يجدLEAو العمل فيgradeإدراجها وتغذيتهاread_bytes. .open_binary،get_evidence_bundleوclose_binaryلا يعيدون سجل الأدلة ويُسجلون مع هوية صفراء.

run_agent(live=True)تحركات ريال عندما npxهو على الطريق وGHIDRA_INSTALL_DIRاسم دليل، وإلا فإنه يعيد تعيين العرض ويقول ذلك على stderr. الاختبارات تذهب خطوة أخرى. code/tests/fake_rea_server.pyهو خادم بديل يرد على كلtools/callمع سجل المسجل لنفس الأداة والحجج. العميل والحلقة تعمل على مواجهته في كل تجربة، دون إثارة rea، Ghidra، أو Java.

ما يمكن ومصادق "ريا" أن يقوله

ريا verify_reconstructionيستخدم الدليل والإرجاعات المترتبة على المعلومات المقدمة أو غير المعروفة لكل مطالبة. يقرأ قيودها الثابت "المرور يعني كل مطالبة مدعومة تم ترتيبه؛ فإنه لا يحدد مساواة التنفيذ العالمية".compare_functions. تُقارن ادعاء سلوكية اثنين من عمليات التقاط المسجلة منcompare_process_capturesعندما يكون سجل المصدر غير مكتمل في تغطية المكالمات، فإن المطالبة بشأن المكالمات هيunknownلا شيء داخل ريا يجمع وظيفة أعاد بناؤها أو يديرها على شبكة المدخل الخاص بك.

سجل الالتزامات لـ rea يتوقع سجل إثبات مع predicate_type: "rea.reconstruction-proof"،operation: "verify_reconstruction_obligations"،confidence: "observed"وnormalized_result.passed === trueلا توجد أداة حقيقة عامة تنبعث منهاbuild_proof()فيcode/main.pyيُصنع:

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_..."
}

الهوية هيev_بالإضافة إلى SHA-256 من السجل دون الهوية، على المفتاح المرتبة JSON. aggregate()يتم إغلاقها بشكل غير فعال: failفازت، إذاًunknownو قائمة دعم فارغة هيunknownعلى مضيف بدون مُحامِل C ، فإنّ مزاعم السلوكين هيunknown، لذا السجل يقولunknownحتى عندما تمر كلتا الطلبينauthorityهوcontrolled-replay، لأن الدليل يأتي من قناعةنا وليس من ملف الكائن وحده.

ما يقول البحوث

قياس ليو وراف وميسينسكي ثمانية أنظمة إزالة التجميع في تسعة تكوينات في عام 2026. لا يزال المرشحون الذين اجتازوا كل اختبار يضمينه المعيار المرجعي مختلفين عن المدخلات المضطربة. كان هذا النسبة 4.9 بالمئة بشكل عام و 13 بالمئة في نظام واحد على 300 وظيفة مكتبة غيتهوب و 287 وظيفة أساسية على CVE، أشد نموذج التحسين رفع معدل بناء غيدرا من 75 في المائة إلى 90 في المائة. على نفس الوظائف انخفض معدل التطابق السلوكي من 74% إلى 62%. لقد تعقبوا السبب إلى الحقول والمختلفات والحراس التي تم اختراعها، والتي استبدلت المجهولات المرئية التي يتركها المتحلل التقليدي.

لديناpercent_change_wide.cهو واحد من تلك الأنواع المخترعة.longتم إعادة بناءها ، وأحرزت النتيجة ، اجتازت عشرة اختبارات ، وفشلت 26 خلية تجاوزية. إعادة بناء العدالة هو حارس مخترع. كل منهما يمكن إعادة تنفيذها بموجب مقياس LLM4Decompile ، الذي يعيد تجميع الناتج ويجري تأكيدات المعيار المرجعي. اجتاز جيدرا نفسه السيدو-C 20.12٪ من تلك التأكيدات على HumanEval-Decompile.

قام DecompileBench بقياس 23400 وظيفة من 130 برنامجًا حقيقيًا. وجد أن أساليب LLM أقل بـ52.2 في المائة على دقة الوظائف من المفكّر الصناعي ، مع درجات تفهمية أعلى.

الأدلة الثلاثة التي تستخدمها الحقل هي الساقين الثلاثة التي يمكنك بناؤها. فينيكس و دريم أعادوا تجميع الناتج وتشغيل اختبارات البرنامج الأصلي. ماكيمن وسميث يديرون المدخلات المولدة ضد الأصلية مع المدخلات التي يتم احتفاظها داخل السلوك المحدد. رامس وإنجلر يبحثون عن وظائف C مع تنفيذ رمزي محدود، مما يثبت المساواة على جميع المسارات المستكشفة حتى حجم المدخل. هذه الدروس تبني الاثنين الأولين الثالث هو مختبر التدريب في دفتر الدليل في الدروس 35 عندما تكون الشبكة صغيرة جداً للاثقة.

بناءها

أستخدم الحزام المعتاد للمكتبة

bashcd phases/13-tools-and-protocols/36-rebuild-and-prove-equal
python3 code/main.py

مع كلانغ على المسار، يقوم التجربة بتجميع كل من أزواج المرشحين وتشغيل 92 خلية لكل منهما. فإنه يعيد تجميع ثمانية مرشحين، ويعيد تشغيل العميل، ويختم سجل الدليل. دون كلانغ يقرأ الأشياء الملتزمة ويبلغ عن ادعاءات السلوك على أنهunknown. . .

الشكل المتوقع على جهاز 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_..."}
}

اقرأcode/main.pyفي هذا الترتيب:

  1. wrap32()،c_div()،idiv32()، والثنين*_model()المهام تحتوي على تعبيرات الأرقام الكاملة في القائمة.
  2. input_grid()وclassify()بناء وتسمية الخلايا 92
  3. build_harness()وexecute()قم بتجميع برنامج واحد من الأصلي، والمرشحين الذين تم تغيير أسمائهم، و driver.cثم تشغيل الخلايا
  4. prove_behaviour()يعيد تقرير الجانب الأول مع عدم المطابقة والاختلافات في النموذج.
  5. ElfObjectيقرأ العناوين والرموز.relaالسجلاتmask_relocations()وread_table()إعداد المقارنة.
  6. prove_bytes()يعيد تقرير الساقين مع الأحجام والمناطق المخفية وشكل الإنتقال والجدوليات.
  7. McpStdioClientوagent_loop()القيادة الحقيقية وتسجيل كل خطوة.
  8. build_proof()وaggregate()لا يوجد سجل مغلق

استخدمها

لإثبات أن وظيفة جديدة متساوية، كرر هذه الخطوات:

  1. أحضرanalyze_functionو سجل هوية الدليل. اقرأ القائمة قبل الرمز السوداوي.
  2. اكتب المرشح من عرض العملية في القائمة. احتفظ باسم الوظيفة الأصلية.
  3. قم بإدراج كل مضاعفة، و أضف، و استبعاد، و تقسيم في القائمة. أضف مدخلات الشبكة التي تغلف كل واحد على عرضه.
  4. إضافة كلا الجانبين من كل ثابت مقارنة في القائمة.gradeتلك كانت 6 و 4 بعد الخسارة
  5. أضف كل مقسم يمكن أن يقع على الهدف، وتسجيل أي بنية كانت تعمل على الشبكة.
  6. إذهب على كلتا الساقين وقرأ الأسباب قبل الحالة
  7. أبقوا سجل الدليل بجانب المرشحevidence_linksأخبر القارئ على أي سجل يستند المطالبة.

إعادة تشغيل كلتا الساقين بعد أي تحديث للمؤلف. تعادل البايت هو بيان عن مُؤلف واحد مع مجموعة واحدة من العلامات.

المختبر التفاعلي

المختبر (أ): النموذج والعمل

منcodeالإرشادات، مفتوحة Python:

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

أركض

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]

المكالمة الأولى تُعطى -133، المنتج المُغلف مقسم على 3.'trap'والثالث يعطي -2147483648 . ويقول التقرير ان 10 حالات ساذجة تمت و 28 خلية شبكة فشلتreport.oracle_disagreementsقائمة فارغة

المختبر ب: قراءة الكائن

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)

البايتات في الحقل هي 00000000أسماء الإنتقال.rodataمع إضافة0x10، و الطاولة تقرأ[1, 2, 3, 4, 4]. . .

المختبر C: كسر ثابت واحد

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

المقاطع تتطابق، السجلات تتطابق، والجداول لا. الآن تحرير grade_case10_wrong.cلذاcase 10يعود 4 مرة أخرى ، أعيد تجميعه مع أمر العدالة ، ومشاهدة تغيير الحالة إلى pass. . .

المختبر D: محرك

مع ريال 6.5.0 وGhidra 12.1.4 مثبتة:

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

الأولanalyze_functionيستغرق الأمر عدة ثوان بينما تقوم Ghidra باستيراد الكائن. مقارنة أرقام الأدلة الجديدة مع الجري الملتزم. نفس البايتات تحت نفس ملف التحليل تعطى نفس الأرقام، لأن أرقام الأدوية هي hash المحتوى الذي يستبعد مسار الملف.

مختبر التدريب

تمدّ كلّ الساقين إلىtotalوgrade_labelمن نفس الكائن

المتطلبات:

  • لأجلtotalإنشاء الخلايا مع عدد 0، عدد سلبي، عنصر واحد، وجملة التي تغلف في 32 بت.
  • لأجلgrade_label، الدعوة الوظيفةgradeفي الخط ويقرأlabels.rel، جدول من خمسة تعويضات سلسلة نسبية مع عمليات إعادة توطين خاصة بها في .rela.rodata. . .
  • إضافةCLAIMSالإدخالgrade_labelمع إعادة التنقلين و جدول من خمسة إدخالات 4 بايت، ومقارنة السلاسل التي تُؤكد الإدخالات.
  • سجل الخلايا التي يختلف النموذج والجولة فيها، إذا كان هناك، ويشرح كل منها من القائمة.
  • إضافة ادعاء واحد لكل قدم لكل وظيفة إلى سجل الدليل والحفاظ على الإجمالي مغلق.

النجاح يعني أن سجل الدليل يضع قائمة بأربعة وظائف، كل ادعاء يحمل رقم الدليل، وgrade_labelيفشل عندما تختلف سلكة العلامة.

مهارات الناتج

هذا الدرس يُنتجoutputs/skill-rebuild-and-prove-equal.md. استخدمها لتحويل دليل الوظيفة وإعادة بناء المرشح إلى دليل على قدمين وسجل دليل. تتطلب المهارة إدراج قبل الرمز الخبيث، وشبكة مع كل فئة خطر، وتعويض التخفيضات بالإضافة إلى فحص ثابت، وجميع مغلق.

تحقق من ذلك

إشغال التجربة التجريبية و مجموعة الاختبار:

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

الاختبارات الـ35 تثبت:

  • c_div()يختصر نحو الصفر و wrap32()يتبع إضافة اثنين
  • النموذج يعطي -133 ل (3, INT_MAX)،trapللخلية الخدعة على x86-64 و -2147483648 على AArch64
  • الشبكة لديها 92 خلية و كل فئة خطرة، والحالات البغيضة تتجنب كل فئة صعبة
  • يجد قراءة ELF أربعة وظائف في التداعيات والحجم المسجلة
  • percent_changeهو 16 بايت بدون إعادة التنقل و gradeلديه واحدR_X86_64_PC32في0x26
  • إن حقل التنقل هو صفر و لا تغير التخفيض شيء في هذا جسم RELA
  • جميع المرشحين المتعهدين الثمانية يصلون إلى الوضع المتوقع، مع الأسباب المسجلة
  • grade_case10_wrongيمر بثبات مخفي و يفشل فقط في التحقق المستمر
  • المشاركات المسجلة في الساق الأولى تظهر عشرة مرّات ساذجة، 28 عدم مطابقة، و صفر الخلافات النموذجية
  • مع محفز، كل من الزوجين إعادة بناء وإعادة تشغيل مع نفس النتيجة
  • الوكيل يدير أهداف الكائن الملتزم به من خلال SHA-256 ، في الترتيب ، مع ev_أوراق الهوية على كل خطوة من الأدلة
  • الموقعread_bytesالسجل يساوي الجدول الذي يجده قارئ ELF
  • العميل يدير الخادم المزيف ويستسجل نفس هويات الأدلة التي تم تشغيلها الحقيقية
  • نتيجة خطأ من الخادم تذهب إلى سجل التشغيل
  • aggregate()يتم إغلاقها بشكل غير صحيح وتقوم الهوية الإثباتية بإعادة الحساب من محتوياتها

أساليب فشل الإنتاج

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

قاعدة العمليات

اكتب إعادة البناء من القائمة وسمي السجل الذي جاء منه. قم بتشغيله ضد الأصلي عبر شبكة تغطي كل متوسطة وتعبر كل ثابت مقارنة. قم بتجميعه مع العلامات الأصلية، و قم بتغطية حقل الترحيل، ومقارنة السجلات، وقراءة الثوابت التي تشير إليها. سجل كل ادعاء مع معرفه الدليل، ودع ادعاء غير مؤكد يبقى مجهولا.

المزيد من القراءة

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.