Phase 13: Tools & Protocols

التفكيك والتحكم في التدفق: من البايت إلى الرسم البياني يمكنك التحقق منه

قائمة التفكيك هي مطالبة حول أي البايتات هي تعليمات، وهذا الدروس يبني المفكّر والجراف الذي يُحقق من الدعوى.

Type: Build

Languages: Python, C, x86-64 assembly

Prerequisites: Phase 13 · 32 (ELF from scratch)

Time: ~150 minutes

أهداف التعلم

  • فكّر مجموعة إرشادات x86-64 في إطارات الكتيب من القواعد في إدارة إدارة الإنترنت (Intel SDM) الجدول 2، الفصل 2.
  • إثبات المفكّر ضد llvm-objdump -d" على كل تعليم " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي " أي
  • اجري مسح خطي وهبوط متكرر على نفس البايتات و اشرح أين كل واحد خاطئ.
  • قطع التعليمات إلى كتلة أساسية وبناء رسمية التحكم في التدفق مع حواف مدرجة.
  • احسب المهيمنين المباشرين باستخدام خوارزمية كوبر و هارفي وكينيدي وتجد حلقات من الحواف الخلفية
  • استعادة أهداف قفزة غير مباشرة من إدخالات إعادة التوطين التي تشير إلى جدولها.

المشكلة

لقد قمت بتجميعهاledger.cفي-O0و سألت عميل ماgradeلقد اتصل العميل بتفكيك، وقرأ القائمة، و أجاب أن الوظيفة تنتهي فيjmpq *%raxبعد ذلك كتب رمزًا مزيفًا مع واحدreturn 0. . .

أنت تفتح نفس الشيء فيllvm-objdump -dو نرى أربع كتلة بعد القفز التي تخزن 4، 3، 2 و 1. كانت في القائمة كلها. لا شيء في القائمة يقول كيفية التنفيذ يصل إليها، لذلك العاملة معالجتها كرمز ميت.

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

لا يوجد في rea 6.5.0 جهاز تفكيك خاص به. للتحليل العميق، يطلب منك README أن تجلب Ghidra، Hopper، أو IDA. للتفتيش خارج الاتصال، يمكن لـ rea أيضاً أن تدعو إلى غنو objdump أو Rizin الذي تقوم بتثبيته. يوقف الرسم البياني للدعوة في أهداف غير حلولة: "المكالمات/المكالمات تحتوي فقط على وظائف حلولة.... لا يزال التدفق الحاسوبي غير المستهدف مجهول". هذا الدروس يُبني الجزء الذي يُنتج هذه الحقائق، بحيث يمكنك أن تحكم عليها عندما تقرأ أداة.

نمط التعليمات لديه قاعدة واحدة لكل مجال

إنديل SDM الجدول 2، الشكل 2-1 يعطي ترتيب البايت لكل تعليمات. أولاً تأتي المضافات الاختيارية، ثم واحد إلى ثلاثة بايتات رمز الخيار، ثم بايت ModRM الاختيارية وبايت SIB الاختيارية. يتبع تحرك من 0, 1 ، 2 ، أو 4 بايت ، ثم مباشرة من 0, 1 ، 2 ، أو 4 بايت. التعليمات الكاملة هي 15 بايت على الأكثر (SDM 2.3.11). يصف أجهزة AMD APM المجلد 3، الفصل 1، القواعد 1.1 إلى 1.7 نفس النموذج في صفحات أقل ويعتبر ذلك تحقيقاً متقاطعاً جيداً.

كل حقل يقرر ما يعنيه الحقل التالي. لذلك فإن المفكّر سلسلة من القرارات الصغيرة، وكل قرار لديه جملة في الدليل وراءه.

المُستَبْدَلَاتِ تأتي في أربع مجموعاتِ

الميزانية 2.1.1 قائمة بالمواعيد المسبقة القديمة.F0القفل والإضافات المتكررةF2وF3المجموعة 2 تمتلك الستة قطاعات المُتجاوزة (2Eهو%cs) و إشارات الفرع. المجموعة 3 هي 66، والحجم التشغيلي.67، حجم العنوان يُغلب.

يدل المذكرة على أنه يمكن استخدام إضافة واحدة من كل مجموعة.ledger-x86_64-O0.oفي التكافؤ0xf2يقرأ66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00، خمسة نسخ من66في صفllvm-objdumpيطبعها على شكلnopw %cs:(%rax,%rax)كما يفعل المقرر الذي تقوم ببناءه. يقوم المقرر بجمع كل قبل البايت الذي يراه ويسأل فيما بعد أي منها يهم.

66لتوثيقات SSE هو مقدمة إلزامية التي تختار رمز الخيار، لذلك 66 0f ef c0هوpxor %xmm0, %xmm0و ليس 16 بت أي شيء.F3 0f 6fهوmovdqu. يبحث المفكّر أولاً عن زوج من المُضافات الإلزامية والبايت الثاني من رمز الاختيار. فقط عندما لا يكون هناك إدخال في هذا الزوج66يعني أوبراندي 16 بت

REX يضيف أربعة بيتات ويجب أن يجلس بجانب رمز الاختيار

SDM 2.2.1 تعريف REX بايت واحد من 40إلى4Fمع الشكل0100WRXB. .Wيختار أوبراند 64 بتRيوسع مجال تنظيم ModRM. Xيوسع حقل مؤشر SIB. Bيمتد حقل ModRM r/m، وحقل SIB الأساسي، أو السجل في البايت في رمز الخيار (الجدول 2-4). يجب أن يأتي REX مباشرة قبل رمز الخيار أو البايتات الهروبية، ويجب تجاهل REX يليها ضيف سابق.

