فاز 13 · درس 35

شواهد، سطح اطمینان و مجهولات در تحلیل باینری

نوع
ساختنی
زمان
حدود 105 دقیقه
زبان‌ها
Python, C
پیش‌نیاز
فاز 13 · درس 32 (ساخت ساختار ELF از صفر)، فاز 13 · درس 33 (دیس‌اسمبلی و جریان کنترل)، فاز 13 · درس 34 (تبدیل به شبه‌کد)
یک دفترچه ثبت سوابق فلزی مهروموم‌شده با زنجیرهای هگزادسیمال متصل به چند برچسب مهر تأیید
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. سه واژه برای سطح اطمینان، پنج واژه برای مرجعیت
  4. ساختار رکورد
  5. شناسه، هشی از محتوا است و مسیر فایل در آن نقشی ندارد
  6. مشاهده‌ی مستقیم فایل باینری
  7. جدول را مشتق کنید و صریحاً بگویید که آن را مشتق کرده‌اید
  8. پیش‌بینی‌ها، رکوردهای استنباط‌شده هستند
  9. راستی‌آزمایی در برابر یک اجرا
  10. قوانین ارتقا
  11. مجهولات، رکوردهایی دارای زنجیره‌ی هش هستند
  12. کش پاسخ‌ها با پایگاه‌داده‌ی تحلیل تفاوت دارد
  13. چرا ایجنت به چنین ساختاری نیاز دارد
  14. پیاده‌سازی
  15. استفاده از آن
  16. کارگاه تعاملی
  17. بخش اول: محاسبه‌ی مجدد شناسه rea
  18. بخش دوم: رد یک ارتقا
  19. بخش سوم: رد اسنپ‌شات منقضی
  20. بخش چهارم: شکستن زنجیره‌ی هش
  21. تمرین عملی
  22. آزمون ارزیابی
  23. مشاهده‌ی اجرای زنده
  24. خروجی این درس
  25. راستی‌آزمایی
  26. حالت‌های شکست در محیط عملیاتی
  27. قانون عملیاتی
  28. برای مطالعه‌ی بیشتر

هنگام استفاده از ایجنت‌های هوش مصنوعی برای مهندسی معکوس یا بررسی کدهای باینری، یکی از خطاهای رایج «اعتماد کورکورانه به خروجی دیکامپایلر» است. مدل‌های زبانی ممکن است شبه‌کد تولیدشده توسط ابزارهایی مثل Ghidra را به‌جای رفتار قطعی و مشاهده‌شده‌ی کد جا بزنند، فرضیات خود را فکت در نظر بگیرند یا نبود یک تابع را نشانه‌ی عدم وجود آن بدانند.

این درس به شما می‌آموزد که چگونه با پایتون خالص، یک دفتر ثبت شواهد (Evidence Ledger) بسازید. در این سیستم، تمام یافته‌ها دارای شناسه مبتنی بر هش محتوا، سطح اطمینان مشخص (مشاهده‌شده، مشتق‌شده یا استنباط‌شده)، مرجعیت و فهرستی شفاف از محدودیت‌ها هستند تا ایجنت نتواند ادعایی تاییدنشده درباره رفتار یک فایل اجرایی مطرح کند.

یادگیری این ساختار برای هر مهندس معکوس و توسعه‌دهنده‌ای که می‌خواهد گردش‌کارهای خودکار تحلیل باینری را پایدار، قابل راستی‌آزمایی و ضد توهم (Hallucination) کند، کاملاً ضروری است.

یافته‌ای درباره‌ی یک برنامه‌ی کامپایل‌شده، رکوردی است شامل هش، سطح اطمینان، مرجعیت و فهرستی از مواردی که قادر به اثبات آن‌ها نیست.

اهداف یادگیری

  • ساخت رکورد شواهد بر اساس مدل Evidence در rea با سه سطح اطمینانِ مشاهده‌شده (observed)، مشتق‌شده (derived) و استنباط‌شده (inferred).
  • محاسبه‌ی شناسه‌های مبتنی بر هش محتوا (Content-hash IDs) به‌گونه‌ای که با جابه‌جایی فایل به پوشه‌ای دیگر تغییر نکنند.
  • ثبت ناتوانی‌های هر ارائه‌دهنده (Provider) در قالب محدودیت‌هایی که همراه با رکورد باقی می‌مانند.
  • خودداری از اعلام نتیجه تا زمانی که یک اجرای واقعی از تابع، مؤید آن باشد.
  • نگهداری مجهولات باقی‌مانده (Residual unknowns) در قالب رکوردهای نسخه‌بندی‌شده متصل به یک زنجیره‌ی هش.
  • کش‌کردن پاسخ‌های ارائه‌دهنده تحت یک کلید دقیق و رد بازپخش داده‌های منقضی (Stale replay).

صورت مسئله

از یک ایجنت (Agent) می‌پرسید تابع grade در فایل ledger-x86_64-O1.o به ازای نمره‌ی 55 چه مقداری برمی‌گرداند. ایجنت rea را فراخوانی می‌کند، شبه‌کد Ghidra را می‌خواند و پاسخ می‌دهد: 0. بازبین می‌پرسد ایجنت از کجا به این پاسخ رسیده است. ایجنت به یک بلوک متنی استناد می‌کند که Ghidra چاپ کرده است. برای مسیر پیش‌فرض، متن به این صورت است: uVar1 = -(param_1 >> 0x1f) ^ (uint)((ulong)((long)param_1 * 0x66666667) >> 0x3f);. اما بایت‌های برنامه در آن نقطه می‌گویند: xor eax, eax.

آن عبارت محاسباتی و این دستورالعمل اسمبلی، هر دو برای هر ورودی مقدار 0 را تولید می‌کنند. ایجنت این را نمی‌دانست؛ فقط به متن اعتماد کرد چون خروجی یک ابزار بود. پژوهشی در سال 2026 عملکرد سه ایجنت را روی 24 تسک مهندسی معکوس بررسی کرد (NDSS BAR 2026). این مقاله چهار نقطه‌ضعف تکرارشونده را نام برد: سوگیری در آموزش، اعتماد بیش از حد به مشاهدات، محدودیت‌های زمینه (Context limits) و پافشاری روی برنامه‌ی قبلی (Plan persistence). پژوهش دیگری در سال 2026 متونی را درون باینری‌ها کاشت و متوجه شد مدل‌ها در 35 مورد از 40 سناریو، اقدام ناامنِ کاشته‌شده را پیشنهاد داده‌اند (arXiv 2607.12507).

نسخه‌ی 6.5.0 ابزار rea واژگان مشخصی برای رفع این معضل به ایجنت می‌دهد. هر فراخوانی ابزار (Tool call) موفق، یک رکورد Evidence تولید می‌کند که شامل confidence (با مقادیر observed، derived یا inferred)، یک authority (مرجعیت) و فهرستی از limitations (محدودیت‌ها) است. مهارت بسته‌بندی‌شده به ایجنت می‌گوید: «هر نتیجه‌گیری باید مشاهدات، استنباط‌ها و مجهولات را از هم تفکیک کند. به شناسه‌های شواهد ارجاع دهید، محدودیت‌ها و پوشش ناقص را حفظ کنید، و هرگز القا نکنید که تحلیل ایستا توانسته اجرای کد را مشاهده کند.»

اما rea سه کار را بر عهده‌ی خودتان می‌گذارد. اسنپ‌شات‌های (Snapshot) آن نتایج دقیق کوئری را کش می‌کنند، نه تحلیل نهایی را. فرایند اعتبارسنجی آن، ادعایی را در صورتی تأیید می‌کند که بعد مقایسه بدون تغییر مانده باشد؛ با این حال کامنت خود کد اشاره می‌کند که این قبولی «هم‌ارزی سراسری پیاده‌سازی را اثبات نمی‌کند.» دفتر تعهدات آن نیز نامزدها را فقط از گراف‌های جاوااسکریپتی و مدیریت‌شده می‌پذیرد؛ بنابراین یک تابع بومی (Native) هرگز از طریق آن به وضعیت «تأییدشده» (verified) نمی‌رسد.

