Phase 13: Tools & Protocols

جداسازی و کنترل جریان: از بایت ها تا نمودار می توانید بررسی کنید

یک لیست از خلع داده ها یک ادعا است که بائتهای کدام دستورالعمل هستند و این درس کد کد و نمودار را که ادعا را بررسی می کند، ایجاد می کند.

Type: Build

Languages: Python, C, x86-64 assembly

Prerequisites: Phase 13 · 32 (ELF from scratch)

Time: ~150 minutes

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

  • فرعي مجموعه دستورالعمل x86-64 در تنظیمات دفترچه از قوانین در جلد 2، فصل 2 Intel SDM رمزگذاری شود.
  • ضد کدگر رو ثابت کن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 GNU یا یک 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 در صفحات کمتر، فرمت مشابه را توصیف می کند و یک بررسی متقابل خوب است.

هر میدان تصمیم می گیرد که میدان بعدی چه معنایی دارد. بنابراین یک decoder یک زنجیره تصمیمات کوچک است و هر تصمیم یک جمله در راهنمای پشت آن دارد.

پیشگویی ها به چهار گروه تقسیم می شوند

SDM 2.1.1 فهرست پیشگام های قدیمی را در نظر می گیرد. گروه 1 نگه دارد F0قفل و تعویضات تکرارF2وF3گروه 2 دارای شش بخش برتری (2E.%cs) و اشاره های شاخه. گروه 366گروه 467، اندازه آدرس رو رد کن

در دستور کار گفته می شود که از هر گروه یک پیشگویی استفاده می شود.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 این یک پیشگویی اجباری است که کد opcode را انتخاب می کند، بنابراین 66 0f ef c0.pxor %xmm0, %xmm0و نه 16 بت چیزی.F3 0f 6f.movdqu. decoder ابتدا زوج از واجب استوانه و دوم byte opcode را می بیند. فقط زمانی که این زوج هیچ ورودی ندارد می کند .66یعنی یک اپراند 16 بیت

REX چهار بیت اضافه می کند و باید در کنار کد آپکود قرار گیرد

SDM 2.2.1 REX را به عنوان یک بایت از تعریف می کند.40به4Fبا شکل0100WRXB.Wیک اپراند 64 بایت را انتخاب می کند.Rدامنه تنظیم ModRM را گسترش می دهد. Xدامنه شاخص SIB را گسترش می دهد. Bگسترش میدان ModRM r/m، میدان پایه SIB یا ثبت در بایت کد های Opcode (جدول 2-4) را فراهم می کند. REX باید درست قبل از بایت های Opcode یا فرار باشد و REX که توسط یک پیشگویی قدیمی دنبال می شود نادیده گرفته می شود.

در48 89 e5بايت48REX با Wتنظیم شده. کد آپ89.MOV r/m, r، و ModRMe5انتخاب می کند%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 به عنوان سه بیت دیگر کد های opcode استفاده می کنند./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 اپراندها را انتخاب می کنند

SDM 2.1.3 ModRM را به mod(بيتس ۷:۶)reg(5:3) وr/m(2:0) mod = 11یعنی r/m یک ثبت است. mod = 00،01و10حافظه متوسط بدون تغییر، تغییر 8 بت یا تغییر 32 بت.r/m = 100یعنی یک بایت SIB دنبال می شود. 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. ModRM44.01 000 100: 8 بت جابجا شدن، reg %eax، و بعد ازش SIB08.00 001 000: مقیاس 1، شاخص %rcx، پايگاه%rax. بايت جابجا شدنfaمتن AT&T ـ ـ6 ـه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. psABI (جدول 4.9) تعریف می کند R_X86_64_PC32مثلS + A - P: آدرس نماد به علاوه ضمیمه به غیر آدرس میدان.

پردازنده این فیلد را به آدرس دستورالعمل بعدی اضافه می کند، بنابراین هدف نهایی S + A + (end - P). براي اينleaاین میدان چهار بایت آخر است، پسend - P4 و هدفش.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. c0ModRM است11 000 000: فرم ثبت، قانون%rax, r/m%rax. .
  4. چهار بايت بعد، بايت هاي فوري و کوچک هستند:0x66666667. .
  5. سفارش AT&T فورا، منبع، مقصد: imulq $0x66666667, %rax, %rax. .

که هفت بايت است وllvm-objdumpثابت متقابل است که اجازه می دهد که clang به 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این پرونده را به عنوان یک تعارض ثبت می کند و مسیر را متوقف می کند، بنابراین تعادل به جای دو بار خاموشی رمزگذاری شده قابل مشاهده است.

آندریسه و دیگران ۹ دستگاه را در ۹۸۱ دستگاه دوگانه تولید شده توسط کامپایلر اندازه گیری کردند (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. دستورات که در0x1432 بتهaddl، و لیست نشان می دهد که 64 بتaddqچهار بايت جدول به عنوان کد ظاهر می شوند.

کاهش مکرر از lookupنماد 6 دستور رو رمز ميکنه، بعد از اون پرش به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) تعریف کرد. این یک سلسله دستورالعمل است که تنها در اولین دستورالعمل وارد می شود و تنها در آخرین دستور آن باقی می ماند. یاقدان و دیگران آن را برای تجزیه در بخش II.A.2 "No More Gotos" (NDSS 2015) ، با گره سر به عنوان ورودی تابع تکرار می کنند.