في48 89 e5البايت48هل REX مع Wالمجموعة، رمز الخيار89هوMOV r/m, rو (مدرم)e5يختار%rspفي%rbpإزالة REX و نفس البايتات هيmovl %esp, %ebp. . .

REX تغير أسماء سجلات البايت أيضا. مع وجود REX، قم بتحديد الرموز 4 إلى 7 %spl،%bpl،%silو%dilبدلاً من%ah،%ch،%dhو%bhوذلك هو السبب في أن جهاز القيادة يحتفظ بمجملتين

الرمز البياني يعيش في خريطتين

رمز عمل واحد بايت هو الحالة الشائعة.0Fيفر من خريطة بثنائي بايت، و 0F 38أو0F 3Aيستخدم بعض الرموز الرقمية حقل ModRM reg كثلاثة بثات أخرى من الرموز الرقمية، مكتوبة /digitفي الدليل83 /7هوcmpمع 8 بتات مباشرة، 83 /5هوsubوFF /4هو غير مباشرjmp. . .

المُشفّر فيcode/main.pyتغطي الرموز في الأربعة كائنات ثابتة بالإضافة إلى عدد قليل من الجيران. القائمة هي الأشكال الحسابية الثمانية،mov،lea،movslq،movzbl،imul, النقبات ,testوidiv. يستمر معpush،pop،call،ret، القفزات المشروطة الـ16، والمتعددة البايتاتnopو خمسة رموز SSE2 أي رموز أخرى ترتفعDecodeErrorمع العنوان والسبب، وحدة فك المعلومات التي تخمن أنها أسوأ من تلك التي تتوقف

تقوم ModRM و SIB باختيار النقاط التنفيذية

الميزانية الدولية 2.1.3 تقسم الميزانية الدولية للعمل الدولي إلى mod(بيتس 7:6)reg(5:3) و r/m(2:0) mod = 11يعني r/m هو سجل. mod = 00،01و10متوسط الذاكرة بدون تحرك، تحرك 8 بتات، أو تحرك 32 بتات. r/m = 100يعني أن بايت SIB يلي.scale(7: 6)index(5:3) و base(2: 0) ، ومؤشر 100يعني عدم وجود سجل مؤشر.

صفين خاصين في الجدول 2-2 في وضع 64 بت. mod = 00معr/m = 101ليس كذلك(%rbp). هو تحرك 32 بت بالنسبة إلى التعليمات التالية (SDM 2.2.1.6). mod = 00مع قاعدة SIB 101هو تحرك 32 بت بدون سجل أساسي.

خذ8d 44 08 faمنgradeفي-O0- رمز البيانات8Dهوlea. مودر ام44هو01 000 100: تحرك 8 بتات، reg %eaxو تليها SIB08هو00 001 000: مقياس 1، مؤشر %rcx، قاعدة%rax. البايت التنقلfaهو -6 . نص AT&T هو leal -0x6(%rax,%rcx), %eax، الذي يحسبscore / 10 - 6، مؤشر المفتاح

النقل هو المكان الذي يكتب فيه محرر الروابط

48 8d 0d 00 00 00 00هوleaq (%rip), %rcx. أربعة البايتات الصفر هي حقل التحرك من أوبراند ذات علاقة RIP. في كائن قابل للتنقل هم حاملي الموقع..rela.textفي التكافؤ0x69يقولR_X86_64_PC32.rodata-0x4. تحدد المادة (المجملة 4.9) R_X86_64_PC32كـS + A - P: عنوان الرمز زائد الإضافة ناقص عنوان الحقل.

المعالج يضيف الحقل إلى عنوان التعليمات التالية، لذلك الهدف النهائي هو S + A + (end - P)لهذا السببleaالحقل هو الأربعة بايت الأخيرة، لذلك end - Pهو 4 والهدف هو .rodata + (-4) + 4 =.rodata + 0x0. تعوض إضافة -4 هذه الأربعة بايتس مسبقاً. كل أوبراند مقارنة مع RIP في الأجهزة المصممة يحمل هذا -4 داخل إضافة، لذلك.rodata+0x10على-O1 leaالوسائل.rodata+0x14. . .

يقوم المقرر بتسجيل تعديل حقل التحرك داخل كل تعليمة. resolve_reference()يبحث ذلك الموازنة في جدول التنقل وتقرير حيث سوف تشير العملية بعد ربط.leaq (%rip), %rcxإلى الطاولة في.rodata+0x0" . "

الأجهزة الفورية 32 بت على الأكثر، باستثناء استثناء واحد