در این درس، این تکه‌ی گم‌شده را با پایتون خالص پیاده‌سازی می‌کنید. دفتری می‌نویسید که تک‌تک فکت‌های مربوط به این نمونه را در قالب رکوردی با هش محتوا ذخیره می‌کند. درباره‌ی percent_change و grade پیش‌بینی‌هایی ثبت کرده، آن‌ها را با برچسب استنباط‌شده (inferred) مشخص می‌کنید و سپس در برابر اجراهای یک برنامه‌ی آزمایشی که خودتان کامپایل کرده‌اید می‌سنجید. همچنین دو رکورد واقعی از rea و اسنپ‌شاتی را که از آن آمده‌اند وارد می‌کنید و بررسی می‌کنید که دفتر شما، تمام هش‌های تولیدیِ rea را دوباره دقیقاً بازتولید کند.

سه واژه برای سطح اطمینان، پنج واژه برای مرجعیت

فیلد confidence یک نوع شمارشی (Enum) با سه مقدار است، هرگز یک عدد نیست. فیلد authority هم پنج مقدار دارد. هرگاه تولیدکننده چیزی مشخص نکند، rea به طور پیش‌فرض از shipped-artifact همراه با observed استفاده می‌کند.

مقدار معنا روی فایل آزمایشی رکوردها در این درس
observed فکتی خوانده‌شده از روی بایت‌ها یا خروجی یک اجرا بخش‌ها و نمادهای خوانده‌شده با ELF reader، دو رکورد Ghidra، 17 اجرای میزبان
derived فکتی محاسبه‌شده از روی رکوردهای مشاهده‌شده مدل جدول switch، مدل تقسیم، تمام جهش‌های مجهول
inferred انتظاری که هنوز هیچ مشاهده‌ای آن را بیان نکرده است 18 پیش‌بینی
shipped-artifact فایل روی دیسک منبع اصلی بوده است هر بار خواندن ledger-x86_64-O1.o
controlled-replay اجرایی تحت کنترل شما فکت را تولید کرده است اجراهای harness.c روی میزبان
analyst-inference قضاوت و تحلیل خودتان پیش‌بینی‌ها و جهش‌های مجهول
historical-reference درخت سورس‌کد قدیمی‌تر یا یک مستند در این درس موردی نداریم
external-service سرویسی خارج از فایل و سیستم میزبان در این درس موردی نداریم

دو اشتباه بیشترین زمان را تلف می‌کنند. اولی: تلقی شبه‌کد به عنوان مشاهده‌ی رفتار برنامه. شبه‌کد تنها مشاهده‌ای است از آنچه Ghidra چاپ کرده است. دومی: تلقی فهرست خالی به عنوان اثبات نبود یک چیز. یادداشت طراحی rea کاملاً صریح است: «نبود پوشش، مجهول بودن است نه عدم وجود.» دفتری که در code/main.py می‌سازیم، هر دو قانون را پیاده‌سازی می‌کند.

ساختار رکورد

یک رکورد دقیقاً شانزده فیلد دارد و rea 6.5.0 هر فیلد دیگری را رد می‌کند. فایل code/main.py همان نام‌های rea را نگه می‌دارد تا رکوردهای هر دو سمت روی دیگری معتبر باشند. فیلد شانزدهم، relationship_coverage، مشخص می‌کند که فهرست توابع فراخواننده (Callers) یا فراخوانده‌شده (Callees) چقدر کامل است. مقدار آن می‌تواند complete، partial، unknown، یا در صورتی که عملیات هیچ روابط فراخوانی را گزارش نکند، null باشد.

فیلد محتوا
evidence_id عبارت ev_ به همراه 64 رقم هگزادسیمال، یعنی هشی از محتوا
subject مقادیر name، digest.sha256، format، architecture، local_path یا null
provider مقادیر id، name، version یا null
analysis_profile تنظیمات ارائه‌دهنده و خلاصه‌هش (Digest) آن‌ها، یا null؛ همیشه وجود دارد
predicate_type یک برچسب، به‌طور پیش‌فرض rea.analysis
operation، parameters ابزار و ورودی‌های آن
raw_result، normalized_result خروجی خام ارائه‌دهنده و نتیجه‌ی استانداردسازی‌شده
confidence، authority مقادیر شمارشی بالا
environment مقادیر id، platform، architecture، isolation یا null
limitations جملات توصیفی متنی ساده
relationship_coverage وضعیت‌های status شامل complete، partial یا unknown همراه با تشخیص‌ها، یا null
locations مقادیر address، address-range، artifact-path، file-offset، file-offset-range
evidence_links شناسه‌های ev_ که این رکورد بر پایه‌ی آن‌ها بنا شده است

این یک رکورد واقعی است که rea برای تابع grade ثبت کرده، و اینجا فقط فیلدهای مهم آورده شده‌اند. فایل کامل با حجم 37,573 بایت در مسیر code/fixtures/rea-evidence-grade.json قرار دارد.

{
  "evidence_id": "ev_67b6d3f992eaf49a2e16de084b6ebfdd9884e8c5c7f75e175b7bb20f4f748ce1",
  "subject": {
    "name": "ledger-x86_64-O1.o",
    "digest": { "sha256": "9a4a8082ba5e285a732552981a6935e0c5255725664f380e37c44ebfb04d803f" },
    "format": "elf",
    "architecture": "x86_64",
    "local_path": "/private/tmp/aiefs-re/phases/13-tools-and-protocols/35-evidence-confidence-and-unknowns/code/fixtures/ledger-x86_64-O1.o"
  },
  "provider": { "id": "ghidra", "name": "Ghidra", "version": "12.1.4" },
  "analysis_profile": {
    "provider": { "id": "ghidra", "name": "Ghidra", "version": "12.1.4" },
    "parameters": { "jump_table_evidence": "typed-case-default-blocks-v1", "import_mode": "ephemeral-source-immutable" },
    "digest": "9c7f6b24be69294e61da046e4583e3856cb9e40433b149c1bf8bb534d2fe03ef"
  },
  "operation": "analyze_function",
  "parameters": { "procedure": "grade" },
  "confidence": "observed",
  "authority": "shipped-artifact",
  "environment": null,
  "limitations": [
    "Default-space addresses use lowercase 0x-prefixed hexadecimal; other address spaces use <percent-encoded-space>:0x<hex>.",
    "Resolved reference metadata identifies computed, indirect, external, call, jump, and data edges; unresolved targetless flows remain unknown, and function classifications distinguish thunks and externals."
  ],
  "relationship_coverage": {
    "status": "complete",
    "diagnostics": [
      "Only resolved Ghidra call references are returned; unresolved computed or indirect calls remain unknown, while function classifications distinguish thunks and externals."
    ]
  },
  "locations": [],
  "evidence_links": []
}

آبجکت واقعی parameters در پروفایل، 25 کلید دارد. فهرست واقعی limitations نیز شامل ده جمله است؛ آن‌ها را حذف نکنید. محدودیت بخشی از محتوا است؛ بنابراین حذف حتی یکی از آن‌ها شناسه را تغییر می‌دهد و باعث می‌شود دفتر شما رکورد را رد کند.

به آنچه در رکورد وجود ندارد دقت کنید. فیلد environment برابر null است، چون Ghidra فقط فایلی را خوانده و کدی اجرا نکرده است. فیلد locations خالی است، زیرا این عملیات آدرس‌ها را درون normalized_result گزارش می‌کند. وضعیت relationship_coverage برابر complete است. بخش تشخیصی آن توضیح می‌دهد که فقط ارجاعات فراخوانی حل‌شده‌ی Ghidra بازگردانده شده‌اند و فراخوانی‌های محاسبه‌شده یا غیرمستقیم نامشخص، همچنان ناشناخته باقی مانده‌اند. در نتیجه، فهرست فراخواننده‌ها و فراخوانده‌شده‌ها تنها برای فراخوانی‌های صریحاً حل‌شده کامل است.

شناسه، هشی از محتوا است و مسیر فایل در آن نقشی ندارد

ابزار rea شناسه‌ی evidence_id را با پیشوند ev_ و سپس هش SHA-256 روی JSON استانداردسازی‌شده (طبق RFC 8785) از یک تصویر کردن (Projection) رکورد محاسبه می‌کند. این تصویر، مقادیر digest، format و architecture سوژه را نگه می‌دارد و name و local_path را حذف می‌کند. اگر مقدار relationship_coverage برابر null باشد، آن را هم کنار می‌گذارد تا رکوردهای فاقد روابط فراخوانی، شناسه‌ای دقیقاً مشابه قبل از اضافه شدن این فیلد داشته باشند. هر چیز دیگری، از جمله raw_result و تمام محدودیت‌ها، وارد هش می‌شوند.

