جداسازی و کنترل جریان: از بایت ها تا نمودار می توانید بررسی کنید
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روی کاغذ
48در حال حاضر40به4F، پس REX باW = 1اندازه اپراند 64 بته69.IMUL r, r/m, imm32. .c0ModRM است11 000 000: فرم ثبت، قانون%rax, r/m%rax. .- چهار بايت بعد، بايت هاي فوري و کوچک هستند:
0x66666667. . - سفارش 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پنج بلوک، پنج لبه
| From | To | Kind | Why |
|---|---|---|---|
0x40 | 0x44 | fallthrough | jle not taken, count is positive |
0x40 | 0x5c | taken | count is zero or negative, return 0 |
0x44 | 0x50 | fallthrough | the nopw is alignment inside the block, execution passes through it |
0x50 | 0x50 | taken | jne 0x50, the loop |
0x50 | 0x5b | fallthrough | loop 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 = Truenumberموقعیت عقب ترتیب هر بلوک را دارد، با ورود به 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:
| Method | Instructions | Blocks | Edges | Bytes | What is wrong |
|---|---|---|---|---|---|
| linear sweep | 34 | 9 | 7 | 118 code + 10 padding | 5 unreachable blocks, no edge into the four case blocks, padding decoded as nopw |
| recursive descent | 25 | 3 | 2 | 82 code | 36 code bytes never reached, jmpq *%rax at 0x74 unresolved |
| descent with the table | 33 | 8 | 11 | 118 code | nothing |
با ميز درخت حاکم تمام شده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در این ترتیب:
ElfObjectسرنخ بخش ها، نمادها و.rela.*نوشته ها وsymbolize()اسم يه آدرس رو به راه ميذارهllvm-objdumpآرهDecoder.decode()پیشگام و REX را می خواند، سپس روی نقشه یک یا دو بیت کد آپکود ارسال می کند._State.modrm()ModRM، SIB و جابجایی را می خواند و جابجایی میدان را ثبت می کند.resolve_reference()اعمال می شودS + A - Pبه نقل مکان در یک محل یا میدان فوری.linear_sweep()وrecursive_descent()دو روش هستند که هر کدام شکست خود را ثبت کرده اند.recover_table()وjump_table_resolver()یک جدول را از طریق نقل مکان ها یا بائتهای آن بخوانید.ControlFlowGraphبنا کردن رهبرها، بلوک ها و لبه های تایپ شده وdominators()الگوریتم تکرار است.analyze_function()هر روش رو در يک تابع اجرا ميکنه وcompare_with_oracle()دکوتر رو چک کنه
دو فرمان ديگه وجود دارهpython3 code/main.py disasm FILE.oبعد از هر دستورالعمل که دارای یک لیست است، لیست با هدف نقل مکان را چاپ می کند. python3 code/main.py cfg FILE.o FUNCTIONتجزیه و تحلیل یک تابع را چاپ می کند.
ازش استفاده کن
از سه روش در این ترتیب برای یک شی جدید استفاده کنید.
- اول مقایسه رو با "آرکال" اجرا کن
llvm-objdumpدر یک دستورالعمل ساده یک خطای وجود دارد و هر ساختار بعدی آن را به ارث می برد. - ردش را از هر نماد تابع اجرا کنید و بخوانید
unresolvedهر ورود يه پرش يا تماس غيرمستقيمه که هدفش رو نداري - برای هر پرش حل نشده، به دنبال زبان میز و نقل مکان که به آن اشاره می کند باشید. اگر زبان مطابقت نداشته باشد، قبل از پرش بایت ها را بخوانید و آنچه را می بینید را بنویسید. این یادداشت اولین ناشناخته در درس 35 است.
- باایت هایی که فقط باایت های پاک کننده ادعا می کنند پر کردن یا داده هستند باایت هایی که هیچ کدام ادعا نمی کنند قابل دسترسی هستند یا متعلق به یک تابع هستند که شما پیدا نکرده اید.
- 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یک جدول رشته ای نسبت به پنج نقل مکان را می خواند- این ارقام تعاملی همان داده هایی را که تجزیه و تحلیل تولید می کند، در خود جای می دهد.
روش های شکست تولید
| Failure | What the weak tool reports | What the analysis must prove |
|---|---|---|
Data inside .text | A plausible listing with every byte decoded | The descent claims no data byte and the jump target starts an instruction |
| Padding after the last return | A nopw instruction at the end of the function | The bytes are unreachable from the entry |
| Indirect jump through a table | "jmp rax" and four dead blocks | Table 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 displacement | callq 0x113, the next instruction | R_X86_64_PLT32 grade-0x4 resolves to 0x30 |
| Decoder guesses an unknown opcode | A mnemonic that looks right | DecodeError with the address, and the sweep records the byte |
| Instruction longer than 15 bytes | A very long "instruction" | The cursor raises before reading byte 16 |
| Branch into the middle of an instruction | Two overlapping listings | A conflict record with both addresses |
| Loop reported as straight-line code | No while in the pseudocode | A back edge whose target dominates its source |
| Oracle captured from a different compiler | Tests pass on stale text | The fixture README names the exact compiler and command |
قاعده عملیاتی
با استفاده از یک کلام، کنترل جریان از ورودی های شناخته شده را رمزگذاری کنید و هر بائتی را به عنوان ادعا شده، پوشاندن، داده ها یا غیر قابل دسترسی حساب کنید. هر هدف غیر مستقیم را از یک مکان یا جدول که می توانید به آن اشاره کنید، بازیابی کنید. بقیه را به عنوان ناشناخته با آدرس ثبت کنید.
خواندن بیشتر
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2, Chapter 2: Instruction Format
- AMD64 Architecture Programmer's Manual Volume 3, Chapter 1: Instruction Encoding
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, Table 4.9: Relocation Types
- Schwarz, Debray, Andrews, "Disassembly of Executable Code Revisited" (WCRE 2002)
- Andriesse et al., "An In-Depth Analysis of Disassembly on Full-Scale x86/x64 Binaries" (USENIX Security 2016)
- Allen, "Control Flow Analysis" (1970)
- Cooper, Harvey, Kennedy, "A Simple, Fast Dominance Algorithm" (2001)
- Lengauer, Tarjan, "A fast algorithm for finding dominators in a flowgraph" (1979)
- Capstone 6.0.0-Alpha10 release notes
- rea 6.5.0 منبع در برچسب
3b79af2که این درس شرح می دهد:README.md،docs/ghidra-provider.md،docs/ghidra-dos.md،docs/reverse-engineering-tools.md،bridge/ghidra/ReaGhidraBridge.java،src/contracts/officialToolContracts.ts،src/ghidra/GhidraAnalysisProfile.ts. .
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.