ساخت دو پاس است که بر اساس دستورالعمل های رمزگذاری شده، مرتب شده توسط آدرس، اسکن می شود. اولین پاس رهبران را جمع آوری می کند. یک رهبر ورود، شاخه یا هدف پرتاب، هدف میز یا دستورالعمل پس از هر انتقال کنترل است. گذر دوم یک بلوک جدید را در هر رهبر و در هر شکاف در ردیف آدرس آغاز می کند.

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در-O131 بايت و 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

اون شش بايتnopwدر0x4aتا حلقه رو به سمت0x50در حد 16 بايت قرار داره، قابل دسترسی است، پس به بلاک تعلق داره0x44. یک بايتnopدر0x5fبعد از آخرينretqو هیچ چیز به آن نمی رسد.unreachable()لیستش رو میذاره

در-O2همان تابع 140 بایت با دو حلقه است.0x80هشت مقدار در هر گذر با اضافه می کندpaddd، و بقيه اسکالري در0xc0. CFG دو لبه عقب داره0x80 -> 0x80و0xc0 -> 0xc0و اتاق آزمايش هر دو رو مي شناسد

تسلط با الگوریتم تکراری

یک بلوکdبر يک بلوک تسلط دارهbوقتي که هر راه از ورودي بهbاز اونجا عبور ميکنهd. حاکم فوریbاون حاکم اونbکه نزدیک تر بهbاین مفهوم به Prosser (1959) تا آلن (1970) برمی گردد و این ساختار است که درس 34 باید قرار دهدifوwhileدرست بود

کوپر، هاروی و کندی "الگاریتم تسلط ساده و سریع" را نوشتند (2001). آنها استدلال می کنند که فرمولا جریان داده، که در بعد از ترتیب معکوس با یک نمایش کامپکت اجرا می شود، در نمودار های زیر حدود هزار گره، Lengauer-Tarjan (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

در-O0clang جمع آوری می کند 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. ورودی 00x91 - 0 = 0x91، شماره 10x8c - 4 = 0x88، و ورودی 20x87 - 8 = 0x7f. نوشته های 3 و 40x82 - 12 = 0x76و0x86 - 16 = 0x76. قضيهاي 6، 7، 8، 9 و 10 از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. .

چه کاری Optimizer کرد تا درجه -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_functionCFG را حمل می کند وbodyاز تابعprocedure_infoاین سازمان را به عنوان محدوده های شامل می کند و هشدار می دهد که "تراشه ها و دم های مشترک نباید به عنوان بایت های متعلق به خود در نظر گرفته شوند".

هرکدام از این واقعیت ها، نسل تکراری گیدرا و تحلیلگرهای آن هستند.instruction_flow_evidence،function_referencesوjump_table_evidenceدر نتیجه، یک نتیجه قوانین تولید کننده آن را نام می دهد. آنچه که نمی تواند انجام دهد، نشان دادن کار خود است. هنگامی که گیدرا جدول را پیدا نمی کند، جریان "نامعلوم" است و بلوک های موردی از تماس گیرندگان، کالز و CFG گم می شوند.

حالا کارت رو داري.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 است. برای هر شی ثابت توافق Oracle را می دهد. برای هر عملکرد دستورالعمل، بلاک و کناری را تحت سه روش می دهد. همچنین بلوک های غیر قابل دسترسی، پرش های غیر مستقیم حل نشده، کناری عقب، تسلط فوری و هر جدول را لیست می کند. با مقایسه فلاک به پایان می رسد.

شکل انتظار می رود:

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

فایل های متن Oracle را با اشیاء نگه دارید. وقتی به نسخه ی کامپایلر جدید می روید، آنها را دوباره ضبط کنید، زیرا یک لیست تغییر یافته یک تنظیم تغییر کرده است.

آزمایشگاه تعاملی

آزمایشگاه A: رمزگذاری روی کاغذ، سپس چک

ازcodeدایرکتوری، پیتون باز:

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

لابراتوار C: نگاه کنید که دو روش متفق نیستند

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متن
  • کد های رمزنگاری شده را با Oracle در هر دو شی قرار دهید. فقط زمانی که دستورالعمل در دستگاه به آنها نیاز دارد، کد های opcode اضافه کنید.
  • طولانيrecursive_descent()پس یک تماس غیرمستقیم سایت تماس را ثبت می کندcallsباNoneبه عنوان هدف و ادامه پس از آن.
  • یک حل کننده بنویسید که صف اشاره کننده تابع را از طریق نقل مکان های آن بخواند و کال های احتمالی را گزارش دهد.
  • تنظیمات calllee را به گزارش عملکرد اضافه کنید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 با تعویض میدان آن در داخل دستورالعمل ثبت می شود
  • پیشگویی های قدیمی و یک کد تخلف بخش مانند Oracle
  • اجباری66پیشگویی کد opcode SSE را انتخاب می کند و از registers 32-bit خارج می شود
  • 8-بایت های فوری برای حساب و حساب و بدون علامت برای شمارش تغییر هستند
  • یک دستور بیش از 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.