استاندارد RFC 8785 کلیدهای آبجکت را مرتب می‌کند، فاصله‌های خالی را حذف می‌کند و نمایش اعداد و رشته‌ها را یکدست می‌سازد. برای رکوردهایی که مقادیر عددی آن‌ها عدد صحیح است، کتابخانه‌ی استاندارد پایتون همان بایت‌ها را تولید می‌کند:

def canonical_json(value):
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)

اسکریپت code/main.py از پذیرش اعداد اعشاری قبل از محاسبه‌ی هش خودداری می‌کند؛ زیرا RFC 8785 و پایتون برخی اعشارها را به شیوه‌ای متفاوت چاپ می‌کنند. دو رکورد rea و اسنپ‌شات هیچ عدد اعشاری ندارند، بنابراین این دستورالعمل تمام هش‌های rea را دقیقاً بازتولید می‌کند:

مقدار در rea بازتولیدشده توسط code/main.py
ev_67b6d3f992eaf49a… برای grade برابر
ev_e88b10dc0c6400e5… برای percent_change برابر
خلاصه‌هش پروفایل 9c7f6b24be69294e… روی {provider, parameters} برابر
query_26122aebe9d8abae… برای ورودی اسنپ‌شاتِ grade برابر
query_0f95ef38038d3666… برای ورودی percent_change برابر

چرا مسیر فایل از هش کنار گذاشته می‌شود؟ برای این‌که مشاهده‌ی یکسان از بایت‌های یکسان، روی هر ماشین و درون هر پوشه‌ای شناسه‌ای یکسان بگیرد. مقدار local_path در نمونه‌ی بالا نام پوشه‌ای روی یک سیستم مک است. اگر این فایل را به مسیر home/you/re/ منتقل کنید، شناسه تغییر نمی‌کند. تابع rebind_subject_path() اجازه می‌دهد رکوردی را با مسیری تازه نمایش دهید، اما اگر مقادیر sha256، format یا architecture تفاوت داشته باشند عملیات را رد می‌کند. ابزار rea 6.5.0 نیز زمان بازپخش رکورد اسنپ‌شات برای هدفی در یک مسیر جدید، دقیقاً همین کار را با تابع rebindEvidenceToTargetPath انجام می‌دهد.

تابع validate_evidence() شناسه را مجدداً محاسبه می‌کند و در صورت عدم تطابق، با همان پیام rea آن را رد می‌کند: «Evidence semantic identifier does not match its record». همچنین رکوردی را که در آن فیلد analysis_profile.provider با provider همخوانی نداشته باشد رد می‌کند. تست test_tampered_id_and_mismatched_profile_are_rejected یک خط از شبه‌کد درون رکورد واقعی grade را تغییر می‌دهد و مشاهده می‌کند که دفتر، این رکورد دستکاری‌شده را نمی‌پذیرد.

مشاهده‌ی مستقیم فایل باینری

پیش از فراخوانی هر ارائه‌دهنده‌ای، دفتر ثبت داده‌ها ابتدا فایل را می‌خواند. تابع observe_object() هدرهای بخش‌های ELF64، نمادهای symtab.، strtab. و جابه‌جایی‌های rela.text. را با استفاده از struct.unpack_from تجزیه می‌کند. این تابع یک رکورد inspect_object و برای هر نماد تابع نیز یک رکورد read_function_bytes ثبت می‌کند. هر پنج رکورد دارای وضعیت observed با مرجعیت shipped-artifact هستند، چرا که منبع آن‌ها فایل واقعی روی دیسک بوده است.

رکورد محتوا شناسه
inspect_object پنج بخش همراه با آفست‌ها، اندازه‌ها و هش‌ها؛ چهار تابع؛ سه جابه‌جایی (Relocation) ev_645e0a145f28bd5c…
read_function_bytes percent_change 16 بایت در text+0x00. ev_2572a7d254ad15cb…
read_function_bytes grade 46 بایت در text+0x10. با SHA-256 برابر ffcb75c2a7bb1104… ev_a467c2427bfc0c56…
read_function_bytes total 31 بایت در text+0x40. ev_b1e845cea2543073…
read_function_bytes grade_label 63 بایت در text+0x60. ev_c50041be26e6b7f4…

دستور llvm-objdump -t نیز همین را نشان می‌دهد: تابع grade در آدرس 0000000000000010 با اندازه‌ی 2e. سه جابه‌جایی موجود در rela.text. همگی از نوع R_X86_64_PC32 نسبت به بخش rodata. در آفست‌های 0x36، 0x84 و 0x93 با مقادیر افزوده (Addends) به ترتیب 0x10، 0x24 و 0x4- هستند. هر رکورد، موقعیت مکانی از نوع file-offset-range و یک محدودیت به همراه دارد: «Section bytes are read from the file on disk. The reader applies no relocation and runs no code.»

درس 32 این مفسر را به‌طور کامل می‌سازد. در این درس تنها به 60 خط خلاصه شده، زیرا تمرکز ما روی مواردی است که پس از خواندن فایل باید ثبت شوند.

جدول را مشتق کنید و صریحاً بگویید که آن را مشتق کرده‌اید

بخش 46 بایتیِ تابع grade حاوی چهار الگویی است که در درس 33 رمزگشایی (Decode) کردیم:

بایت‌ها دستورالعمل آفست در grade
48 69 c0 67 66 66 66 imul rax, rax, 0x66666667 3
48 c1 f9 22 sar rcx, 34 13
83 c1 fa add ecx, -6 23
83 f9 04 77 0c cmp ecx, 4 و سپس ja 28

عملیات ضرب در 0x66666667 به همراه شیفت به راستِ 34 بیتی، در واقع تقسیم علامت‌دار بر عدد 10 است. درون فایل هیچ عددی برابر با 10 ذخیره نشده است. تابع derive_grade_model() این واقعیت را به عنوان یک محدودیت ثبت می‌کند: «The divisor 10 is a derivation from the multiplier 0x66666667 and the shift 34. The file holds no 10.»

دستور add ecx, -6 نقطه‌ی شروع شرط را روی عدد 6 تنظیم می‌کند. دستور cmp ecx, 4 همراه با پرش بدون علامت، پنج حالت معتبر یعنی اعداد 6 تا 10 را می‌پذیرد.

دستور lea در text+0x33. حامل جابه‌جایی با مقدار افزوده‌ی 0x10 است. یک جابه‌جایی PC32 از انتهای جابه‌جاییِ 4 بایتی اندازه‌گیری می‌شود؛ بنابراین جدول از rodata+0x14. آغاز خواهد شد. خواندن پنج کلمه (Word) با فرمت اندیان کوچک (Little-endian) در آن محل، مقادیر 1, 2, 3, 4, 4 را نتیجه می‌دهد. بلاک switch در ledger.c مقادیر 1 را برای بازه‌ی 60 تا 69، مقدار 2 را برای دهه‌ی 70، مقدار 3 را برای دهه‌ی 80، و مقدار 4 را برای نمرات 90 تا 100 برمی‌گرداند. کامپایلر، عبارت switch را به یک جدول جستجو (Lookup table) از مقادیر بازگشتی تبدیل کرده است.

رکورد نهایی با شناسه‌ی ev_605106bff2d9998f… دارای سطح اطمینان derived است و فیلد evidence_links آن به دو رکورد مشاهده‌شده‌ی پایه‌ای اشاره دارد. اگر رکورد مربوط به بایت‌ها را از دفتر حذف کنید، رکورد مشتق‌شده پیوندی معلق و ناقص خواهد داشت. تصویر بالا چنین وضعیتی را در صورت غیرفعال کردن ev_a467c242… نشان می‌دهد.

اکنون آن را با خروجی Ghidra مقایسه کنید؛ شبه‌کد تولیدی آن برای بایت‌های یکسان به این صورت است:

uint grade(int param_1)
{
  uint uVar1;
  uint uVar2;

  uVar2 = param_1 / 10 - 6;
  uVar1 = -(param_1 >> 0x1f) ^ (uint)((ulong)((long)param_1 * 0x66666667) >> 0x3f);
  if (uVar2 < 5) {
    uVar1 = *(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4);
  }
  return uVar1;
}