SDM 2.2.1.5 تقول البقاء الفوري في أقصى 32 بت في وضع 64 بت وتكون إشارة تمتد. الاستثناء هو MOV r64, imm64(B8+rمعREX.W), الذي يسميه النصوصية AT&T movabsq. القرآن يقرأ 8 بتات مباشرة كما هو موقوف لعدد (83 c1 faهوaddl $-0x6, %ecx) و غير مؤهلة لعدة التحولات و اختيارات التشويش (pshufd $0xeeهذا التقسيم يطابق ماllvm-objdumpبصمات

بالقرب من الفروعpushوpopافتراضية على أوبراند 64 بت وتجاهل 66(SDM 2.2.1.7) لهذا السبب يطلق عليها المُشعّر اسمهاpushq،popq،callqوretqبدون قراءة مقدمة حجم الأوبران

فك التعليمات اليدوية

قبل أن تثق بالرمز، فك رمزاً48 69 c0 67 66 66 66منgradeعلى الورق

  1. 48هو في40إلى4F، لذا هو REX مع W = 1حجم الاوبراند 64 بت
  2. 69هوIMUL r, r/m, imm32. . .
  3. c0هو ModRM 11 000 000: نموذج التسجيل، reg %rax, r/m %rax. . .
  4. الأربعة البايتات التالية هي الفورية، القليل من الإنديان:0x66666667. . .
  5. طلب AT&T هو فوري، المصدر، الوجهة: imulq $0x66666667, %rax, %rax. . .

هذا سبعة بايت، و llvm-objdumpيُطبق نفس النص. الثابت هو المتبادل الذي يسمح للصوت أن يُقسم بـ 10 مع مضاعفة و sarq $0x22سوف تقابل هذا الزوج مرة أخرى في الدروس 34، حيث يقوم المصعد بتحويله مرة أخرى إلىscore / 10. . .

يقوم المفكّر بهذه الخطوات الخمسة فيDecoder.decode()،_State.modrm()و_State.finish()اقرأها بالترتيب الذي تمت فيه وافق كل فرع على قسم SDM المشار إليه في سلسلة الوثائق

التصفية الخطية

يصف شوارز وديبرا واندروز التصفح الخطي في القسم 3.1 من "إزالة الرمز التنفيذي المراجعة" (WCRE 2002). تبدأ في أول بايت من قسم الرمز ، وتفكيك تعليمات واحدة ، وتستمر في بايت بعد ذلك ، وتوقف في النهاية. objdumpيعمل بهذه الطريقة، وكذلك يفعل llvm-objdump. . .

pythondef linear_sweep(code: bytes, start: int = 0, end: int | None = None) -> SweepResult:
    position = start
    while position < end:
        try:
            instruction = decode(code[:end], position)
        except DecodeError:
            undecodable.append((position, code[position]))
            position += 1
            continue
        instructions.append(instruction)
        position = instruction.end

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

التصفح هو أيضاً الوسيلة في هذا الدروس يجب أن يتفق المفكّر معllvm-objdump -dعلى كل تعليمات كل جهاز، وllvm-objdumpهو نفسه مسح خطي. النص المسجل يعيش فيcode/fixtures/*.objdump.txt. يختبر الاختبار كل سطر إلى عنوان، وبايت، ونص، ويترك التعليقات بعد #ويتقارن. الأجسام الأربعة تحتوي على 86، 56، 84، و 11 تعليمات، 237 في الإجمال، و المفكّر يطابق كلّهم.

التراجع المتكرر

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

pythonif instruction.flow == "branch":
    worklist.append(instruction.target)
    address = instruction.end
elif instruction.flow == "indirect-jump":
    table = resolver(instruction, instructions) if resolver else None
    if table is None:
        unresolved.append(instruction)
        break

وضع الفشل هو العكس. القفز غير المباشر ليس له هدف في بايتاته. يتوقف المشي هناك، ويبقى كل كتلة يمكن الوصول إليها فقط من خلال القفز مجهولة. يظهر الشكل 2 في الورقة خطرًا ثانيًا: مجموعة هدفي مشتقة من عمليات نقل تشير إلى منتصف تعليمات. النزول في main.pyيُسجل هذه الحالة كصراع ويقف تلك المسارة، لذا فإن التداخل مرئي بدلاً من فك رمزها بصمت مرتين.

قياس أندرييس وغيرها تسعة مفصلات على 981 محمولاً من المجموعات الثنائية التي تم إنشاؤها (USENIX Security 2016). كانت البيانات الداخلية والرقم المتداخلة نادرة في إنتاج المحمول الحقيقي من المفترض في الأدب، وكانت البدائية الصعبة استرداد حدود الوظيفة. ضع كلا النتائج في اعتبارك. فخ الفخ في القسم التالي نادر في إنتاج المحمول. قفزة غير مباشرة في gradeهو شائع

مصنع حيث يكون التصفح خاطئ والنزول صحيح

code/fixtures/sweep-trap.sهو 24 بايت من الجمعية المكتوبة يدويا مع جدول أربعة بايت داخل جسم الوظيفة.movzbl (%rcx,%rax), %eaxويقفز فوقها

asmlookup:
        movslq  %edi, %rax
        leaq    .Ltable(%rip), %rcx
        movzbl  (%rcx,%rax), %eax
        jmp     .Ldone
.Ltable:
        .byte   0x31, 0xc0, 0x90, 0x48
.Ldone:
        addl    $1, %eax
        retq

بناءه معclang -target x86_64-unknown-linux-gnu -c sweep-trap.sتم اختيار الأبيط الأربعة من الجدول بحيث يتم فكها كإرشادات معقولة31 c0هوxorl %eax, %eax. .90هوnop. .48هو إضافة REX، لذلك الجذب يجمعها مع ثلاث بايت من الحقيقي addl $1, %eaxو بصماتaddq $0x1, %rax- المعتقلينllvm-objdumpالنتائج تظهر بالضبط أن:

text       e: eb 04                        	jmp	0x14 <lookup+0x14>
      10: 31 c0                        	xorl	%eax, %eax
      12: 90                           	nop
      13: 48 83 c0 01                  	addq	$0x1, %rax
      17: c3                           	retq

لا شيء في هذه القائمة يبدو مكسوراً كل بايت تم فك رمزه كل مخطط حقيقيretqالقائمة لا تزال خاطئة في ثلاثة طرق.0xeالأهداف0x14، ولا توجد تعليمات في القائمة تبدأ في 0x14. التعليمات التي تعمل في0x14هو 32 بت addl، والقائمة تظهر 64 بت addqقبل بايت واحد. البايتات الأربعة في الجدول تظهر كرمز.

التراجع المتكرر منlookupالرمز يقرأ ستة تعليمات، ويتبع القفز إلى 0x14و تقاريرaddl $0x1, %eaxلا يُدعي أيّ من الأربعة بايتات من الجدول ولا أيّ من الأثمانية بايتات من الملفات السابقةplus_one. .trap_report()فيmain.pyيطبخ المقارنة:

json"linearSweep": {
  "instructions": 9,
  "atJumpTarget": {"starts": "0x13", "text": "addq\t$0x1, %rax", "startsAtTarget": false},
  "claimedDataBytes": 4,
  "claimedPaddingBytes": 8
},
"recursiveDescent": {
  "instructions": 6,
  "atJumpTarget": "addl\t$0x1, %eax",
  "claimedDataBytes": 0,
  "claimedPaddingBytes": 0
}

الاختبارtest_sweep_and_objdump_share_the_same_mistakeيثبت ذلكllvm-objdumpطباعةaddq $0x1, %raxفي0x13الـ"أوراكل" هو مسح خطي ويحتوي على نقطة عمياء. الاتفاق مع الـ"أوراكل" يتحقق من المفكّر، و"الـ"إنزال" يتحقق من إرشادات التشغيل.

الكتل الأساسية

حدد ألين الكتلة الأساسية في "تحليل التدفقات التحكمية" (1970). إنها تسلسل من التعليمات التي يتم إدخالها فقط عند أول تعليماتها وتتركها فقط في آخرها. يعيد ياكدان وغيره من إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إعادة إ إعادة إعادة إعادة إعادة إ إ إ إعادة إعادة إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إعادة إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إعادة إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ إ

إن البناء هو مسح مزدوج عبر التعليمات المفكورة ، يتم فرزها حسب العنوان. يجمع المرور الأول القادة. المرور هو الدخول أو هدف الفرع أو القفز ، أو هدف الطاولة ، أو التعليمات بعد أي نقل التحكم. يبدأ المرور الثاني كتلة جديدة في كل رئيسي وفي كل فجوة في تسلسل العنوان.

pythonfor instruction in self.instructions:
    if instruction.address in leaders or current is None or current.end != instruction.address:
        current = Block(instruction.address, [])
        self.blocks[instruction.address] = current
    current.instructions.append(instruction)

الحواف تتبع من آخر تعليمات كل كتلة. العودة ليس لها خلف. قفزة واحدة، علامة jumpفروع مشروطة لها اثنين، معلقتtakenوfallthrough. إن تعليمات بسيطة في نهاية كتلة تتراوح إلى كتلة أخرى، والتي تحدث عندما يكون التعليمات التالية هدفاً فرعياً.tableأو لا يوجد، ويتم تسجيل الكتلة فيunresolved. . .

هناك حقائق أخرى تظهر من نفس المسح.gradeفي-O0وتنتج خمسة منها. كتلة يقع عنوانها المبدئي داخل تعليمات قائمة طريقة أخرى هي حالة التداخل من الفخ.

الرسم البياني للتدفقات التحكمية للجميع

totalفي-O1هو 31 بايت و 13 تعليمات:

text      40: 85 f6                        	testl	%esi, %esi
      42: 7e 18                        	jle	0x5c <total+0x1c>
      44: 89 f1                        	movl	%esi, %ecx
      46: 31 d2                        	xorl	%edx, %edx
      48: 31 c0                        	xorl	%eax, %eax
      4a: 66 0f 1f 44 00 00            	nopw	(%rax,%rax)
      50: 03 04 97                     	addl	(%rdi,%rdx,4), %eax
      53: 48 ff c2                     	incq	%rdx
      56: 48 39 d1                     	cmpq	%rdx, %rcx
      59: 75 f5                        	jne	0x50 <total+0x10>
      5b: c3                           	retq
      5c: 31 c0                        	xorl	%eax, %eax
      5e: c3                           	retq

القادة هم0x40(الدخول)0x44(بعدjle()0x50(هدف المجموعة)jne()0x5b(بعدjne() و0x5c(هدف المجموعة)jleخمسة كتلة، خمسة حواف:

FromToKindWhy
0x400x44fallthroughjle not taken, count is positive
0x400x5ctakencount is zero or negative, return 0
0x440x50fallthroughthe nopw is alignment inside the block, execution passes through it
0x500x50takenjne 0x50, the loop
0x500x5bfallthroughloop exit, return the sum

الـ6 بايتnopwفي0x4aهو التدليك حتى أن الحلقة رأس في 0x50يقع على حدود 16 بايت. يمكن الوصول إليها، لذلك تنتمي إلى الحدود0x44- البايت الواحدnopفي0x5fبعد آخر مرةretqو هو أيضا التدليك، ولا شيء يصل إليه. التدفق على الوظيفة يجعل حجر السادس لها، وunreachable()يُدرّسُه.

في-O2نفس الوظيفة هي 140 بايت مع حلقين.0x80يضيف ثمانية قيم لكل مرسل مع paddd، والباقي من السكالار يذهب إلى0xc0. المكتب لديه حوافين خلفية0x80 -> 0x80و0xc0 -> 0xc0و مجموعة الاختبارات تحقق في كليهما

السيطرة على الخوارزمية المتكررة

  • حجرdيهيمن على كتلةbعندما كل طريق من المدخل إلىbيمر من خلالd. السيطرة المباشرة علىbهو المهيمن علىbهذا أقرب إلىb. هذه الفكرة تعود إلى Prosser (1959) إلى Allen (1970) ، و هي هي الهيكل الذي يجب أن يضع الدروس 34ifوwhileصحيحاً

كتب كوبر و هارفي وكينيدي "الخوارزمية السائدة والسريعة" (2001). يجادلون بأن صيغة تدفق البيانات ، التي تعمل في الترتيبات اللاحقة العكسية مع تمثيل ضيق ، تضرب لينغواير-تارجان (1979) على الرسوم البيانية تحت حوالي ألف عقدة. ورقتهم تقول أن هدفها "ليس تقديم خوارزمية جديدة". يظهر أن الخوارزميات البسيطة يمكن أن تفوز في الممارسة العملية و هي أدوات تعليمية جيدة. وظيفة CFG لا يكون لديها أبدا ألف عقدة.

pythondef intersect(first: int, second: int) -> int:
    while first != second:
        while number[first] > number[second]:
            first = idom[first]
        while number[second] > number[first]:
            second = idom[second]
    return first

changed = True
while changed:
    changed = False
    for start in order[1:]:
        processed = [p for p in self.predecessors(start) if p in idom]
        candidate = processed[0]
        for other in processed[1:]:
            candidate = intersect(other, candidate)
        if idom.get(start) != candidate:
            idom[start] = candidate
            changed = True

numberيحتوي على وضع بعد ترتيب العكس لكل كتلة، مع الدخول عند 0. intersectيمر أصابعين فوق شجرة المهيمنة الحالية حتى يلتقيون.<هذا الرمز يستخدم أرقام بعد ترتيب العكس، لذلك المقارنة هي >كل شيء آخر يتبع رمزية الورقة

لأجلtotalفي-O1النتيجة هيidom(0x44) = 0x40،idom(0x50) = 0x44،idom(0x5b) = 0x50وidom(0x5c) = 0x40- الحافة0x50 -> 0x50هو حافة خلفية لأن هدفها يهيمن على مصدرها، و حافة خلفية تعبر عن حلقة (ألن 1970). back_edges()يعيد ذلك الاختبارtest_dominators_on_a_hand_built_diamond_with_a_loopيستخدم نفس الشفرة على تسلسل 16 بايت مع نقطة اتصال، لذلك intersectيرى اثنين من أسلافه

الجدول القادم في درجة -O0

في-O0كلانغ يجمع switchفيgradeإلى لغة جدول القفز المستقل عن الموقف:

text      5d: 83 e8 04                     	subl	$0x4, %eax
      60: 77 38                        	ja	0x9a <grade+0x6a>
      62: 48 8b 45 f0                  	movq	-0x10(%rbp), %rax
      66: 48 8d 0d 00 00 00 00         	leaq	(%rip), %rcx
		0000000000000069:  R_X86_64_PC32	.rodata-0x4
      6d: 48 63 04 81                  	movslq	(%rcx,%rax,4), %rax
      71: 48 01 c8                     	addq	%rcx, %rax
      74: ff e0                        	jmpq	*%rax

اقرأها على أنها أربعة حقائقsubl $0x4يليها jaهو إختبار الحدود: المؤشرات فوق 4 تذهب إلى حالة الافتراض في 0x9a، لذا الجدول لديه خمس إدخالات.leaqمع إعادة توطينها يضع عنوان الجدول في %rcx، والقسم السابق أظهر أن العنوان هو .rodata+0x0- المُؤمنونmovslqتحميل إدخال مؤلف من 32 بت مقياس 4 addqيضيف قاعدة الجدول، لذلك كل مدخل هو المسافة من الجدول إلى كتلة الحجرة.

في ملف الكائن كل بايت من الجدول هو صفر. الأهداف موجودة فقط كتحويلات في .rela.rodata:

textRELOCATION RECORDS FOR [.rodata]:
OFFSET           TYPE                     VALUE
0000000000000000 R_X86_64_PC32            .text+0x91
0000000000000004 R_X86_64_PC32            .text+0x8c
0000000000000008 R_X86_64_PC32            .text+0x87
000000000000000c R_X86_64_PC32            .text+0x82
0000000000000010 R_X86_64_PC32            .text+0x86

التطبيقS + A - Pللدخولkفي التكافؤ4kالقيمة المخزنة هي.text + A - (.rodata + 4k)، والرقم يضيف .rodata + 0لذلك الهدف هو.text + A - 4k. دخول 0 هو 0x91 - 0 = 0x91, المدخل 1 هو 0x8c - 4 = 0x88، والدخل 2 هو 0x87 - 8 = 0x7fالإدخالات 3 و 4 هي0x82 - 12 = 0x76و0x86 - 16 = 0x76الحالات السادسة والسبعة والثمانية والنابعة والعاشرة منscore / 10اذهب إلى الكتل التي تخزن 1، 2، 3، 4 و 4. وهذا يطابق خط المصدر C للخط، بما في ذلك الحالة المشتركة ل 9 و 10.

recover_table()يُفعل هذا الحسابjump_table_resolver()يطابق الإيقاع العكسيjmpq. إنه يجدaddqالذي جعل السجل،movslqالذي تحمله،leaqالذي جعل القاعدة والحدودja. ثم يدفع الانخفاض الأهداف الأربعة المختلفة ويتم الاستمرار. يكون للجهاز التنفيذي المرتبط القيم النهائية في الجدول ولا توجد إعادة التنقل. ثم تقوم نفس الوظيفة بقراءة الإدخالات الموقعة بـ 32 بت وإضافة عنوان الجدول ، و evidenceتسجل المسار الذي أدى إلى تحقيق الأهداف.

أرقام gradeفي-O0، منpython3 code/main.py cfg code/fixtures/ledger-x86_64-O0.o grade:

MethodInstructionsBlocksEdgesBytesWhat is wrong
linear sweep3497118 code + 10 padding5 unreachable blocks, no edge into the four case blocks, padding decoded as nopw
recursive descent253282 code36 code bytes never reached, jmpq *%rax at 0x74 unresolved
descent with the table33811118 codenothing

مع الطاولة شجرة المهيمنة كاملة.idomمن كل كتلة قضية0x62، الحجر مع القفز.idomمن0xa1هو المدخل0x30، لأن المسار الافتراضي من 0x9aيصل0xa1بدون أن تمر0x62. . .

ما الذي فعله المُحسّن لتحديد الدرجة عند -O1

سترىgradeووصف بأنه مفاتح يجمع إلى جدول قفزة. البايتات تقول شيئا أكثر تحديدا.-O1و-O2لا يوجد قفزة غير مباشرةgradeعلى الإطلاق:

text      2c: 83 f9 04                     	cmpl	$0x4, %ecx
      2f: 77 0c                        	ja	0x3d <grade+0x2d>
      31: 89 c8                        	movl	%ecx, %eax
      33: 48 8d 0d 00 00 00 00         	leaq	(%rip), %rcx
		0000000000000036:  R_X86_64_PC32	.rodata+0x10
      3a: 8b 04 81                     	movl	(%rcx,%rax,4), %eax
      3d: c3                           	retq

إضافة للانتقال0x10+ 4 المعتاد يضع الطاولة في .rodata+0x14الحمل هو ساديmovl، والبايتات هناك01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 04 00 00 00. استبدل المجمع خمسة كتلة الحالة مع جدول من خمسة قيم العودة. هناك نقل، جدول، ومراقبة الحدود. تدفق التحكم هو ثلاثة كتلة وثلاث حواف مع فرع مشروط واحد. find_value_tables()يبلغ عن ذلك كـvalue-tableمع إدخالات [1, 2, 3, 4, 4]وevidence: "bytes". . .

grade_labelفي-O2يذهب خطوة أخرىlabelsتعدد من خمسة مؤشرات السلك أصبح جدول من خمسة 32 بت تعويضات في .rodata+0x0كل مدخل يحمل رقمR_X86_64_PC32إعادة التنقل ضد قسم السطر، ويتم إضافة عنوان الجدول إلى الإدخال الذي تحمله. recover_table()يقرأ تلك الإنتقالات الخمسة ويعود تعويضات0, 2, 4, 6, 8في.rodata.str1.1، التي تحملF،D،C،BوA. نفس الرياضيات التي تعيد جدول القفز تعيد جدول البيانات النسبية ، لأن كليهماS + A - P" في مُعَيَّن " معين .

الدروس من الإعدادين هي تسمية ما تظهر البايتات. جدول القفز يغير الرسم البياني للتدفق التحكم. جدول القيمة يغير الحمل. دعوة كل من "المفتاح" يخفي الفرق الذي يجب أن يعيد إعادة بناء الدروس 34.

ما الذي يعطيك إياه و (غيدرا) هنا

rea 6.5.0 مع مزود Ghidra 12.1.4 يعيد نص إدراج وظيفة من procedure_assemblyكحبل واحدinspect_native_instructionيعيد البايتات والطول ونص المقرر، وعلامات التشغيل، والإشارات المطبوعة، وتدفق تعليمات واحدة، مع تعويض مصنف من رمز p. xrefsيعيد العناوين المصدرة الفريدة المرتبة من Ghidra ReferenceManager.getReferencesTo. .analyze_functionيحمل جهاز CFG و bodyمن الوظيفة. procedure_infoيقدم الهيئة على أنها نطاقات شاملة ويحذر من أن "الفجوات والذيل المشترك يجب ألا يعاملون كبايتات مملوكة".

كل هذه الحقائق هي نزول غيدرا التكراري بالإضافة إلى المحللين، المتسلسلين.instruction_flow_evidence،function_referencesوjump_table_evidenceإذاً، فإن النتيجة تسمي القواعد التي صنعتها. ما لا يمكن أن تفعله هو إظهار عملها. عندما لا تجد غيدرا الجدول، فإن التدفق "غير معروف"، وتفتقر كتلة الحالة من المُتصلين، والمدعوين، والسي إف جي.

لديك الآن العمل، إركضpython3 code/main.py cfgعلى نفس الموضوع ومقارنة البلوك بدء، الحافة العد، والجدول المردد مع ما يعيد المقدم. حيث أنها توافق لديك اثنين من مشتقات مستقلة. حيث أنها تختلف لديك عنوان محدد للنظر في، وهو ما الدروس 35 تسجل كجهل.

ملاحظة على المفكّر كبرنامج. Capstone 6.0.0-Alpha10 (2026-07-21) ثابت خارج الحدود يقرأ ويكتب تحت CVE-2026-55893 و CVE-2026-55894, و 5.0.9 (2026-05-28) إصلاحات CVE محمولة خلفيا. يقرأ المفكّر بايتات لم يكتبها. الحدّ الـ15 بايت، تحقق التقطيع في _Cursor.byte()، ورفض تخمين حماية رمز غير معروفmain.pyمن المدخل الذي لم يتم بناؤه له

بناءها

أطلق التجربة:

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow
python3 code/main.py

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

الشكل المتوقع:

json{
  "fixtures": {
    "ledger-x86_64-O0.o": {
      "oracle": "86/86 instructions agree with llvm-objdump",
      "functions": {
        "grade": {
          "range": "0x30-0xa6",
          "paddingBytes": 10,
          "linearSweep": {"instructions": 34, "blocks": 9, "edges": 7, "unreachableBlocks": ["0x76", "0x7f", "0x88", "0x91", "0xa6"], "...": "..."},
          "recursiveDescent": {"instructions": 25, "blocks": 3, "edges": 2, "unresolvedIndirectJumps": ["0x74"], "...": "..."},
          "recursiveDescentWithTables": {"instructions": 33, "blocks": 8, "edges": 11, "...": "..."},
          "tables": [{"kind": "jump-table", "table": ".rodata+0x0", "count": 5, "entries": ["0x91", "0x88", "0x7f", "0x76", "0x76"], "evidence": "relocation", "...": "..."}]
        }
      }
    }
  },
  "sweepTrap": {"...": "..."}
}

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

  1. ElfObjectيقرأ عناوين القسم والرموز.rela.*الإدخالات، وsymbolize()اسم عنوان الطريقllvm-objdump-لا، لا
  2. Decoder.decode()يقرأ المواعيد المسبقة و REX، ثم يرسل على خريطة رمز الخيارات ذات بايت واحد أو بايتين.
  3. _State.modrm()يقرأ ModRM، SIB، والتحرك، ويُسجل تعويضات حقل التحرك.
  4. resolve_reference()يطبقS + A - Pإلى إعادة التأهيل في مكان النزوح أو في ساحة مباشرة.
  5. linear_sweep()وrecursive_descent()هي الطرق الثنائية، كل منها مع فشله المسجل.
  6. recover_table()وjump_table_resolver()قراءة الجدول من خلال عمليات إعادة توزيعه أو بايتاته.
  7. ControlFlowGraphيُبني القادة والبلوك والحواف المخطوطة، و dominators()هو الخوارزمية التكرارية.
  8. analyze_function()يستخدم كل طريقة على وظيفة واحدة وcompare_with_oracle()يُفحص المُشفّر

هناك أمرين آخرينpython3 code/main.py disasm FILE.oيطبخ قائمة مع هدف الإنتقال بعد كل تعليمات لديها واحدة. python3 code/main.py cfg FILE.o FUNCTIONيطبخ تحليل وظيفة واحدة.

استخدمها

استخدم الأساليب الثلاثة في هذا الترتيب على كائن جديد.

  1. أبدأ بمقارنة النبي صلى الله عليه وسلم أولاًllvm-objdumpعلى تعليمات بسيطة لديها خطأ، وكل هيكل لاحقا يرثها.
  2. قم بتشغيل النزول الترددي من كل رمز وظيفة وقراءة unresolvedكل دخول هو قفزة أو مكالمة غير مباشرة التي لا تملك أهداف
  3. لكل قفزة غير حل، ابحث عن لغة الجدول والتحويل الذي يشير إليها. إذا لم يتماشى لغة الجدول، اقرأ البايت قبل القفزة واكتب ما ترى. هذه الملاحظة هي الأولى غير معروفة في الدروس 35.
  4. مقارنة المصفوفة والتنزيل بايت بـ بايت. البايتات التي فقط المصفوفة تدعي أن تكون ملء أو بيانات. البايتات التي لا تدعي لا يمكن الوصول إليها أو تنتمي إلى وظيفة لم تجدها.
  5. قم ببناء CFG من الهبوط مع الجداول التي تم استعادتها وقراءة الحواف الخلفية. كل واحد هو حلقة التي سوف تتحول الدروس 34 في whileأوfor. . .

احتفظ بملفات النص في النص مع الأشياء. عندما تنتقل إلى نسخة مُحاملة جديدة، أعد تسجيلها، لأن قائمة تغيير هي جهاز تغيير.

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

المختبر (أ): فك الرمز على الورق، ثم تحقق

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

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow/code
python3 -q

أركض

pythonfrom main import *
decode(bytes.fromhex("48 8d 0d 00 00 00 00"), 0).text
decode(bytes.fromhex("8d 44 08 fa"), 0).text
decode(bytes.fromhex("66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00"), 0).length

اكتب ما تتوقعه قبل كل مكالمة ثم إزالة الـ48من الأول وشاهد تغيير حجم العرض ثم حاولdecode(bytes.fromhex("0f 0a"), 0)و قراءة DecodeError. . .

المختبر ب: إعادة التأهيل على موقع

pythonelf = load_fixture("ledger-x86_64-O0.o")
code = elf.section(".text").data
lea = decode(code, 0x66)
lea.displacement_offset
resolve_reference(elf, lea)

المرجع يقول.rodata+0x0. تغيير العنوان إلى0x116فيgrade_labelو قراءة المرجع في .dataثم فك رموز الـcallqفي0x10eو تحلّي الأمر، الحقل المباشر يحملR_X86_64_PLT32 grade-0x4، والهدف المحدد هو0x30، وهوgrade. . .

المختبر (ج): شاهد أن الطرق لا تتفق

pythontrap = load_fixture("sweep-trap.o")
text = trap.section(".text").data
[i.text for i in linear_sweep(text, 0, 0x20).instructions]
[i.text for i in recursive_descent(text, [0], 0x20).sorted_instructions()]

عد التعليمات في كل قائمة و اعثر على تلك التي تبدأ من 0x13ثم قم بتغيير آخر بايت في الجدولsweep-trap.sمن0x48إلى0xe8، إعادة بناءها، و إعادة تشغيلها مرة أخرىaddlو retqإلىcallqمع هدف خارج القسم يبدو أن الوظيفة لا تعود

المختبر D: المهيمنين يدوياً

pythonanalysis = analyze_function(load_fixture("ledger-x86_64-O1.o"), "total")
analysis.cfg.edges
analysis.cfg.reverse_postorder()
analysis.cfg.dominators()
analysis.cfg.back_edges()

رسم الخمسة كتلة على الورق وحساب السيطرة قبل أن ننظر إلى النتيجة ثم تشغيل نفس الخطوط الأربعة علىgradeفي-O0و اشرح لماذاidom(0xa1)هو0x30و ليس0x62. . .

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

إضافة موقع مكالمة غير مباشرة إلى الأجهزة وتوسيع التحليل.

المتطلبات:

  • اكتب وظيفة C التي تدعو من خلال مؤشر وظيفة مختارة من صف صغير. بنيها في -O0و-O1و إلتزام الأشياء معهمllvm-objdumpرسالة
  • اجعل المفكّر يتوافق مع الـ (أوراكول) على كلتا الأشياء. أضف رموزًا فقط عندما تكون هناك تعليمات في الجهاز تحتاج إليها.
  • التمديدrecursive_descent()لذا ، يقوم مكالمة غير مباشرة بتسجيل موقع المكالمة في callsمعNoneكهدف و يستمر وراءه
  • اكتب حللا يقرأ صف المؤشرين للعمل من خلال عمليات إعادة توطينها ويعطي تقارير عن المشاركات المحتملة.
  • إضافة مجموعة الدعوات إلى تقرير الوظيفة على النحو indirectCallsمع نوع الأدلة لكل هدف
  • اكتب اختبار يفشل عندما يعيد القرار هدفاً ليس رمزًا للعمل.

النجاح يعنيpython3 code/main.py cfgاسم كل مكالمة ممكنة مع نقل يثبت ذلك، ومقارنة الوساطة لا تزال تنتقل على كل كائن.

درساً أساسياً

هذا الدروس يتضمنoutputs/skill-disassembly-and-control-flow.md. استخدمها لتحويل اسم كائن وسم عمل إلى قائمة معينة ، والجبرات ، والحواف المطبوعة ، والهيمنات ، وكل نقل غير حل. يتطلب مقارنة النبي ، وطريقتي التفكيك ، وحساب البايت ، ودليل نقل لكل جدول تم استعادته ، وإصدار ما ظل مجهولا.

تحقق من ذلك

إشغال التجربة والسلسلة التحديدية:

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow
python3 code/main.py
python3 code/tests/test_main.py

يجب أن تثبت التحقق:

  • المُشفّر يوافق على llvm-objdump -dعلى جميع 237 تعليمات الأربعة كائنات ثابتة
  • REX.W يختار 64 بتة العمليات و REX.R و REX.B تمديد حقل السجل
  • ModRM و SIB يعطي أوبراند ذاكرة AT&T، بما في ذلك النطاق، المؤشر، والتحرك الموقع
  • يتم تسجيل التحرك النسبي لـ RIP مع تعويضات مجالها داخل التعليمات
  • المشاريع المتكررة القديمة و إعادة تشكيل القسم مثل القرآن
  • إلزامي66يختار المقبل رمز SSE opcode ويترك السجلات 32-bit
  • 8-bit الفورية يتم تمديد الإشارة للحسابات وغير الموقع لعدة التحولات
  • إرشاد أكثر من 15 بايت، إرشاد قصير، ورفع رمز الاختيار غير معروف DecodeError
  • R_X86_64_PC32علىleaيقرر على الطاولة وR_X86_64_PLT32علىcallيُحلّلُ إلى الـ (كالي)
  • المصفاة تقوم بتفكيك جدول الفخ كرمز ولا توجد تعليمات في الهدف القفز
  • السقوط لا يطالب أبداً بالطاولة أو بالملابس ويقرأaddl $0x1, %eaxفي الهدف
  • llvm-objdumpيرتكب نفس الخطأ الذي يرتكبه المفتاح
  • يتم تسجيل دخول داخل تعليمات كصراع
  • قفزة غير مباشرة غير حل توقف الهبوط وتدخل في قائمة
  • totalفي-O1لديه خمسة كتلة، خمسة حواف مدرجة، واحد حافة خلفية، والهيمنة المنتظرة مباشرة
  • الماس المصمم يدويا مع حلقة يعطي المهيمنين الخوارزمية تحدد
  • totalفي-O2لديه حلقين
  • gradeفي-O0يُعطى طاولة قفزة خمس دخولات من .rela.rodataمع أهداف 0x91, 0x88, 0x7f, 0x76, 0x76
  • بدون الطاولةgradeفي-O0لديه ثلاث كتلة و قفزة واحدة غير حل
  • gradeفي-O1و-O2هو جدول القيمة[1, 2, 3, 4, 4]بدون قفزة غير مباشرة
  • grade_labelفي-O2يقرأ جدول سلسلة نسبي من خلال خمسة عمليات نقل
  • الرقم التفاعلي يضم نفس البيانات التي ينتجها التحليل

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

FailureWhat the weak tool reportsWhat the analysis must prove
Data inside .textA plausible listing with every byte decodedThe descent claims no data byte and the jump target starts an instruction
Padding after the last returnA nopw instruction at the end of the functionThe bytes are unreachable from the entry
Indirect jump through a table"jmp rax" and four dead blocksTable address from the lea relocation, entry count from the bounds check, targets from .rela.rodata
Switch turned into a value table"a jump table with a relocation"A movl load from .rodata+0x14, three blocks, no indirect transfer
Call with a zero displacementcallq 0x113, the next instructionR_X86_64_PLT32 grade-0x4 resolves to 0x30
Decoder guesses an unknown opcodeA mnemonic that looks rightDecodeError with the address, and the sweep records the byte
Instruction longer than 15 bytesA very long "instruction"The cursor raises before reading byte 16
Branch into the middle of an instructionTwo overlapping listingsA conflict record with both addresses
Loop reported as straight-line codeNo while in the pseudocodeA back edge whose target dominates its source
Oracle captured from a different compilerTests pass on stale textThe fixture README names the exact compiler and command

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

فكّر ضدّ أوراكول، وتحكم تدفق المشي من إدخالات معروفة، وتحسب كل بايت كما يُزعم، أو ملء، أو بيانات، أو غير قابل للوصول. استعاد كل هدف غير مباشر من إعادة التنقل أو جدول يمكنك الإشارة إليه. سجّل الباقي كغير معروف، مع العنوان.

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

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.