ابزار Ghidra جابه‌جایی را اعمال کرده و جدول را DAT_001000b4 نامیده است (که پس از بارگذاری text. در 0x100000، معادل rodata+0x14. است). این ابزار عمل تقسیم بر 10 و پنج حالت مختلف را بازیابی کرده، اما برای مقدار پیش‌فرض عبارتی را چاپ کرده که گرچه همواره مقدار 0 دارد، اما صراحتاً عدد 0 در آن ذکر نشده است. زبان میانی p-code در native_value_flow، این دست عملیات را با پرچم is_dead: true نشانه‌گذاری می‌کند. این رکورد، یک مشاهده‌ی صادقانه از خروجی Ghidra است. اما معنایی که از آن برداشت می‌کنید «مشتق‌شده» (derived) است، و مقداری که از اجرای آن انتظار دارید «استنباط‌شده» (inferred).

رکورد percent_change نوع دیگری از نقص را آشکار می‌کند. ساختار تابع خروجی Ghidra این‌گونه است: undefined1 [16] percent_change(int param_1,int param_2,ulong param_3). در حالی که سورس اصلی تابع می‌گوید: int percent_change(int before, int after). از آنجا که idiv رجیسترهای EDX:EAX را بازنویسی می‌کند و مسیر صفر رجیستر EDX را دست‌نخورده باقی می‌گذارد، دیکامپایلر خروجی را 16 بایتی فرض کرده و پارامتر سومی را به تابع نسبت داده است. فیلد native_api.return_type.confidence در خود Ghidra برای تابع grade مقدار "low" دارد. بنابراین امضای بازیابی‌شده را همواره استنباطِ ارائه‌دهنده بدانید و آن را دقیقاً به همین صورت ثبت کنید.

پیش‌بینی‌ها، رکوردهای استنباط‌شده هستند

پیش‌بینی، ادعایی درباره‌ی رفتار است که تا این لحظه هیچ مشاهده‌ای مستقیماً آن را بیان نکرده است. تابع claim_suite() تعداد 18 مورد از این ادعاها را با تکیه بر مدل‌های مشتق‌شده و سورس‌کد تولید می‌کند. تابع record_prediction() هر کدام را در قالب رکوردی با سطح اطمینان inferred و مرجعیت analyst-inference ذخیره کرده و به رکوردهای پایه‌ای مرتبط متصل می‌سازد.

ادعا مقدار پیش‌بینی‌شده متکی بر
percent_change(12400, 15500) 25، حاصل از 310000 / 12400 مدل تقسیم ev_076a9167…
percent_change(0, 5) 0، گارد بررسی صفر مدل تقسیم
percent_change(100, 50) -50 مدل تقسیم
percent_change(-7, 3) -142، زیرا idiv به سمت صفر گرد می‌کند مدل تقسیم
grade(55) 0، اندیس 1- در بررسی بدون علامت رد می‌شود مدل جدول ev_605106bf…
grade(60)، grade(69) 1، 1 مدل جدول
grade(70)، grade(85) 2، 3 مدل جدول
grade(95)، grade(100)، grade(105) 4، 4، 4 مدل جدول
grade(110)، grade(-5) 0، 0 مدل جدول
total(1, 2, 3)، total() 6، 0 رکورد سورس‌کد ev_2f1e6d25…
grade_label(85) "B" رکورد سورس‌کد و مدل جدول
grade(score) به ازای نمرات 90 تا 100 4 به ازای تمام یازده ورودی مدل جدول

تابع predict() از قواعد معنایی زبان C پیروی می‌کند نه پایتون. تابع c_div() اعداد را به سمت صفر گرد می‌کند و wrap32() همه‌ی محاسبات میانی را به محدوده‌ی 32 بیتی می‌برد. عملگر // در پایتون به سمت منفی بی‌نهایت گرد می‌کند؛ از این رو عبارت 7- // 100 * (7- - 3) در پایتون مقدار 143- و در برنامه‌ی C مقدار 142- را می‌دهد. در درس 36 این شبکه مقادیر را بسیار گسترده‌تر خواهیم کرد.

هر پیش‌بینی، یک مجهول باقی‌مانده‌ی جدید ایجاد می‌کند. این مجهول، به دنبال شواهدی از نوع observed با مرجعیت controlled-replay می‌گردد و یک بررسی تجربی (Probe) پیشنهاد می‌دهد: فراخوانی execute_function روی بیلد سیستم میزبان. بخش بعدی این شواهد را تولید می‌کند. پرسشِ مطرح‌شده برای نخستین ادعای مربوط به نمره این است: «آیا هنگام اجرای تابع کامپایل‌شده، گزاره‌ی grade(95) == 4 برقرار است؟»

راستی‌آزمایی در برابر یک اجرا

فایل code/fixtures/harness.c سورس ledger.c را اضافه (include) کرده و به ازای هر فراخوانی یک نتیجه چاپ می‌کند. تابع observe_runs() آن را با اولین کامپایلر در دسترس از میان clang، cc یا gcc کامپایل می‌کند و تمام ورودی‌های ماتریس را یک بار اجرا می‌کند. هر اجرا به یک رکورد تبدیل می‌شود:

{
  "operation": "execute_function",
  "parameters": { "function": "grade", "args": [95] },
  "normalized_result": { "result": 4, "stdout": "4\n", "exit_code": 0 },
  "confidence": "observed",
  "authority": "controlled-replay",
  "environment": { "id": "host-process", "platform": "darwin", "architecture": "arm64", "isolation": "process" },
  "limitations": [
    "The run executed a host build of ledger.c, not the x86-64 object. The claim about the object rests on the shared source and on the compiler.",
    "One run observes one input. It proves nothing about other inputs."
  ]
}

سوژه‌ی رکورد اجرا، فایل باینری تست یعنی یک فایل Mach-O معماری arm64 روی این سیستم مک است که هش SHA-256 مخصوص به خود را دارد. ارائه‌دهنده، course-harness به همراه رشته‌ی مشخصات نگارش کامپایلر است؛ اینجا: Apple clang version 17.0.0 (clang-1700.6.3.2). فیلد evidence_links به رکورد سورس‌کد ev_2f1e6d251543d017… اشاره دارد؛ چرا که تنها نقطه‌ی اشتراک میان فایل شیء و این اجرا، فایل ledger.c بوده و نه هیچ چیز دیگر.

محدودیت اول را با دقت بخوانید: آبجکت x86-64 هرگز روی این سیستم اجرا نشده است. آنچه اجرا شد، بیلد دومی از همان سورس‌کد اما برای معماری متفاوتی بود. این باز هم یک مشاهده محسوب می‌شود و در غیاب شبیه‌ساز، معتبرترین شواهد ممکن است؛ بنابراین ادعا به شرط الصاق این توضیح پذیرفته می‌شود. گزارشی که این نکته را از قلم بیندازد، اطلاعات حیاتی مورد نیاز مخاطب را حذف کرده است.

تمام 17 اجرای تک‌ورودی دقیقاً با پیش‌بینی‌ها همخوانی داشتند. فایل code/fixtures/run-evidence.json این 17 رکورد و رکورد سورس را در بر می‌گیرد که با پرچم record-- در تاریخ 2026-10-13 خروجی گرفته شده‌اند. اگر کامپایلری در دسترس نباشد، build_ledger() به جای اجرای زنده، این بسته را وارد می‌کند و در فیلد runs گزارش، این موضوع را قید می‌کند.

قوانین ارتقا

یک ادعا تنها در صورتی از وضعیت استنباط‌شده به پشتیبانی‌شده ارتقا می‌یابد که اجراهای مشاهده‌شده آن را تأیید کنند. تابع check_claim() به دنبال رکوردهای execute_function با تابع و آرگومان‌های یکسان می‌گردد، هر نتیجه را با پیش‌بینی می‌سنجد و تعداد ورودی‌های پوشش‌داده‌شده را می‌شمارد:

وضعیت شرط دفتر اتمام rea
pass تمام ورودی‌ها دارای اجرای منطبق باشند و هیچ مغایرتی نباشد pass
fail حتی یکی از اجراها مغایرت داشته باشد fail
unknown برخی ورودی‌ها دارای اجرا باشند و بقیه بدون اجرا unknown
unsupported هیچ ورودی‌ای سابقه‌ی اجرا نداشته باشد unsupported

تابع promote_claim() فرم سخت‌گیرانه‌تر این فرایند است. این تابع در صورتی که هر شناسه‌ای از نوع inferred یا derived باشد خطای ClaimError صادر می‌کند. اگر شناسه به اجرای تابع دیگری یا آرگومان‌های متفاوتی اشاره داشته باشد خطا می‌دهد. همچنین در صورتی که فهرست خالی باشد یا ورودی‌های پوشش‌داده‌شده کمتر از موارد مطرح‌شده در ادعا باشند نیز خطا تولید می‌کند. رکورد شبه‌کد Ghidra و مدل جدول، هر دو برای grade(55) موجودند، ولی هیچ‌کدام توانایی ارتقای ادعا را ندارند. در تصویر بالا، اگر ev_7be90c0c… را غیرفعال کنید، با وجود باقی ماندن دو رکورد پیرامون grade، ادعا بلافاصله به وضعیت unsupported سقوط می‌کند.

ادعای مربوط به بازه‌ی نمرات، حالت پوشش ناکامل را به خوبی نشان می‌دهد. از میان یازده ورودی آن، تنها دو مورد 95 و 100 در ماتریس تست وجود دارند. تابع check_claim() وضعیت unknown با پوشش 2 از 11 ورودی را برمی‌گرداند، و promote_claim() با پیام «2 of 11 inputs have observed runs» درخواست را رد می‌کند. دفتر، هرگز این مقادیر را به بالا گرد نمی‌کند.

خروجی completion_ledger() معادل EvidenceCompletionLedger در ابزار rea است. شناسه‌ی آن با ecl_ شروع شده و از هش رکوردهایش به دست می‌آید؛ فیلد complete نیز تنها در شرایطی true می‌شود که تعداد pass با مقدار total برابر باشد. خروجی تست، شناسه‌ی ecl_da64933ea13befed… را با مقدار complete: false نمایش می‌دهد، چون همان یک ادعای بازه‌ای پوشش کامل نگرفته است.

مجهولات، رکوردهایی دارای زنجیره‌ی هش هستند

ابزار rea مجهولات باقی‌مانده را در قالب یک رکورد بسیار دقیق ذخیره می‌کند. اسکریپت code/main.py دقیقاً همین فیلدها و شروط تغییرناپذیر (Invariants) را حفظ می‌کند.

شناسه با unk_ و هش SHA-256 روی {domain, question, scope_digest} ساخته می‌شود، به‌طوری که فاصله‌های مکررِ موجود در پرسش، پیش از هش شدن به یک فاصله تبدیل می‌شوند. به این ترتیب، دو تحلیل‌گر که پرسشی یکسان درباره‌ی فایلی یکسان بپرسند، شناسه‌ای دقیقاً مشابه به دست خواهند آورد. فیلد scope_digest همان هش SHA-256 فایل باینری است.

هر ویرایش یا تغییر، یک نسخه‌ی (Revision) تازه ایجاد می‌کند. نسخه 1 مقدار previous_revision_digest را برابر null دارد. هر نسخه‌ی بعدی، مقدار revision_digest نسخه‌ی ماقبل خود را قید می‌کند؛ هشی که تمامی فیلدها به جز خودش را پوشش می‌دهد. تابع verify_unknown_chain() لیست را بررسی کرده و هش هر نسخه را بازتولید می‌کند. این تابع کنترل می‌کند که هر پیوند به نسخه‌ی قبلی اشاره کند و بررسی می‌کند شواهد جهشی که در هر نسخه ارجاع داده شده، حتماً درون دفتر ثبت شده باشند.

هر جهش (Mutation) خود یک رکورد شواهد جداگانه است، درست مشابه کاری که rea با ارائه‌دهنده‌ی rea-unknown-registry انجام می‌دهد. رکورد ما حاوی سطح اطمینان derived، مرجعیت analyst-inference و فاقد سوژه (Subject) است. محدودیت آن تصریح می‌کند: «A registry mutation records what the analyst decided. It is not proof of the answer.»

به همین علت، از میان 81 رکورد نسخه‌ی نمایشی، 36 مورد رکوردهای جهش مشتق‌شده هستند: 18 مورد برای باز کردن مجهول و 18 مورد برای به‌روزرسانی آن.

سه شرط تغییرناپذیر در تک‌تک نسخه‌ها اعمال می‌شوند:

  • تنها یک مجهول با وضعیت resolved حق داشتن فیلد resolution را دارد، و فقط نسخه 1 می‌تواند فاقد سلف (Predecessor) باشد.
  • یک مجهول با وضعیت contradicted باید حداقل یک شناسه‌ی متناقض معرفی کند، و هیچ شناسه‌ای نباید هم‌زمان در هر دو فهرست تأیید و تناقض قرار بگیرد.
  • یک راه‌حل با وضعیت verified باید حداقل به یک شناسه با سطح اطمینان و مرجعیت معتبر اشاره کند.

رد کردن درخواست‌ها توسط دفتر، اغلب بر سر همین قانون سوم اتفاق می‌افتد. مستندات کاری rea می‌گویند: «تنها شواهد مشاهده‌شده‌ی واجد شرایط می‌توانند یک نتیجه‌گیری را تأیید کنند.» از این رو، تابع resolve_unknown() مدل جدول را رد می‌کند، رکورد مشاهده‌شده با مرجعیت shipped-artifact را نمی‌پذیرد، اما رکورد اجرای ev_f4ffbe8f922ec7fe… را قبول می‌کند. نسخه‌ی حل‌شده برای grade(95) نسخه‌ی 2 است و فیلد previous_revision_digest آن روی ffca840b55a8a188… یعنی خلاصه‌هش نسخه 1 تنظیم شده است.

تابع update_unknown() آرگومان expected_revision را بررسی می‌کند و چنانچه داده منقضی شده باشد، مانند ابزار update_unknown در rea خطای revision-conflict می‌دهد. واکنش صحیح در چنین شرایطی همان توصیه‌ی مستندات rea است: نسخه‌ی فعلی را دوباره بخوانید و از نو تصمیم‌گیری کنید.

پس از پایان تست، یک مجهول باز می‌ماند: unk_f2eb0d7f84c80a19… با پرسش «Does grade(score) == 4 for every score from 90 to 100 hold when the compiled function runs?». وضعیت آن همچنان investigating است، شناسه‌های پشتیبان شامل دو اجرا می‌شوند، و پروب آن کماکان نام آن نه ورودیِ بدون تست را یدک می‌کشد.

کش پاسخ‌ها با پایگاه‌داده‌ی تحلیل تفاوت دارد

ابزار rea دارای سه ساختار با ماهیت شبیه به حافظه‌ی موقت (Cache) است: اسنپ‌شاتِ هدف Ghidra کپی اختصاصی از ورودی است که با آرگومان‌های readOnly -deleteProject- وارد شده و با بسته شدن برنامه حذف می‌گردد؛ حافظه‌ی موقت AnalysisSnapshotCache تا 10,000 ورودی را برای هویت یک باینری در رم نگه می‌دارد؛ و فایل اسنپ‌شات که شامل بخش‌های target، binding، entries، workflow_entries و یک evidence_bundle است.

کلید کش با query_ و سپس هش SHA-256 روی {target, binding, operation, parameters} ساخته می‌شود. فیلد target شامل {sha256, kind, format, architecture} است و برای ورودی‌های raw-binary فیلد entry_point را نیز در بر می‌گیرد. فیلد binding شامل ارائه‌دهنده و کل پروفایل تحلیل همراه با خلاصه‌ی هش آن است. اگر هر یک از 25 پارامتر پروفایل تغییر کند، مثلاً پارامتر jump_table_evidence از نگارش typed-case-default-blocks-v1 به v2 ارتقا یابد، مقدار خلاصه‌هش تغییر می‌کند و تمامی کلیدها از درجه اعتبار ساقط می‌شوند.

فایل code/fixtures/rea-snapshot.json فایلی به حجم 142,690 بایت است که rea حین دو اجرای قبلی ثبت کرده است. این فایل با مجوزهای دسترسی محدود به مالک ذخیره شده بود، اما کپی نهایی در مخزن برای همه قابل خواندن است. این اسنپ‌شات دارای دو ورودی و یک بسته‌ی شواهد با دو رکورد یکسان است. متد AnswerCache.import_snapshot() تمام مقادیر query_id را دوباره محاسبه کرده و در صورت بروز کوچک‌ترین مغایرت، فایل را رد می‌کند. اگر مشخصات target یا binding با پروفایل فعال ناهمخوان باشد، فایل با خطای استاندارد rea یعنی profile_mismatch کنار گذاشته می‌شود. همچنین اگر format_version با مقدار course-answer-cache-1 مطابقت نداشته باشد پذیرفته نخواهد شد.

سه نتیجه‌ی اندازه‌گیری‌شده روی این سیستم: اولین پرس‌وجوی تابع grade همراه با یک ایمپورت تازه در Ghidra معادل 8.6 ثانیه طول کشید. پرس‌وجوی percent_change روی همان اسنپ‌شات 8.5 ثانیه زمان برد؛ چون یک کوئریِ متفاوت دچار خطای نبود در کش (Cache miss) می‌شود. سومین کوئری برای grade تنها 2.1 ثانیه طول کشید و رکوردی کاملاً یکسان با همان evidence_id برگرداند. فرایند بازپخش از روی کش در حافظه درون rea، این محدودیت را اضافه می‌کند: «Loaded from a local REA analysis snapshot; this call did not re-run the provider.» تابع lookup() ما نیز جمله‌ای مشابه اضافه می‌کند تا کاربر بتواند پاسخ بازیابی‌شده را از یک خروجی تازه و زنده تشخیص دهد.

هرگونه جهش، کار کش را تمام می‌کند. در rea 6.5.0 تغییر نام یا ثبت کامنت، کش اسنپ‌شات را پیش از ارسال درخواست به ارائه‌دهنده به طور کامل پاک می‌کند. یک پاسخ ناموفق نمی‌تواند ثابت کند که تغییرات واگردانی (Rollback) شده‌اند، بنابراین نوشتن ناموفق نیز بازپخش را متوقف می‌کند. وارد کردن متاداده‌ای که دیتابیس را دچار تغییر کند نیز اثر مشابهی دارد.

چرا ایجنت به چنین ساختاری نیاز دارد

ایجنتی که rea را از بستر MCP کنترل می‌کند، در هر فراخوانی رکورد کامل را می‌بیند و قادر است در گزارش خود به evidence_id استناد کند. پژوهش‌های سال 2026 دقیقاً نشان می‌دهند اشکال کار از کجاست: اعتماد بیش از حد به خروجی ابزارها، یکی از چهار نقطه‌ضعف اصلی در پژوهش NDSS BAR بود. در مقاله‌ی مربوط به کدهای کاشته‌شده نیز حتی با نمایش مستقیم محتوای باینری صرفاً به عنوان داده، کماکان 15 پیشنهاد ناامن از 40 سناریو باقی ماند؛ و تنها بررسی‌های منشأ و سندیت (Provenance) به همراه اعتبارسنجی ابزار توانست این خطاها را حذف کند. در پژوهش سوم، ایجنتی تحت بستر MCP بر Ghidra نظارت داشت (arXiv 2606.06838). در باینری‌هایی با صدها تابع، ایجنت تنها 10 تا 15 درصد از توابع را به شکل واقعی تحلیل کرد و برای مابقی فقط استاب (Stub) تحویل داد.

دفتر شواهد با ثبت رکوردها به این ابهامات پاسخ می‌دهد و ایجنت را ملزم می‌سازد که تنها بر پایه‌ی دارایی‌های دفتر گزارش بنویسد. ادعایی بدون اجرای مشاهده‌شده، برچسب unsupported می‌خورد؛ ادعایی با اجراهای ناکافی، unknown تلقی می‌شود؛ و خروجی یک ارائه‌دهنده تنها به عنوان داده‌ای observed از همان ابزار ثبت می‌شود و نه بیشتر. فایل مهارت موجود در outputs/ را در اختیار ایجنت قرار دهید و این دفتر را تنها مرجع نوشتن نتیجه‌گیری‌های آن تعیین کنید. این قاعده برای Claude، Cursor، Codex، OpenClaw، Hermes و هر ایجنت دیگری که SKILL.md را بخواند صادق است.

پیاده‌سازی

اجرای دمو با کتابخانه‌ی استاندارد پایتون:

cd phases/13-tools-and-protocols/35-evidence-confidence-and-unknowns
python3 code/main.py

این برنامه ابتدا فایل باینری را می‌خواند، دو رکورد rea را ایمپورت می‌کند، اسنپ‌شات rea را می‌سنجد و دو مدل مشتق‌شده را به دست می‌آورد. سپس 18 پیش‌بینی و 18 مجهول ثبت می‌کند، برنامه‌ی تست را کامپایل کرده و به اجرا درمی‌آورد، تک‌تک ادعاها را بررسی می‌کند و سرانجام 17 مورد از مجهولات را به وضعیت حل‌شده می‌رساند. خروجی مورد انتظار:

{
  "records": 81,
  "by_confidence": { "observed": 25, "derived": 38, "inferred": 18 },
  "rea_snapshot": { "entries": 2, "replay_hit": true, "stale_profile_refused": true },
  "grade_table": [1, 2, 3, 4, 4],
  "runs": "live: Apple clang version 17.0.0 (clang-1700.6.3.2)",
  "claims": [ { "claim_id": "grade(95)", "status": "pass", "covered": "1/1" }, { "claim_id": "grade(90..100)", "status": "unknown", "covered": "2/11" } ],
  "completion_ledger_id": "ecl_da64933ea13befed9483f8b00447bb3f4ceb74324c01e2515eff03f3af3ca750",
  "complete": false,
  "unknowns": { "resolved": 17, "investigating": 1 }
}

در صورت نبود کامپایلر C، عبارت روبروی runs به صورت imported 17 recorded run records from run-evidence.json نمایش داده خواهد شد و باقی مراحل تفاوتی نخواهد داشت.

فایل code/main.py را با این ترتیب مطالعه کنید:

  1. توابع canonical_json() و compute_evidence_id() شامل دستورالعمل هش و تصویر کردن معنایی.
  2. توابع create_evidence() و validate_evidence() برای ساخت و ارزیابی رکوردها.
  3. متدهای EvidenceLedger.record() و import_bundle() جهت ذخیره‌ی شواهد و ممانعت از تعارض‌ها.
  4. توابع open_unknown()، update_unknown() و resolve_unknown() برای مدیریت زنجیره‌ی نسخه‌بندی.
  5. تابع observe_object() برای خواندن ELF و derive_grade_model() برای خواندن جدول.
  6. توابع claim_suite()، predict() و record_prediction() جهت مدیریت لایه‌ی استنباط‌ها.
  7. تابع observe_runs() برای تبدیل هر اجرای تستی به رکوردی مشاهده‌شده.
  8. توابع check_claim()، promote_claim() و completion_ledger() برای پیاده‌سازی قوانین ارتقا.
  9. ساختار AnswerCache برای کلیدگذاری پاسخ‌ها مشابه اسنپ‌شات‌های rea و رد موارد منقضی.

استفاده از آن

به ازای هر هدف و هر بررسی، یک دفتر جداگانه باز کنید. هش SHA-256 هدف را در بخش موضوع هر رکورد و هش دامنه‌ی (Scope) پرسش را درون هر مجهول قرار دهید. چنانچه ابزار را عوض کردید، شناسه و نام provider را نیز تغییر دهید تا هش شناسه‌ها متمایز شوند. با بازکامپایل باینری، خلاصه‌ی سوژه تغییر خواهد کرد و تمام رکوردهای قدیمی به شواهدی برای فایلی متفاوت بدل خواهند شد.

محدودیت‌ها را دقیقاً در همان لحظه‌ی کشف ثبت کنید. خواننده‌ای که جابه‌جایی آدرس‌ها را اعمال نمی‌کند، باید فوراً این موضوع را بنویسد. اجرای فایل روی سیستم میزبان باید اعلام شود. مدلی که مقسوم‌علیه را استنباط می‌کند، موظف به ثبت آن است. رکوردی که بدون ذکر محدودیت‌هایش نوشته شود، قوی‌تر از واقعیت موجود جلوه می‌کند؛ نقصی که هیچ خواننده‌ای در آینده قادر به جبران آن نخواهد بود.

متون خروجی ابزارها را همیشه صرفاً به دیدِ «داده» نگاه کنید. رشته‌ها و شبه‌کدهایی که از فایل هدف حاصل می‌شوند، محتوایی تحت کنترل باینری هدف هستند. این بخش‌ها هرگز نباید به عنوان دستورالعمل اجرایی در نظر گرفته شوند و مشاهداتی از رفتار به حساب نمی‌آیند. نسخه‌ی 6.5.0 ابزار rea چنین خروجی‌هایی را با فیلد _meta.provenance.untrusted_content: true نشانه‌گذاری می‌کند. دستورالعمل‌های ارائه‌شده به سرور از مدل می‌خواهند که محتوای فایل هدف را تنها یک داده بداند و هرگونه درخواست درون آن برای دانلود، اجرا، نصب یا ارسال محتوا را مصداق حمله‌ی تزریق پرامپت (Prompt injection) تلقی کند. اگرچه وجود این نشانه‌ها مفید است، اما وظیفه‌ی دفتر شماست که مطمئن شود متون هدف هرگز وارد بخش دستورالعمل‌ها نشوند.

کارگاه تعاملی

بخش اول: محاسبه‌ی مجدد شناسه rea

از پوشه‌ی code، محیط پایتون را اجرا کنید:

cd phases/13-tools-and-protocols/35-evidence-confidence-and-unknowns/code
python3 -q

کدها را اجرا کنید:

from main import *
record = load_evidence_file(FIXTURES / "rea-evidence-grade.json")
record["evidence_id"] == compute_evidence_id(record)
record["subject"]["local_path"] = "/home/you/re/ledger-x86_64-O1.o"
record["evidence_id"] == compute_evidence_id(record)
record["limitations"].pop()
record["evidence_id"] == compute_evidence_id(record)

دو مقایسه‌ی اول صحیح هستند؛ زیرا مسیر فایل در هش نقشی ندارد. مقایسه‌ی سوم نادرست است، چون محدودیت جزیی از محتوای رکورد است.

بخش دوم: رد یک ارتقا

ledger, report = build_ledger(live_runs=False)
claim = [c for c in claim_suite(ledger.find(operation="derive_switch_model")[0], ledger.find(operation="derive_arithmetic_model")[0], ledger.find(operation="read_source")[0]) if c.claim_id == "grade(55)"][0]
promote_claim(ledger, claim, [ledger.find(operation="derive_switch_model")[0]["evidence_id"]])

این فراخوانی خطای ClaimError صادر کرده و شناسه‌ی داده‌ی مشتق‌شده را گزارش می‌کند. اکنون شناسه‌ی رکورد اجرا را به آن بدهید:

run = run_records_for(ledger, "grade", [55])[0]
promote_claim(ledger, claim, [run["evidence_id"]])["status"]

وضعیت به pass تغییر کرده و فیلد limitations بازگردانده‌شده، حاوی عبارت مربوط به اجرای بیلد میزبان است.

بخش سوم: رد اسنپ‌شات منقضی

import json
snapshot = json.loads(REA_SNAPSHOT_PATH.read_text())
target, binding = rea_snapshot_binding(snapshot)
AnswerCache(target, binding).import_snapshot(snapshot)
stale = dict(binding["analysis_profile"]["parameters"], jump_table_evidence="typed-case-default-blocks-v2")
AnswerCache(target, {"provider": binding["provider"], "analysis_profile": make_profile(binding["provider"], stale)}).import_snapshot(snapshot)

ایمپورت نخست مقدار 2 را برمی‌گرداند. دومی خطای CacheError با پیام profile_mismatch تولید می‌کند؛ چرا که تغییر حتی یک رشته‌ی نگارش، موجب رد کل اسنپ‌شات می‌شود.

بخش چهارم: شکستن زنجیره‌ی هش

unknown_id = [u for u, r in ledger.unknowns.items() if r[-1]["status"] == "investigating"][0]
ledger.verify_unknown_chain(unknown_id)
ledger.unknowns[unknown_id][0]["severity"] = "critical"
ledger.verify_unknown_chain(unknown_id)

فراخوانی اول مقدار 2 را بازمی‌گرداند. دومی با خطای EvidenceIntegrityError متوقف می‌شود، چون نسخه 1 دیگر با خلاصه‌هش خودش تطابق ندارد و نسخه 2 نیز به هشی ناموجود ارجاع داده است.

تمرین عملی

مجهول بازمانده را ببندید و ابزار ارائه‌دهنده‌ی دوم را اضافه کنید.

الزامات:

  • تابع run_grid() را طوری توسعه دهید که نه ورودیِ باقی‌مانده از ادعای بازه‌ای نیز اجرا شوند، و سپس اطمینان یابید که مجهول در نسخه‌ی 3 به وضعیت resolved برسد.
  • دستور llvm-objdump -d را در قالب یک ارائه‌دهنده‌ی مستقل با آبجکت provider مجزا اضافه کرده و خروجی دیس‌اسمبلی تابع grade را با وضعیت observed ثبت کنید.
  • رکوردی از نوع derived بسازید که مشخص کند خروجی Ghidra و llvm-objdump، هر دو 14 دستورالعمل کاملاً یکسان را رمزگشایی (Decode) کرده‌اند؛ و آن را به هر دو رکورد پیوند دهید.
  • برای فراخوانی grade_label(105) با سطح اولویت high یک مجهول جدید باز کنید (زیرا اندیس 4 جدول، آرایه‌ی labels[4] را می‌خواند)، و با اجرای یک تست آن را برطرف کنید.
  • دفتر را با دستور export_bundle() خروجی بگیرید، آن را در یک دفتر تازه ایمپورت کنید و ثابت کنید تمامی زنجیره‌ها همچنان معتبر هستند.

موفقیت زمانی حاصل می‌شود که تمام جملات گزارش، به شناسه‌ای از این بسته استناد کرده باشند و آن یک ادعای unknown کاملاً برطرف شده باشد.

آزمون ارزیابی

پنج پرسش درباره‌ی فایل‌های نمونه‌ی این درس مطرح شده است. پاسخ‌ها را از طریق کدی که نوشته‌اید استخراج کرده و در فایل code/lab_answers.json وارد کنید. مقادیر عددی را به شکل عدد یا رشته‌های هگزادسیمال مانند "0x40" بنویسید و فهرست‌ها را در قالب لیست‌های JSON وارد کنید.

  1. t1: شناسه‌ی شواهدی که تابع compute_evidence_id برای فایل rea-evidence-grade.json تولید می‌کند.
  2. t2: تعداد محدودیت‌های موجود در رکورد مربوط به grade.
  3. t3: وضعیت فیلد relationship_coverage در رکورد grade.
  4. t4: سطح اطمینان و مرجعیت رکورد grade، در قالب آرایه‌ای شامل دو رشته متنی.
  5. t5: مقدار هش SHA-256 سوژه‌ی رکورد grade.

پاسخ‌های خود را بیازمایید:

cd phases/13-tools-and-protocols/35-evidence-confidence-and-unknowns
python3 code/lab.py

اسکریپت ارزیابی، پاسخ‌های مورد انتظار را از روی code/main.py و فایل‌های نمونه‌ی موجود محاسبه می‌کند. این برنامه به ازای هر بخش، وضعیت‌های PASS، FAIL یا TODO و در نهایت امتیاز شما را نمایش می‌دهد، و تنها زمانی خروجی صفر می‌دهد که هر پنج سؤال درست باشند. اجرای دستور python3 code/lab.py my-answers.json امکان بررسی فایلی دیگر را فراهم می‌سازد. فایل code/tests/test_lab.py نشان می‌دهد که سیستم، پاسخ‌های درست را تأیید و موارد اشتباه را رد می‌کند.

مشاهده‌ی اجرای زنده

محیط تعاملی Inside a Decompiler دفتری از شواهد با چهار دسته‌بندی نگه می‌دارد: مشاهده‌شده، مشتق‌شده، استنباط‌شده و مجهول. این لینک، برنامه را در وضعیتی باز می‌کند که جدول نمادها خاموش است؛ بنابراین نام و انتهای تابع در قالب مجهول، و ابتدای آن به صورت استنباط‌شده ثبت می‌شود.

خروجی این درس

حاصل این درس فایل outputs/skill-evidence-ledger.md است. این مستند به ایجنت می‌آموزد که چگونه یافته‌ها را در ساختار رکوردهایی حاوی سطح اطمینان، مرجعیت، محدودیت‌ها و پیوندها مستند کند. همچنین نحوه‌ی ثبت مجهولات در قالب رکوردهای نسخه‌بندی‌شده و چگونگی تشخیص وضعیت‌های ادعا اعم از pass، fail، unknown یا unsupported را به ایجنت آموزش می‌دهد.

راستی‌آزمایی

برنامه‌ی دمو و مجموعه تست‌های قطعی را اجرا کنید:

cd phases/13-tools-and-protocols/35-evidence-confidence-and-unknowns
python3 code/main.py
python3 -m unittest discover -s code/tests -v

این 28 تست نشان می‌دهند:

  • شناسه‌های evidence_id در هر دو رکورد rea، خلاصه‌هش پروفایل و شناسه‌های query_id در هر دو اسنپ‌شات تماماً بر پایه‌ی محتوا بازتولید می‌شوند.
  • جابه‌جایی مسیر تاثیری در شناسه ندارد، اما تغییر محدودیت شناسه را عوض می‌کند.
  • رکوردهای دستکاری‌شده، ارائه‌دهنده‌ی متناقض با پروفایل، ورودی‌های اعشاری و مقادیر شمارشی ناشناخته رد می‌شوند.
  • افزودن رکورد تکراری وضعیت duplicate را برمی‌گرداند، رکورد متناقض خطا صادر می‌کند، و وارد کردن بسته‌ها اتمیک است.
  • بسته‌ی اجراهای ثبت‌شده شامل 18 رکورد بدون هیچ پیوند معلقی وارد می‌شود.
  • مجهول در نسخه‌ی 1 باز می‌شود، مقدار منقضی expected_revision را رد می‌کند، و تنها با شواهد مشاهده‌شده از نوع controlled-replay حل می‌شود.
  • تغییر در یک نسخه، زنجیره را می‌شکند.
  • ارتقای ادعا برای شواهد استنباط‌شده، مشتق‌شده، خالی یا غیرمرتبط رد می‌شود و تنها با یک اجرای تأییدکننده می‌گذرد.
  • پوشش ناکامل به وضعیت unknown و اجرای دارای مغایرت به وضعیت fail می‌انجامد.
  • کش روی کلید دقیق پاسخ مثبت می‌دهد و با تغییر پارامترها پاسخ نمی‌دهد.
  • افزایش نگارش پروفایل، هدف اشتباه، نگارش نادرست ساختار یا کلید دستکاری‌شده، از سوی کش رد می‌شوند.
  • خروجی مفسر ELF با دستور llvm-objdump در اندازه‌ها، آفست‌ها، بایت‌ها و جابه‌جایی‌ها مطابقت دارد و جدول مقادیر 1, 2, 3, 4, 4 را بازمی‌گرداند.
  • پیش‌بینی‌ها از تقسیم با منطق C استفاده می‌کنند و تست‌های ضبط‌شده با تمام 17 ادعای تک‌ورودی تطابق دارند.
  • در حضور کامپایلر، اجراهای زنده نیز همگی تأیید می‌شوند.

حالت‌های شکست در محیط عملیاتی

خطا متن یک گزارش ضعیف الزام دفتر شواهد
تلقی شبه‌کد به عنوان رفتار واقعی «تابع grade برای نمرات مردود مقدار 0 می‌دهد» ثبت یک رکورد execute_function برای آن ورودی یا برچسب unsupported
اعتماد کامل به ساختار ارائه‌شده از دیکامپایلر «تابع percent_change خروجی 16 بایتی دارد» ثبت ساختار تابع به عنوان استنباط ارائه‌دهنده با قید سطح اطمینان low
تلقی لیست خالی به عنوان نبود چیزی «این تابع هیچ فراخواننده‌ای ندارد» وضعیت unknown یا پر کردن فیلد پوشش در صورت گزارش ابزار
افزودن مسیر فایل به هش «رکورد تغییر یافت» پس از کپی فایل تصویر کردن معنایی بدون فیلدهای name و local_path
حذف محدودیت‌ها حین کپی رکوردی که قوی‌تر از واقعیت منبع به نظر می‌رسد عدم تطابق شناسه و توقف فرایند ایمپورت
گرد کردن رو به بالای پوشش ناکامل «تمام نمرات بالای 90 قبول می‌شوند» برچسب unknown همراه با ثبت پوشش 2 از 11
جلوه دادن اجرای میزبان به جای اجرای باینری اصلی «فایل شیء x86-64 مقدار 25 را برگرداند» الصاق محدودیتِ اجرای بیلد میزبان به ادعا
بستن مجهول تنها با نوشتن یک توضیح متنی «حل شد، بالا را ببینید» ثبت یک نتیجه‌گیری verified با شناسه‌های مشاهده‌شده و مرجعیت معتبر
بازنویسی نسخه‌های منقضی تصادم تغییرات هم‌زمان دو تحلیل‌گر صدور خطای revision-conflict و الزام به بازخوانی نسخه
بازپخش اسنپ‌شات قدیمی پس از انتشار جدید پاسخ‌ها از قراردادی که تغییر کرده است خطای profile_mismatch حین ایمپورت
نگه‌داشتن کش پس از تغییر نام بازپخشی که نوشتن متاداده را پنهان می‌کند باطل‌سازی پیش از ارسال و ممانعت از بازپخش در صورت شکست عملیات نوشتن

قانون عملیاتی

مشاهدات مستقیم، محاسبات و انتظارات خود را در قالب سه دسته‌بندی مشخص با سه واژه‌ی دقیق ثبت کنید. مجهولات را همیشه با ارجاع به یک شناسه مختومه کنید، هرگز با نوشتن یک جمله نبندید. پاسخ‌ها را تنها تحت همان کلید دقیقی که ذخیره شده‌اند بازپخش کنید، و اگر حتی یک رشته مربوط به نگارش تغییر کرد، آن را کنار بگذارید.

برای مطالعه‌ی بیشتر

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسابزار rea به هر رکورد شواهد یک مقدار اطمینان (confidence) می‌دهد. اسکیما کدام مجموعه از مقادیر را مجاز می‌داند؟
  2. حین درسدو نفر یک کوئری rea یکسان را روی یک فایل آبجکت یکسان در پوشه‌های مختلف روی ماشین‌های متفاوتی اجرا می‌کنند. چه اتفاقی برای evidence_id می‌افتد؟
  3. حین درسیک ادعا بیان می‌کند grade(55) == 0 است. دفتر کل شامل رکورد شبه‌کد Ghidra و مدل جدول مشتق‌شده است. وضعیت این ادعا چیست؟
  4. حین درسیک اسنپ‌شات تحت دایجست پروفایل a04af771 ذخیره شده است. در نسخه‌ی بعدی rea، قرارداد jump_table_evidence به v2 تغییر می‌کند. کش با این اسنپ‌شات چه کار می‌کند؟
  5. پس از درسچه چیزی یک مجهول باقی‌مانده (residual unknown) را با وضعیت نهایی verified می‌بندد؟
  6. پس از درساجراهای بیلد هاست نشان دادند که grade(95) == 4 و grade(100) == 4 است. ادعا شامل تمام نمرات از 90 تا 100 می‌شود. کدام وضعیت درست است؟

سؤالات متداول

چرا مسیر فایل نباید در محاسبه شناسه شواهد (evidence_id) دخیل باشد؟

برای اینکه هش محتوا با جابه‌جایی فایل روی سیستم‌های مختلف یا تغییر نام پوشه‌ها عوض نشود؛ مشاهده‌ی یکسان از بایت‌های یکسان باید همیشه شناسه یکسانی تولید کند.

تفاوت سطوح اطمینان observed و derived در چیست؟

سطح observed فکتی است که مستقیماً از بایت‌ها یا اجرای واقعی کد خوانده شده، در حالی که derived دانشی است که از تحلیل و محاسبه روی داده‌های observed استخراج می‌شود.

چگونه یک ادعا (Claim) به وضعیت پشتیبانی‌شده (pass) ارتقا پیدا می‌کند؟

صرفاً در صورتی که اجرای واقعی و ثبت‌شده از تابع برای تمام ورودی‌های مشخص‌شده در ادعا وجود داشته باشد و نتیجه با پیش‌بینی کاملاً همخوانی داشته باشد.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.