فاز 13 · درس 37

اکوسیستم مهندسی معکوس: تاریخچه، خانواده ابزارها و معماری دیکامپایلرها

نوع
ساختنی
زمان
حدود 150 دقیقه
زبان‌ها
Python, C, x86-64 assembly, Java (one Ghidra script)
پیش‌نیاز
فاز 13 · درس 32 (ساختار ELF از صفر)، فاز 13 · درس 33 (دیس‌اسمبل و جریان کنترل)، فاز 13 · درس 34 (تبدیل به شبه‌کد)
چندین ابزار و لنز مختلف تحلیلی روی میز کار مهندسی که به یک برد الکترونیکی مشترک اشاره دارند و هرکدام بخش‌های متفاوتی را برجسته کرده‌اند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. پنجاه و سه سال تاریخ در گام‌های زمان‌بندی‌شده
  4. از 1973 تا 1991: ابتدا دیباگرها، سپس دیس‌اسمبلرها
  5. از 1991 تا 2009: دیس‌اسمبلرهای تعاملی و نخستین دیکامپایلرها
  6. از 2009 تا 2019: فریم‌ورک‌ها و بازنمایی‌های واسط
  7. از 2019 تا 2026: دیکامپایلرهای متن‌باز و چرخش مسیر پژوهش‌ها
  8. خانواده‌های ابزارها
  9. دیس‌اسمبلرها
  10. دیکامپایلرها
  11. دیباگرها
  12. فریم‌ورک‌های تحلیل باینری
  13. کتابخانه‌های فرمت و موتورهای پردازش
  14. ابزارهای مقایسه و نمایشگرهای سمت کامپایلر
  15. نسخه‌ها، مجوزها و آنچه در این درس اجرا شد
  16. پایپ‌لاین دیکامپایلر، مرحله به مرحله
  17. لودر
  18. دیس‌اسمبل
  19. بازیابی CFG
  20. تبدیل به کد واسط (Lifting)
  21. فرم SSA
  22. جریان داده و تایپ‌ها
  23. ساختاربندی
  24. تولید کد نهایی
  25. یک دستورالعمل در چهار بازنمایی واسط
  26. چهار آدرس پایه برای یک تابع
  27. نقاط اشتراک و افتراق ابزارها
  28. حالت توافق کامل: آبجکت ledger با -O1
  29. حالت برداری: آبجکت ledger با -O2
  30. جدول پرش: تابع grade با -O0
  31. داده در میان کد: تله‌ی sweep-trap
  32. کدهای ساختگی و جاافتاده به تفکیک ابزار
  33. عملکرد سه دیکامپایلر روی grade
  34. پیاده‌سازی از صفر
  35. استفاده از ابزارهای واقعی
  36. کارگاه تعاملی
  37. آزمایش الف: یک تابع، چهار آدرس مبنا
  38. آزمایش ب: بررسی جدول پرش از چهار زاویه
  39. آزمایش ج: فلگ‌های بی‌درنگ در برابر ساختار تانک
  40. آزمایش د: جدول تله در پنج ابزار
  41. تمرین عملی
  42. خروجی این درس
  43. ارزیابی و اعتبارسنجی
  44. حالت‌های شکست در محیط عملیاتی
  45. دستورالعمل اجرایی
  46. برای مطالعه‌ی بیشتر

وقتی یک فایل باینری را به ابزارهای متداول مهندسی معکوس مثل Ghidra، radare2 یا angr می‌دهید، ممکن است با خروجی‌های متناقضی روبه‌رو شوید؛ یکی آدرس تابع را متفاوت اعلام می‌کند، دیگری تعداد بلوک‌های پایه‌ای را کمتر نشان می‌دهد و سومی در مواجهه با یک جدول پرش ساختار کد را گم می‌کند. این تفاوت‌ها اغلب ناشی از اشتباه ابزارها نیست، بلکه به تفاوت در معماری لودر، فرضیات دیس‌اسمبل و مراحل تحلیل جریان کنترل برمی‌گردد.

درک معماری داخلی دیکامپایلرها و پایپ‌لاین چندمرحله‌ای آن‌ها به شما کمک می‌کند متوجه شوید داده‌های خام چگونه به فرم‌های میانی (IR) مانند P-code یا VEX و در نهایت به کد شبیه به C تبدیل می‌شوند. با این شناخت، به جای اعتماد کورکورانه به اعداد و آدرس‌های گزارش‌شده، دلیل هر اختلاف را در گراف جریان کنترل و منطق داخلی ابزارها پیدا می‌کنید.

پنج ابزار یک فایل آبجکت را می‌خوانند و چهار آدرس مختلف، سه شمارش متفاوت برای بلوک‌ها و دو پاسخ متناقض برای یک ساختار switch تحویل می‌دهند؛ در این درس بستر آزمایشی (harness) لازم را می‌سازیم تا نشان دهیم واقعیت ماجرا چیست.

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

  • ابزارهای اصلی این حوزه را روی یک خط زمانی تاریخ‌دار از 1973 تا 2026 قرار دهید و نام خانواده‌ی هر یک را مشخص کنید.
  • مراحل هشت‌گانه‌ی یک دیکامپایلر و اصطلاحی را که Ghidra، Binary Ninja، Hex-Rays، radare2، rizin و angr برای هر مرحله به کار می‌برند نام ببرید.
  • یک دستورالعمل اسمبلی را به ESIL، RzIL، P-code و VEX تبدیل کنید (Lift) و توضیح دهید کدام‌یک فلگ‌ها را بی‌درنگ (eager) می‌نویسند و کدام‌یک یک تانک (thunk) ذخیره می‌کنند.
  • آدرس‌های خروجی پنج ابزار را به یک آفست یکتا در بخش .text تقلیل دهید و صحت این تقلیل را روی یک تابع اثبات کنید.
  • بستر مقایسه‌ای بسازید که خروجی‌های ثبت‌شده‌ی ابزارها را بخواند و میزان توافق روی نقطه‌ی شروع دستورالعمل‌ها، بلوک‌ها، یال‌ها و شروع توابع را گزارش دهد.
  • بر اساس شواهد ثبت‌شده، نشان دهید هر ابزار در کجا کدهای ساختگی سرهم می‌کند، کدهایی را جا می‌اندازد یا جلوی یک پرش غیرمستقیم متوقف می‌شود.

صورت مسئله

از یک ایجنت (Agent) می‌خواهید گزارش سه ابزار مهندسی معکوس را درباره‌ی تابع grade در فایل ledger-x86_64-O1.o خلاصه کند. ایجنت هر ابزار را اجرا می‌کند، سه لیستینگ را می‌خواند و گزارش می‌دهد که تابع در یک ابزار در آدرس 0x08000050، در دیگری در 0x400010 و در سومی در 0x100010 قرار دارد. در نتیجه نتیجه‌گیری می‌کند که این ابزارها روی محل تابع با هم اختلاف نظر دارند.

در واقع ابزارها کاملاً هم‌نظرند. آبجکت از نوع بازآرایی‌پذیر (relocatable) است و آدرس بارگذاری مشخصی ندارد؛ بنابراین هر ابزار آدرس مبنای فرضی خودش را انتخاب می‌کند. آدرسی را که هر ابزار به بخش .text اختصاص داده از آدرس تابع کم کنید؛ پاسخ در تمام آن‌ها دقیقاً 0x10 است. ایجنت آدرس‌های خام را مقایسه کرده بود، چون چیزی به آن تفهیم نکرده بود که این آدرس در هر ابزار چه معنایی دارد.

کمی بعد، همان ایجنت تابع grade را در بیلد -O0 می‌خواند و از چهار ابزار تعداد بلوک‌های پایه‌ایِ 3، 8، 8 و 9 را گزارش می‌کند. هر چهار عدد واقعی هستند: یک ابزار جلوی jmp rax متوقف شده؛ دو ابزار جدول پرش (jump table) را با دنبال کردن بازآرایی‌هایش خوانده‌اند؛ و ابزار چهارم بلوک را در محل پرش شکسته تا یک برچسب (label) بگذارد. شمارش تعداد بلوک‌ها بدون دانستن دلیل پشت آن، عددی بی‌معناست.

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

نقشه میدان1973 تا 2026، هشت مرحله، شش ابزار
Timeline of reverse engineering tools and papers from 1973 to 202643 dated events. Squares are disassemblers and debuggers, diamonds are decompilers, triangles are frameworks, circles are research, diffing, and compiler-side tools.19701980199020002010202020301973 Hollander (Stanford) and Housel (Purdue) write the first decompilation theses [research]1977 adb tutorial at Bell Labs; the debugger ships with UNIX V7 in 1979 [debugger]1981 DEBUG with an 8086 disassembler ships in PC-DOS 1.00 [debugger]1986 GDB enters distribution [debugger]1988 Sourcer commenting disassembler; first GNU binutils snapshots [disassembler]1991 IDA 0.1 released as shareware on 20 May [disassembler, IDA / Hex-Rays]1994 Cifuentes, Reverse Compilation Techniques; the dcc decompiler [decompiler]1997 REC, Reverse Engineering Compiler 1.1 [decompiler]2002 Boomerang open-source decompiler, first code [decompiler]2004 Flake, Structural Comparison of Executable Objects; BinDiff goes on sale [diffing]2006 radare starts as a forensic hex editor [disassembler, radare2]2007 Hex-Rays Decompiler 1.0; Van Emmerik, SSA for decompilation [decompiler, IDA / Hex-Rays]2009 radare2 r2-0.1, the modular rewrite, LGPL-3.0 [framework, radare2]2010 LLDB announced as an LLVM subproject [debugger]2011 BAP (CAV); Google acquires zynamics; Hopper appears [framework]2012 Compiler Explorer starts [compiler-side]2013 angr first line of code; Phoenix decompiler (USENIX Security) [framework, angr]2015 DREAM, No More Gotos (NDSS) [research]2015 Vector 35 founded; Binary Ninja prototype [disassembler, Binary Ninja]2015 angr released publicly under BSD-2-Clause [framework, angr]2016 Binary Ninja on sale with LLIL and a Python API [disassembler, Binary Ninja]2016 BinDiff free of charge; angr SoK paper (IEEE S&P) [diffing]2017 Cutter 1.0, a Qt GUI for radare2 [disassembler, radare2]2017 IDA 7.0 native 64-bit; Binary Ninja MLIL [disassembler, IDA / Hex-Rays]2018 Guilfanov, Decompiler internals: microcode (Recon) [decompiler, IDA / Hex-Rays]2019 Ghidra 9.0 released by NSA under Apache-2.0 [decompiler, Ghidra]2019 angr 8.19.2.4 adds a preliminary decompiler [decompiler, angr]2020 Binary Ninja 2.0 with the HLIL decompiler [decompiler, Binary Ninja]2020 Rizin announced as a fork of radare2; the Cutter team joins [framework, rizin]2021 IDA Freeware 7.6 with a cloud x64 decompiler [decompiler, IDA / Hex-Rays]2021 Cutter 2.0 on rizin; iaito continues for radare2 [disassembler, rizin]2023 BinDiff 8 open-sourced under Apache-2.0 [diffing]2024 SAILR becomes the default angr structurer (USENIX Security) [research, angr]2024 Binary Ninja 4.0 with the Free edition; IDA 9.0 with idalib [disassembler, Binary Ninja]2025 Ghidra 12.0 [decompiler, Ghidra]2025 Binary Ninja 5.0; DecompileBench (ACL Findings) [research, Binary Ninja]2026 Ghidra 12.1.4 (21 Sep) [decompiler, Ghidra]2026 radare2 6.2.4 (5 Oct) [framework, radare2]2026 rizin 0.9.1 and Cutter 2.5.0 (June) [framework, rizin]2026 Binary Ninja 6.0 Krypton (3 Sep) [disassembler, Binary Ninja]2026 IDA 9.4 (13 Jul) [disassembler, IDA / Hex-Rays]2026 angr 10.0.3 (10 Oct) [framework, angr]2026 LLM decompiler studies: readability metrics; recompile more, preserve less [research]یک نشانه برای هر رویداد تاریخ‌دار؛ روی هم قرارگرفته هنگام رویدادهای چندگانه
نشانه‌های آبی و ستون آبی به ابزار انتخاب‌شده تعلق دارند. دکمه‌های خط‌چین ابزارهایی را مشخص می‌کنند که برای این درس اجرا نشدند. خط شواهد گزارش می‌دهد که هارنس موجود در code/main.py برای ابزار انتخاب‌شده روی نمونه‌های درس چه چیزی را اندازه گرفته است: تعداد بلاک‌ها در ledger با سطح بهینه‌سازی O1-، جدول پرش در grade با سطح بهینه‌سازی O0-، بایت‌های داده در sweep-trap، و توابع ابداع‌شده بدون نماد.

پنجاه و سه سال تاریخ در گام‌های زمان‌بندی‌شده

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

از 1973 تا 1991: ابتدا دیباگرها، سپس دیس‌اسمبلرها

نخستین پایان‌نامه‌های حوزه‌ی دیکامپایل مربوط به سال 1973 هستند. هولندر در استنفورد و هوسل در پردو آن‌ها را نوشتند و هر دو امروزه از طریق ارجاعاتشان شناخته می‌شوند. ابزارهای کاربردی آن دوران دیباگرها بودند. مارانزانو و بورن در سال 1977 آموزش adb را در آزمایشگاه‌های بل نوشتند و این دیباگر در سال 1979 همراه با UNIX V7 عرضه شد. ابزار DEBUG تیم پترسون همراه با یک دیس‌اسمبلر 8086، در اوت 1981 با PC-DOS 1.00 منتشر شد. گنو دیباگر (GDB) ریچارد استالمن در سال 1986 وارد مرحله‌ی توزیع شد. SoftICE در سال 1987 یک دیباگر کرنل (Kernel) را به محیط DOS آورد.

سپس نوبت به دیس‌اسمبلرهای دسته‌ای (batch) رسید. ابزار Sourcer از شرکت V Communications در سال 1988، برنامه‌های DOS را در چند دور پردازش می‌کرد و خروجیِ تفکیک‌شده همراه با کامنت تولید می‌کرد. گنو binutils با اسنپ‌شات‌های (Snapshot) بدون شماره در سال 1988 آغاز شد و objdump کپی‌رایت سال 1990 را بر پیشانی دارد. ایلپاک گویلفانوف نخستین خط کد IDA را در ژانویه‌ی 1991 نوشت و IDA 0.1 را در 20 مه 1991 به‌صورت نرم‌افزار اشتراکی (shareware) منتشر کرد.

از 1991 تا 2009: دیس‌اسمبلرهای تعاملی و نخستین دیکامپایلرها

کریستینا سیفوئنتس رساله‌ی «تکنیک‌های ترجمه‌ی معکوس» را در ژوئیه‌ی 1994 در QUT به پایان رساند. دیکامپایلر او، dcc، برنامه‌های i80286 محیط DOS را به C تبدیل می‌کرد؛ مقاله‌ی مشترک او و گاف در سال 1995 مراحلی را پایه‌گذاری کرد که تمام دیکامپایلرهای بعدی به آن وفادار ماندند. در پی آن، REC در 1997 و Boomerang در 2002 به‌عنوان نخستین دیکامپایلر متن‌باز تحت مجوز BSD عرضه شدند.

ابزار IDA در سال 1996 در شرکت DataRescue به محصولی تجاری تبدیل شد، در 1999 کیت توسعه‌ی افزونه (SDK) دریافت کرد و در سال 2005 به Hex-Rays منتقل شد. دیکامپایلر Hex-Rays Decompiler 1.0 در 17 سپتامبر 2007 عرضه شد. در همان سال مایک ون امریک رساله‌ی «فرم انتساب تک‌ایستا برای دیکامپایل» را ارائه داد؛ رساله‌ای که فرم انتساب تک‌ایستا (Static Single Assignment یا SSA) را در کانون تحلیل جریان داده‌ی دیکامپایلرها نشاند. مقاله‌ی هالوار فلیک با عنوان «مقایسه‌ی ساختاری آبجکت‌های اجرایی» (2004) جرقه‌ی خانواده‌ی ابزارهای مقایسه (diffing) را زد و BinDiff در اوت 2004 روانه‌ی بازار شد.

پروژه‌ی radare در فوریه‌ی 2006 به‌عنوان یک ویرایشگر هگز فارنزیک با آفست‌های 64 بیتی کلید خورد. پروژه‌ی radare2، بازنویسی ماژولار آن تحت مجوز LGPL-3.0، نخستین بار در 7 مارس 2009 منتشر شد.

از 2009 تا 2019: فریم‌ورک‌ها و بازنمایی‌های واسط

ابزار LLDB در 8 ژوئن 2010 به‌عنوان یک زیرپروژه‌ی LLVM معرفی شد. BAP یا پلتفرم تحلیل باینری همراه با زبان واسط BIL در کنفرانس CAV 2011 ارائه شد. گوگل در 1 مارس 2011 شرکت zynamics (سازنده‌ی BinDiff) را خرید. Hopper در اواخر 2011 وارد فروشگاه مک اپ استور شد. مت گادبولت در سال 2012 پروژه‌ی Compiler Explorer را راه انداخت تا برنامه‌نویسان C++ بتوانند کد اسمبلی خروجی کامپایلر را ببینند.

نخستین خط کد angr در اوت 2013 نوشته شد. مقاله‌ی Phoenix (شوارتز، لی، وو، براملی، USENIX Security 2013) بازیابی ساختار با حفظ معنا را با تنها 40 دستور goto در میان 8,676 تابع به نمایش گذاشت. مقاله‌ی DREAM (یکدان و دیگران، NDSS 2015) دستورهای goto را با ساختاربندی مستقل از الگو حذف کرد. شرکت Vector 35 در 14 ژانویه‌ی 2015 تأسیس شد و Binary Ninja در 23 ژوئیه‌ی 2016 با زبان واسط Low Level IL و API پایتون عرضه شد. angr در 2015 به‌صورت عمومی منتشر شد و در مسابقه‌ی سایبری DARPA Cyber Grand Challenge در اوت 2016 مقام سوم را کسب کرد. BinDiff در مارس 2016 رایگان شد. Cutter 1.0 در دسامبر 2017 رابط کاربری Qt را به radare2 اضافه کرد. در سال 2018 گویلفانوف ارائه‌ی «سازوکار درونی دیکامپایلر: مایکروکد» را در Recon ارائه کرد که نخستین تشریح عمومی از بازنمایی واسط (IR) در Hex-Rays بود.

از 2019 تا 2026: دیکامپایلرهای متن‌باز و چرخش مسیر پژوهش‌ها

ابزار Ghidra 9.0 در 5 مارس 2019 توسط آژانس امنیت ملی آمریکا (NSA) در کنفرانس RSA تحت مجوز Apache-2.0 منتشر شد و سورس کامل آن یک ماه بعد روی GitHub قرار گرفت. نسخه‌ی 8.19.2.4 ابزار angr در همان سال یک دیکامپایلر مقدماتی اضافه کرد. Binary Ninja 2.0 دیکامپایلر مبتنی بر High Level IL خود را در 20 مه 2020 عرضه کرد. Rizin در 8 دسامبر 2020 به‌عنوان یک fork از radare2 متولد شد و تیم Cutter به آن مهاجرت کردند. نسخه‌ی رایگان IDA Freeware 7.6 در مه 2021 یک دیکامپایلر ابری x64 اضافه کرد.

ابزار BinDiff 8 در 25 سپتامبر 2023 تحت Apache-2.0 متن‌باز شد. ساختاربند SAILR (باسک و دیگران، USENIX Security 2024) به ساختاربند پیش‌فرض angr تبدیل شد و استدلال کرد که فقط باید دستورهای goto اضافی حذف شوند، زیرا لینوکس 6.1 در کد منبع خود 3,754 دستور از این نوع دارد. Binary Ninja 4.0 در فوریه‌ی 2024 یک نسخه‌ی رایگان (Free) معرفی کرد و IDA 9.0 کتابخانه‌ی idalib را برای استفاده بدون رابط گرافیکی ارائه داد. مقالات حوزه‌ی دیکامپایلرهای مبتنی بر یادگیری ماشین از SLaDe (2023) آغاز شد، به LLM4Decompile (2024) و DecompileBench (2025) رسید و با دو مطالعه در 2026 در مورد خوانایی کد با کمک ایجنت‌ها و دیکامپایلرهایی که بیشتر بازکامپایل می‌کنند و کمتر ساختار را حفظ می‌کنند، ادامه یافت.

نسخه‌هایی که این درس اجرا کرده در 14 اکتبر 2026 بررسی شده‌اند: Ghidra 12.1.4 (21 سپتامبر 2026)، radare2 6.2.4 (5 اکتبر 2026)، rizin 0.9.1 (29 ژوئن 2026) و angr 10.0.3 (10 اکتبر 2026). ابزارهای تجاری و غیرمتن‌باز روز عبارت‌اند از Binary Ninja 6.0 "Krypton" (3 سپتامبر 2026) و IDA 9.4 (13 ژوئیه 2026) که این درس آن‌ها را مستقیماً اجرا نکرده است.

خانواده‌های ابزارها

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

دیس‌اسمبلرها

روش پیمایش خطی (linear sweep) بایت‌ها را از ابتدای بخش کد تا انتهای آن بدون وقفه رمزگشایی (Decode) می‌کند. ابزارهای objdump -d گنو و llvm-objdump -d به این شکل کار می‌کنند و هیچ‌کدام گراف جریان کنترل (CFG) نمی‌سازند. ابزار objdump گنو نمادها (symbols) را مرز دستورالعمل در نظر می‌گیرد و بین آن‌ها را پیمایش می‌کند. نسخه‌ی binutils 2.47 (26 ژوئیه 2026) تحت GPL-3.0-or-later است. نسخه‌ی LLVM 23.1.3 تحت مجوز Apache-2.0 با استثنای LLVM منتشر شده است. بسته‌ی Command Line Tools اپل llvm-objdump را با عنوان "Apple LLVM version 17.0.0" ارائه می‌دهد که داده‌های این درس از روی همین نسخه ثبت شده‌اند.

یک دیس‌اسمبلر تعاملی، روش فرود بازگشتی (recursive descent) را از نقاط ورودی شناخته‌شده اجرا می‌کند، برای تشخیص آغاز توابع از روش‌های اکتشافی (heuristics) کمک می‌گیرد و یک پایگاه داده نگه می‌دارد. IDA Pro (ماژول‌های پردازنده، صف‌های تحلیل خودکار، امضاهای FLIRT)، Ghidra (کلاس Disassembler به همراه تحلیل‌گرهای Auto Analysis)، Binary Ninja (فرود بازگشتی و سپس پیمایش خطی روی نواحی نامشخص)، Hopper، radare2 و rizin (دستورهای aa، aaa، af) و Cutter و iaito به‌عنوان واسط گرافیکی، همگی در این خانواده قرار دارند. آندریسه و همکارانش (USENIX Security 2016) نه مورد از این ابزارها را روی باینری‌های کامپایل‌شده سنجیدند. دقت تشخیص دستورالعمل‌ها نزدیک به 100 درصد بود، اما تشخیص نقطه‌ی آغاز توابع بسیار چالش‌برانگیز بود و نرخ منفی کاذب (false negative) آن به 20 درصد یا بیشتر می‌رسید.

دیکامپایلرها

دیکامپایلرهای تجاری: Hex-Rays Decompiler (برای دوازده معماری از x86 تا RISC-V)، سطح HLIL در Binary Ninja به همراه نمایش شبه‌کد C، و نمای شبه‌کد در Hopper. دیکامپایلرهای متن‌باز: دیکامپایلر Ghidra، یک موتور C++ تحت Apache-2.0 که در r2ghidra و rz-ghidra به‌عنوان افزونه و در pypcode به‌عنوان لیفتر استفاده می‌شود؛ دیکامپایلر angr بر پایه‌ی AIL با ساختاربندی SAILR تحت مجوز BSD-2-Clause؛ پروژه‌های r2dec و jsdec در جاوااسکریپت؛ و نمونه‌های تاریخی Boomerang و dcc. ابزار radare2 یک دستور داخلی به نام pdc هم دارد: «خروجی شبه‌دیس‌اسمبل با نحو شبیه به C». ابزار rizin دیکامپایلر داخلی ندارد؛ دستور pdc در rizin 0.9.1 وجود ندارد و اجرای pdg به افزونه‌ی rz-ghidra وابسته است.

دیباگرها

ابزار GDB (1986) همراه با فرانت‌اندهای GEF و pwndbg، ابزار LLDB (2010)، WinDbg با قابلیت Time Travel Debugging (2017)، ابزار OllyDbg (توقف توسعه در 2014)، x64dbg (مجوز GPL-3.0، 2014)، دیباگر Ghidra با اتصال‌دهنده‌های (Connector) پایتون، دیباگر Binary Ninja و دیباگرهای داخلی radare2 و rizin. در درس 42 از دو مورد از آن‌ها روی کدهای خودتان استفاده می‌کنید.

فریم‌ورک‌های تحلیل باینری

فریم‌ورک angr (زبان واسط VEX، اجرای نمادین، تحلیل‌های CFGFast و CFGEmulated، دیکامپایلر)، ابزار BAP (زبان واسط BIL، OCaml)، Dyninst، Jakstab و ByteWeight چارچوب‌هایی هستند که در مقاله‌ی آندریسه نام برده شده‌اند. ابزار Ghidra از طریق PyGhidra، ابزار Binary Ninja از طریق import binaryninja و IDA از طریق idalib به‌صورت کتابخانه‌ای قابل استفاده هستند.

کتابخانه‌های فرمت و موتورهای پردازش

کتابخانه‌ی LIEF 1.0.0 (12 ژوئیه 2026، Apache-2.0) فرمت‌های ELF، PE، Mach-O، COFF و فرمت‌های اندروید را پارس می‌کند. کتابخانه‌ی CLE مخفف "CLE Loads Everything"، لودر اختصاصی angr است. Capstone 5.0.9 (28 مه 2026، BSD-3-Clause) رمزگشایی (Decode) می‌کند؛ Keystone 0.9.2 (GPL-2.0) اسمبل می‌کند؛ Unicorn 2.1.4 (GPL-2.0) شبیه‌سازی اجرا انجام می‌دهد. radare2 6.2.4 شامل Capstone 5 است و rizin 0.9.1 کتابخانه‌های Capstone، Zydis و tree-sitter را در خود جای داده است.

ابزارهای مقایسه و نمایشگرهای سمت کامپایلر

ابزار BinDiff 8 (Apache-2.0) با افزونه‌ی BinExport 12 برای IDA Pro، Binary Ninja و Ghidra؛ ابزار Diaphora 3.4.3 (11 اکتبر 2026، AGPL-3.0) به‌عنوان افزونه‌ی IDA؛ ابزارهای radiff2 و rz-diff. وب‌سایت Compiler Explorer (BSD-2-Clause) کد اسمبلی تولیدشده توسط کامپایلر را برای یک قطعه کد منبع نشان می‌دهد که دقیقاً برعکسِ مسیر تمام ابزارهای این لیست است. درس 44 به خانواده‌ی مقایسه‌گرها اختصاص دارد.

نسخه‌ها، مجوزها و آنچه در این درس اجرا شد

ابزار نسخه‌ی بررسی‌شده در 2026-10-14 مجوز ورود بدون محیط گرافیکی اجرا در اینجا
Ghidra 12.1.4 Apache-2.0 analyzeHeadless، pyghidra.start() بله، روی هر چهار آبجکت
radare2 6.2.4 LGPL-3.0 r2 -qc '<cmds>' file، rabin2 بله
rizin 0.9.1 LGPL-3.0 rizin -qc '<cmds>' file، rz-bin بله
angr 10.0.3 BSD-2-Clause angr.Project در پایتون بله
llvm-objdump Apple LLVM 17.0.0 Apache-2.0 با استثنای LLVM llvm-objdump -d -r بله
Binary Ninja 6.0 "Krypton" تجاری؛ نسخه‌ی Free برای استفاده‌ی غیرتجاری binaryninja.load() با لایسنس Commercial، Ultimate یا headless خیر، صرفاً بررسی مستندات
IDA Pro 9.4 اشتراک تجاری؛ نسخه‌ی IDA Free برای x86 و x86-64 غیرتجاری idat، ida -A -S، idalib خیر، صرفاً بررسی مستندات
Hopper 6.6.0 تجاری، دائمی ثبت‌نشده در مستندات خیر، صرفاً بررسی مستندات
Cutter 2.5.0 GPL-3.0 واسط گرافیکی روی rizin خیر

نسخه‌ی رایگان Binary Ninja فقط کدهای x86، x86-64، ARMv7 و AArch64 را دیکامپایل می‌کند و API ندارد. نسخه‌ی رایگان IDA فاقد IDAPython، فاقد SDK و فاقد idalib است و دیکامپایلر آن در فضای ابری اجرا می‌شود. تمام نکات ذکرشده در مورد این دو ابزار بر اساس مستندات و گزارش تغییرات رسمی آن‌هاست و در نمودارها نیز با برچسب عدم اجرا مشخص شده‌اند.

پایپ‌لاین دیکامپایلر، مرحله به مرحله

سیفوئنتس در سال 1994 این مراحل را نام‌گذاری کرد: تحلیل نحو (syntax analysis)، تحلیل معنایی (semantic analysis)، تولید کد واسط، تولید گراف جریان کنترل، تحلیل جریان داده، تحلیل جریان کنترل و تولید کد نهایی. او آن‌ها را به بخش فرانت‌اند وابسته به ماشین، بخش میانی مستقل از ماشین و زبان، و بخش بک‌اند وابسته به زبان تقسیم کرد. تمام دیکامپایلرهای بعدی، همین چارچوب را حفظ کردند و فقط نام اجزا را تغییر دادند. جدول زیر مراحل هشت‌گانه‌ی این درس و نام معادل آن را در هر ابزار نشان می‌دهد. در تصویر تعاملی، ستون‌های این جدول قابل انتخاب هستند.

مرحله Ghidra Binary Ninja Hex-Rays / IDA radare2 rizin angr
لودر ElfLoader، MachoLoader، PeLoader؛ تعیین زبان با .ldefs BinaryView، BinaryViewType ماژول لودر (LDSC) RBin (rabin2) RzBin (rz-bin) CLE (cle.Loader، main_object)
دیس‌اسمبل Disassembler؛ کامپایل .slaspec مربوط به SLEIGH به .sla؛ تحلیل‌گرهای "Disassemble Entry Points"، "Function Start Search" پلاگین معماری؛ فازهای Recursive Descent و سپس linear sweep ماژول پردازنده (ana.cpp، emu.cpp، out.cpp)؛ صف‌های تحلیل خودکار؛ FLIRT پلاگین RArch به RAnalOp؛ دستورهای aa، aaa، af پلاگین RzAnalysis؛ دستورهای aa، aaa، af بلوک‌های Capstone؛ جستجوی آغاز تابع با CFGFast
بازیابی CFG "Generate Basic Blocks and the CFG"؛ BlockGraph؛ "Decompiler Switch Analysis" Function، BasicBlock؛ فاز "Control Flow Graph (CFG) Analysis" mbl_graph_t، آماده در MMAT_LOCOPT afb، agf، aab afb، agf، aab (موتور Nucleus) CFGFast، CFGEmulated؛ kb.functions؛ IRSB
تبدیل به کد واسط (Lifting) "Generate Raw P-code"؛ ورنودها (Varnodes) در فضاهای ram، register، const، unique Lifted IL، LLIL، Mapped MLIL، MLIL مایکروکد (minsn_t، mop_t)؛ وضعیت MMAT_GENERATED ESIL (asm.esil، ae) RzIL (aoi، aez) ابتدا VEX (pyvex.lift، IRSB) سپس AIL
فرم SSA Heritage، ActionHeritage؛ عملگرهای MULTIEQUAL، INDIRECT سطوح LLIL SSA، MLIL SSA، HLIL SSA؛ عملگر HLIL_VAR_PHI فرم صریحی با نام SSA ندارد؛ تحلیل جریان use-def در سطوح MMAT_GLBOPT1 تا 3 ندارد؛ ESIL برای شبیه‌سازی طراحی شده است ندارد؛ RzIL برای شبیه‌سازی طراحی شده است فازهای _stage_transform_to_ssa_level0 و level1؛ SeqNodeDephication
جریان داده و تایپ‌ها حلقه‌ی ActionDeadCode، ActionInferTypes و مجموعه‌ی Rule؛ استخراج پروتوتایپ از .cspec انتشار مقادیر ثابت، متغیرها و تایپ‌ها در MLIL؛ مرحله‌ی analyzeReturns مرحله‌ی "Transform microcode"؛ سطوح MMAT_CALLS، MMAT_GLBOPT، MMAT_LVARS؛ اطلاعات TIL دستورهای afv، afc، aaft؛ شبیه‌سازی با ESIL دستورهای afv، afc، aft ابزارهای CallingConventionAnalysis، VariableRecoveryFast، Typehoon
ساختاربندی (Structuring) ماژول CollapseStructure؛ بلوک‌های BlockIf، BlockWhileDo، BlockSwitch، BlockGoto ساختارهای HLIL_IF، HLIL_WHILE، HLIL_SWITCH؛ درخت AST در root ساخت "ctree"؛ تحلیل ساختاری؛ نواحی goto pdc؛ pdd (در r2dec)؛ pdg (در r2ghidra) pdg (در rz-ghidra)؛ jsdec ابزار RegionIdentifier؛ RecursiveStructurer همراه با SAILRStructurer
تولید کد نهایی مرحله‌ی "Emit Final C Tokens"؛ ماژول‌های PrintC، ActionNameVars، ActionSetCasts رندر کردن HLIL؛ زبان‌های Pseudo C، Pseudo Rust، Pseudo Python مراحل "Beautify ctree; Print ctree"؛ نوع cfunc_t متن خروجی pdc، pdd، pdg متن خروجی pdg ماژول CStructuredCodeGenerator؛ خروجی codegen.text

لودر

لودر ساختار فایل را پارس می‌کند، بخش‌ها (sections) را نگاشت می‌دهد و نمادها و بازآرایی‌ها را می‌خواند. در یک فایل آبجکتِ بازآرایی‌پذیر هنوز هیچ بخشی آدرس نهایی ندارد، بنابراین لودر یک آدرس مبنا انتخاب می‌کند. radare2 و rizin فایل را در 0x08000000 نگاشت می‌دهند؛ در نتیجه بخش .text در 0x08000040 می‌نشیند، چون این بخش از آفستِ فایلی 0x40 شروع می‌شود. CLE بخش .text را در 0x400000 قرار می‌دهد. لودر ElfLoader در Ghidra آن را در 0x100000 بارگذاری می‌کند. ابزار llvm-objdump فقط آفست‌های درون بخش را چاپ می‌کند و اصلاً آدرس پایه‌ای در نظر نمی‌گیرد.

لودرها در اعمال بازآرایی‌ها نیز تفاوت دارند. Ghidra آن‌ها را در زمان ایمپورت اعمال می‌کند و CLE هم آن‌ها را پردازش می‌کند. radare2 هنگام باز کردن آبجکت اخطار می‌دهد که بازآرایی‌ها اعمال نشده‌اند و تغییر تنظیم به bin.relocs.apply=true رفتار آن را عوض می‌کند. رفتار لودر در برخورد با بازآرایی‌ها تعیین می‌کند که مراحل بعدی چه داده‌هایی را می‌بینند؛ اثری که در جدول پرش کامپایل -O0 به‌وضوح خواهید دید.

دیس‌اسمبل

در درس 33 هر دو روش را پیاده‌سازی کردیم. تمام ابزارهای جدول به‌جز llvm-objdump ابتدا فرود بازگشتی را اجرا می‌کنند و سپس جستجویی برای کشف آغاز توابع انجام می‌دهند. مستندات Binary Ninja مراحل را به این ترتیب نام می‌برد: فرود بازگشتی، تحلیل اهداف فراخوانی، تحلیل CFG به‌صورت «دیس‌اسمبل خطیِ نواحی نامشخص» و تحلیل فراخوانی‌های دم‌دستی (Tail Call). ابزار Ghidra آنالایزرهای خود را طبق اولویت اجرا می‌کند و آنالایزرهای "Disassemble Entry Points" و "Function Start Search" در آغاز کارند. ابزار IDA صف‌هایی با اولویت‌های مشخص برای تحلیل خودکار دارد و امضاهای FLIRT را برای کدهای کتابخانه‌ای استفاده می‌کند. تحلیل CFGFast در angr چهار منبع برای یافتن شروع توابع ذکر می‌کند: نمادها، اسکن پرولوگ، مقاصد فراخوانی و اسکن کامل بایت‌های باقی‌مانده. angr دقیقاً در همین منبع چهارم است که روی پدینگ‌ها (حاشیه‌گذاری) توابع ساختگی ایجاد می‌کند.

بازیابی CFG

هر ابزار بلوک‌ها را مشابه روش درس 33 می‌شکند، با یک تفاوت که آن را اندازه خواهید گرفت: angr بلوک را با رسیدن به دستور call پایان می‌دهد، چون بلوک IRSB در آن یک ورودی و چند خروجی دارد و هر فراخوانی یک خروج محسوب می‌شود. اما Ghidra، radare2 و rizin از روی دستور call عبور می‌کنند. در تابع grade_label با بهینه‌سازی -O0 که تابع grade را فراخوانی می‌کند، angr دو بلوک گزارش می‌دهد اما سه ابزار دیگر 1 بلوک شناسایی می‌کنند؛ درحالی‌که دستورالعمل‌ها کاملاً یکسان هستند.

تبدیل به کد واسط (Lifting)

Binary Ninja تبدیل به کد واسط را این‌گونه تعریف می‌کند: «ترجمه از یک زبان سطح پایین‌تر به زبان واسطی که سطح‌بالاتر محسوب می‌شود». بازنمایی‌های واسط در هدف طراحی با یکدیگر تفاوت دارند. P-code و VEX برای تحلیل و دیکامپایل ساخته شده‌اند. ESIL و RzIL برای شبیه‌سازی طراحی شده‌اند و مرحله‌ی SSA ندارند. مایکروکد Hex-Rays با کدی شبیه به RISC و دارای 72 آپ‌کد شروع می‌شود و از سطوح تکاملی مختلف، از MMAT_GENERATED تا MMAT_LVARS عبور می‌کند. Binary Ninja کد را به Lifted IL برمی‌گرداند، سپس در سطح LLIL فلگ‌ها را در قالب شرط‌ها ترکیب می‌کند و سطح MLIL آن «ثبات‌ها و دسترسی‌های حافظه را به متغیر تبدیل می‌کند». در بخش بعد، این چهار زبان واسط را روی یک دستورالعمل در کنار هم بررسی می‌کنیم.

فرم SSA

پایان‌نامه‌ی ون امریک مزایای استفاده از SSA را برای دیکامپایلر برمی‌شمارد: انتشار عبارات، شناسایی پارامترها و مقادیر بازگشتی بدون نیاز به پیش‌فرض‌های قرارداد فراخوانی (ABI)، تحلیل تغییرناپذیری متغیرها، تحلیل تایپ‌ها در قالب یک مسئله‌ی تنک جریان داده، و تحلیل پرش‌ها و فراخوانی‌های غیرمستقیم. Ghidra بخش ساخت فرم SSA خود را Heritage می‌نامد و دو عملگر جدید اضافه می‌کند که فقط در گراف وجود دارند: MULTIEQUAL برای تابع فی (phi) و INDIRECT برای اثرات جانبی. ابزار Binary Ninja در تمام سطوح IL فرم SSA را ارائه می‌دهد. ابزار angr دو مرحله‌ی SSA درون ماژول Clinic دارد. Hex-Rays فرم مجزایی با نام SSA ندارد و تحلیل use-def را در گام‌های بهینه‌سازی سراسری انجام می‌دهد. ESIL و RzIL این مرحله را ندارند.

جریان داده و تایپ‌ها

حلقه‌ی اصلی ساده‌سازی در Ghidra شامل "Eliminate Dead Code"، "Propagate Local Types" و "Perform Term Rewriting" است که تا رسیدن به حالت پایدار تکرار می‌شود و بازنویسی عبارات با مجموعه‌ای از اشیای Rule انجام می‌گیرد. موتور Hex-Rays مایکروکد را تبدیل می‌کند، آرگومان‌های توابع را در MMAT_CALLS تشخیص می‌دهد و متغیرهای محلی را در MMAT_LVARS تخصیص می‌دهد. ابزار angr فازهای CallingConventionAnalysis، VariableRecoveryFast و Typehoon (پیاده‌سازی سیستم استنتاج چندریختی نونان، لاگینوف و کوک) را اجرا می‌کند. درس 41 به این مرحله اختصاص دارد.

ساختاربندی

ماژول CollapseStructure در Ghidra زیرگراف‌های منطبق بر یک الگو را پیدا می‌کند و آن‌ها را در یک گره ادغام می‌کند؛ اگر در جایی متوقف شود، یالی را حذف کرده و آن را ساختارنیافته علامت می‌زند. Hex-Rays گراف CFG را به ساختارهای if، while، do و switch تجزیه می‌کند و باقی‌مانده را به‌عنوان نواحی دارای goto رها می‌کند. ابزار angr نواحی تک‌ورودی تک‌خروجی را شناسایی کرده و به‌طور پیش‌فرض با SAILRStructurer و در صورت نیاز با PhoenixStructurer یا DreamStructurer آن‌ها را ساختاربندی می‌کند. در درس 34 یک ساختاربند را به‌صورت دستی پیاده کردیم.

تولید کد نهایی

ابزار Ghidra پس از فازهای ActionNameVars و ActionSetCasts توکن‌های C را از طریق PrintC با ابزار مرتب‌ساز اوپن (Oppen pretty-printer) تولید می‌کند. Hex-Rays درخت ctree را مرتب کرده و خروجی می‌دهد. ابزار Binary Ninja سطح HLIL را در قالب‌های Pseudo C، Pseudo Rust یا Pseudo Python رندر می‌کند. ابزار angr متن را در codegen.text برمی‌گرداند. ابزار radare2 خروجی دستورهای pdc، pdd یا pdg را چاپ می‌کند.

یک دستورالعمل در چهار بازنمایی واسط

بایت‌های 48 83 ec 10 یعنی subq $0x10, %rsp را در نظر بگیرید. طبق قواعد درس 33 آن را به شکل دستی رمزگشایی کنید:

  1. بایت 48 یک پیشوند REX با بیت W = 1 است؛ پس عملوند 64 بیتی است.
  2. بایت 83 آپ‌کد محاسباتی گروه 1 با یک مقدار فوری (immediate) 8 بیتی است. فیلد reg در ModRM نوع عملیات را تعیین می‌کند.
  3. بایت ec بایت ModRM معادل 11 101 100 است. مقدار mod = 11 حالت کار با ثبات است. مقدار reg = 101 برابر با کد عملگر /5 یعنی sub است. مقدار r/m = 100 ثبات %rsp را مشخص می‌کند.
  4. بایت 10 همان مقدار فوری یعنی عدد 16 است.

این چهار بایت به چهار لیفتر تحویل داده شد. دستورات دقیق و متن کامل در فایل‌های تست ثبت شده‌اند.

خروجی ESIL در radare2 6.2.4 یک رشته‌ی جداشده با کاما شامل 30 توکن است:

16,0x8000000000000000,-,!,16,rsp,-=,63,$o,^,of,:=,63,$s,sf,:=,$z,zf,:=,$p,pf,:=,64,$b,cf,:=,4,$b,af,:=

این مدل یک ماشین مبتنی بر پشته است. عبارت 16,rsp,-= تفریق را درجا انجام می‌دهد. سپس تک‌تک فلگ‌ها با یک عملگر داخلی مقداردهی می‌شوند: $o برای سرریز، $s برای علامت، $z برای صفر، $p برای توازن، و $b برای قرض گرفتن.

خروجی RzIL در rizin 0.9.1 یک S-expression با 14 عملگرِ set است:

(seq (set op1 (var rsp)) (set op2 (bv 64 0x10)) (set sub (- (var op1) (var op2))) (set rsp (var sub))
     (set _result (var sub)) (set pf (! (lsb (let _val (cast 8 false (var _result)) ...))))
     (set zf (is_zero (var _result))) (set sf (msb (var _result))) ... (set cf (|| ...)) (set of (|| ...)) (set af (|| ...)))

هر فلگ یک عبارت بولی روی بردار بیت‌هاست. فلگ توازن به‌تنهایی یک عبارت let چهار سطحی تولید کرده است.

خروجی P-code از مشخصات SLEIGH به شناسه x86:LE:64:default از طریق pypcode 4.0.1، بعد از شناسه IMARK شامل 9 عملیات است:

CF = RSP < 0x10
OF = sborrow(RSP, 0x10)
RSP = RSP - 0x10
SF = RSP s< 0x0
ZF = RSP == 0x0
unique[58300:8] = RSP & 0xff
unique[58400:1] = popcount(unique[58300:8])
unique[58500:1] = unique[58400:1] & 0x1
PF = unique[58500:1] == 0x0

انتقال‌های بین ثبات‌ها با متغیرهای موقت در فضای unique. فلگ‌ها بلافاصله در محل دستور و به‌صورت بی‌درنگ محاسبه می‌شوند و فلگ‌های CF و OF پیش از آن‌که تفریق مقدار RSP را بازنویسی کند، محاسبه می‌گردند.

خروجی VEX در pyvex 10.0.3 شامل 7 عبارت بعد از IMark است:

t2 = GET:I64(offset=48)
t0 = Sub64(t2,0x0000000000000010)
PUT(offset=144) = 0x0000000000000008
PUT(offset=152) = t2
PUT(offset=160) = 0x0000000000000010
PUT(offset=168) = 0x0000000000000000
PUT(offset=48) = t0

هیچ فلگی در این خروجی دیده نمی‌شود. VEX ثبات‌ها را با آفست آن‌ها در وضعیت ماشین مهمان مشخص می‌کند و آفست 48 همان rsp است. آفست‌های 144 تا 168 مربوط به cc_op، cc_dep1، cc_dep2 و cc_ndep هستند. لیفتر کد عملیات 8 (که نشان‌دهنده‌ی تفریق 64 بیتی است) و دو عملوند را ذخیره می‌کند. اگر انشعابی در ادامه نیاز به فلگ داشته باشد، مقدار آن را از این تانک محاسبه خواهد کرد. به همین دلیل در گزارش بستر ما، مقدار pcode.writesFlags برابر با true و vex.computesFlags برابر با false است، و VEX کوتاه‌ترین خروجی را بین این چهار فرمت دارد.

اگر همین چهار لیفتر روی بایت‌های 48 63 c7 مربوط به movslq %edi, %rax (نخستین دستور تابع grade در بهینه‌سازی -O1) اجرا شوند: 5 توکن ESIL (عبارت 32,edi,~,rax,=)، 1 عملگر set در RzIL، 1 عملیات P-code (عبارت RAX = sext(EDI)) و 4 عبارت در VEX تولید می‌کنند. هیچ لیفتری فلگی ثبت نمی‌کند، چون این دستور هیچ فلگی را تغییر نمی‌دهد. لیستینگ خود Ghidra برای این دستور که از طریق اسکریپت headless ثبت شده، همین عمل را به‌صورت (register, 0x0, 8) INT_SEXT (register, 0x38, 4) چاپ می‌کند: آفست 0 ثبات RAX، آفست 0x38 ثبات RDI، و اندازه‌ها به ترتیب 8 و 4 بایت هستند.

دستور 83 f9 04 معادل cmpl $0x4, %ecx که مربوط به بررسی محدوده در تابع grade است، 30 توکن در ESIL، 13 عملگر set در RzIL، 10 عملیات در P-code و 7 عبارت در VEX با مقدار cc_op برابر با 7 (تفریق 32 بیتی) تولید می‌کند. هیچ مقداری در %ecx نوشته نمی‌شود. دو اندازه‌ی تفریق که در VEX خواهید دید 7 برای 32 بیت و 8 برای 64 بیت است، و مترجمی که می‌سازید این جدول را در خود خواهد داشت.

چهار آدرس پایه برای یک تابع

پیش از آنکه بستر ما بتواند چیزی را مقایسه کند، باید مشخص شود هر تابع کجاست. این محاسبه را یک بار به‌صورت دستی برای تابع grade در ledger-x86_64-O1.o انجام دهید:

ابزار آدرس بخش .text آدرس تابع grade آفست محاسبه‌شده
llvm-objdump 0x0 0x10 0x10 - 0x0 = 0x10
radare2، rizin 0x08000040 0x08000050 0x08000050 - 0x08000040 = 0x10
angr 0x400000 0x400010 0x400010 - 0x400000 = 0x10
Ghidra 0x100000 0x100010 0x100010 - 0x100000 = 0x10

جدول نمادها نشان می‌دهد که grade در آفست .text + 0x10 با طول 46 بایت قرار دارد. پس از کسر آدرس مبنا، تمام ابزارها هم‌نظر هستند. بستر تست آدرس مبنا را از خروجی ثبت‌شده‌ی هر ابزار می‌خواند: خروجی iSj در radare2 و rizin، ورودی بخش .text در خروجی angr، بلوک حافظه‌ی .text در خروجی Ghidra، و مقدار صفر برای روش پیمایش خطی. تست test_every_tool_places_grade_at_offset_0x10_from_its_own_base درستی این مبنا را می‌سنجد.

نقاط اشتراک و افتراق ابزارها

بستر آزمایشی موجود در code/main.py خروجی‌ها را می‌خواند، آدرس‌ها را تقلیل می‌دهد و مقایسه می‌کند. در ادامه اعدادی که گزارش می‌کند آمده و تست‌ها صحت تک‌تک آن‌ها را می‌سنجند.

حالت توافق کامل: آبجکت ledger با -O1

چهار تابع، 56 دستور در پیمایش خطی (54 دستور در محدوده‌ی نمادها و 2 دستور در پدینگ). تمام ابزارها نقاط شروع یکسانی را برای دستورات در هر تابع گزارش می‌کنند: 9، 14، 13 و 18 دستور. چهار ابزار سازنده‌ی گراف، 3، 3، 5 و 4 بلوک و همچنین 2، 3، 5 و 4 یال را ثبت می‌کنند؛ مجموعه‌ی یال‌ها جفت به جفت میان radare2، rizin، angr و Ghidra کاملاً یکسان است. پنج بلوک و پنج یال مربوط به تابع total دقیقاً همان مواردی هستند که در درس 33 با دست رسم کردیم.

تنها اختلاف در بخش پدینگ است. فاصله‌ی 3 بایتی میان توابع (66 90 بعد از grade، و 90 بعد از total) توسط پیمایش خطی رمزگشایی می‌شود، چون دلیلی برای توقف ندارد؛ angr نیز آن‌ها را رمزگشایی کرده و دو تابع ساختگی بدون نماد به نام‌های sub_40003e و sub_40005f می‌سازد. radare2، rizin و Ghidra هیچ ادعایی روی این بایت‌ها مطرح نمی‌کنند.

حالت برداری: آبجکت ledger با -O2

تابع total در هر چهار سازنده‌ی گراف، شامل 41 دستور در 10 بلوک و 13 یال است. حلقه‌ی SSE2 و حلقه‌ی اسکالر باقی‌مانده از درس 33 در تمام ابزارها یکدست شکسته شده‌اند. angr در اینجا هم دو تابع ساختگی در پدینگ اضافه می‌کند.

جدول پرش: تابع grade با -O0

در اینجا ابزارها از هم جدا می‌شوند و اصل درس در همین نقطه است.

ابزار تعداد بلوک‌ها تعداد یال‌ها داده‌ی ثبت‌شده از ابزار
radare2 3 2 بلوک واقع در 0x62 خروجی ندارد؛ بایت‌های 0x76 تا 0x9a اصلاً تحلیل نشده‌اند؛ 8 نقطه‌ی شروع دستور جا افتاده است
rizin 8 11 بلوک واقع در 0x62 شامل یک switch_op با 5 حالت و 4 مقصد مجزاست
angr 8 7 بلوک‌های مربوط به هر حالت ایجاد شده‌اند، اما انتقال در 0x62 به یک گره‌ی مقصد غیرقابل‌حل می‌رسد
Ghidra 9 12 برچسب switchD در 0x74 بلوک را می‌شکند؛ چهار مسیر COMPUTED_JUMP به آدرس‌های 0x76، 0x7f، 0x88 و 0x91 وجود دارد

هر چهار ابزار در مواجهه با دستورات، 33 دستورالعمل یکسان را رمزگشایی کردند. radare2 جلوی دستور jmp rax متوقف شد و 36 بایت را پردازش‌نشده رها کرد؛ بستر مقایسه این بازه را کد جاافتاده (missed code) می‌نامد. rizin و Ghidra چهار مقصد متمایز را پیدا کردند که ما در درس 33 از روی پنج بازآرایی در بخش .rela.rodata خوانده بودیم. ابزار angr بلوک‌های مربوط به caseها را درون تابع grade فهرست کرده اما یالی از بلوک پرش به آن‌ها وصل نکرده است؛ خروجی ثبت‌شده از آن نشان می‌دهد انتقال کنترل در 0x62 به یک گره مقصد غیرقابل‌حل ختم شده و دیکامپایلرش یک دستور goto محاسباتی چاپ کرده است. ابزار Ghidra به دلیل تحلیل سوییچ، یک برچسب روی jmp rax قرار داده و مدل بلوک‌بندی آن بلوک جدیدی در محل برچسب ایجاد می‌کند؛ بنابراین با وجود جریان کنترل یکسان، یک بلوک و یک یال بیشتر از rizin دارد.

گراف درس 33 دارای 8 بلوک و 11 یال بود و خروجی rizin دقیقاً با آن مطابقت دارد. نکته‌ی مهم این است که اعداد 9، 11، 7 و 2 نشان‌دهنده‌ی تفاوت در حجم کد شناسایی‌شده نیستند؛ بلکه چهار رفتار متفاوت در مواجهه با یک پرش غیرمستقیم و نحوه‌ی قطعه‌بندی بلوک‌ها را نشان می‌دهند.

داده در میان کد: تله‌ی sweep-trap

تابع lookup کلاً 24 بایت است که 4 بایت جدول داده شامل 31 c0 90 48 در آفست 0x10 آن قرار دارد. پیمایش خطی در این بازه 8 نقطه‌ی شروع دستور گزارش می‌کند. بستر ما تفاوت‌های آن با اکثریت ابزارها را فهرست می‌کند: شروع‌های اضافی در 0x10، 0x12 و 0x13، و یک شروع ناموجود در 0x14. این همان دستور addq $0x1, %rax درس 33 است که یک بایت زودتر خوانده شده است. radare2، rizin و Ghidra تعداد 6 شروع، 2 بلوک و 1 یال گزارش می‌کنند و بازه‌ی 0x10 تا 0x14 را بدون پردازش باقی می‌گذارند. angr برای تابع lookup همان 2 بلوک و 1 یال را تشخیص می‌دهد، اما روی بایت‌های جدول یک تابع ساختگی به نام sub_400010 می‌سازد: 2 دستورالعمل در یک بلوک 4 بایتی که در 0x14 تمام می‌شود؛ یعنی درست جایی که بلوک دوم lookup آغاز می‌شود، و در نتیجه بایت REX در 0x13 به هیچ دستوری تعلق نمی‌گیرد. نتیجه‌گیری نهایی بستر ما برای این آبجکت این است: چون هیچ سازنده‌ی گرافی بازه‌ی 0x10 تا 0x14 را رمزگشایی نکرده، این بایت‌ها درون تابع قرار دارند و ماهیت داده‌ای دارند، نه کد.

کدهای ساختگی و جاافتاده به تفکیک ابزار

در میان این چهار آبجکت، بستر آزمایشی 9 تابع ساختگی و بدون نماد را شناسایی می‌کند که همگی توسط angr و روی پدینگ‌ها یا جدول داده ایجاد شده‌اند. یک بازه نیز توسط radare2 جا افتاده است که شامل 36 بایت مربوط به caseهای تابع grade با بهینه‌سازی -O0 است. همچنین 9 بازه توسط پیمایش خطی رمزگشایی شده که هیچ ابزار مبتنی بر گرافی آن‌ها را کد در نظر نگرفته است: هشت فاصله‌ی پدینگ به‌اضافه‌ی جدول داده در فایل sweep-trap. هیچ تابعی در هیچ ابزاری جا نیفتاده است.

عملکرد سه دیکامپایلر روی grade

سه ابزار تست‌شده برای تابع grade با بهینه‌سازی -O1 خروجی شبیه به C تولید می‌کنند. کد اصلی شامل switch (score / 10) با پنج حالت است که به یک بررسی محدوده و یک جدول از مقادیر بازگشتی کامپایل شده است.

خروجی Ghidra 12.1.4:

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

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

خروجی angr 10.0.3:

extern unsigned int g_4000b4[4];

unsigned int grade(int a0)
{
    unsigned int v1;  // ecx
    unsigned int v2;  // eax

    v1 = (unsigned int)((long long)(a0 * 1717986919) >> 0x22) + (unsigned int)(a0 * 1717986919 >> 63) - 6;
    v2 = 0;
    if (v1 <= 4)
        v2 = g_4000b4[v1];
    return v2;
}

خروجی pdc در radare2 6.2.4:

void sym.grade (int64_t arg1) {
    rax = edi
    rax = rax * 0x66666667
    rcx = rax
    rcx >>= 0x22
    rax >>>= 0x3f
    ecx += eax
    ecx += 0xfffffffa
    eax = 0
    v = ecx - 4
    if (((unsigned) v) <= 0) {
        eax = ecx
        rcx = rip
        eax = dword [rcx + rax*4]
    }
    return
}

این سه خروجی را در مقایسه با مراحل پایپ‌لاین بررسی کنید: مرحله‌ی بازنویسی عبارات در Ghidra ضرب در 0x66666667 و شیفت به راستِ 34تایی را به ساختار param_1 / 10 برگردانده که اصطلاح معرفی‌شده در درس 34 است. angr همان بایت‌ها را خوانده و محاسبات را به شکل دست‌نخورده‌ی a0 * 1717986919 >> 0x22 رها کرده است. خروجی pdc در radare2 صرفاً شبیه‌سازی ESIL با نحو شبه‌کد است: نام ثبات‌ها، بدون متغیر، بدون تایپ، و بررسی محدوده نیز به‌صورت 0 => پس از یک تفریق بدون علامت نوشته شده است. ابزار Ghidra بارگذاری جدول را با تایپ DAT_001000b4 مشخص کرده است؛ angr آرایه‌ای به شکل g_4000b4[4] تعریف کرده که چهار عنصری است، درحالی‌که کد اصلی پنج عنصر دارد. نه Ghidra و نه angr ساختار اصلی switch را بازسازی نکرده‌اند، چون در خروجی بهینه‌سازی -O1 دیگر ساختار switch در بایت‌ها باقی نمانده است. اما در بهینه‌سازی -O0 که جدول پرش مستقیماً وجود دارد، Ghidra عبارت switch(param_1 / 10) را با caseهای 6 تا 10 و حالت default بازسازی کرده و angr یک goto محاسباتی چاپ کرده است.

ابزار rizin دیکامپایلر داخلی ندارد و داده‌های ثبت‌شده نشان می‌دهند که دستور pdc در آن وجود ندارد. زبان واسط HLIL در Binary Ninja و درخت ctree در Hex-Rays دو خروجی‌ای بودند که این درس امکان ثبت مستقیم آن‌ها را نداشت.

پیاده‌سازی از صفر

اجرای دمو:

cd phases/13-tools-and-protocols/37-the-reverse-engineering-landscape
python3 code/main.py

خروجی کار یک گزارش JSON است. گزارش با مشخصات نسخه‌ی ابزارهای ذخیره‌شده و ابزارهای نصب‌شده روی سیستم شما (که ممکن است هیچ‌کدام نصب نباشند) شروع می‌شود. سپس برای هر یک از چهار آبجکت موارد زیر را ارائه می‌دهد: دامنه‌ی نمادها، فواصل پدینگ، آدرس مبنای هر ابزار، نقاط شروع توابع (یافت‌شده یا ساختگی)، و برای هر تابع وضعیت انطباق شروع دستورالعمل‌ها، تعداد بلوک‌ها، تعداد یال‌ها و یال‌های ناموجود در هر ابزار. در پایان، آمار بایت‌ها به تفکیک ابزار، یادداشت‌های داخلی ابزارها، فهرستی از یافته‌ها به زبان ساده، و بخش IR شامل خروجی مترجم برای سه دستورالعمل در کنار مقادیر ثبت‌شده ارائه می‌شود.

ساختار مورد انتظار:

{
  "captureTools": {"llvm-objdump": "Apple LLVM version 17.0.0", "radare2": "radare2 6.2.4 ...", "rizin": "rizin 0.9.1 ...", "angr": "10.0.3", "ghidra": "12.1.4"},
  "localTools": {"radare2": "absent", "...": "..."},
  "fixtures": {
    "ledger-x86_64-O0.o": {
      "bases": {"llvm-objdump": "0x0", "radare2": "0x8000040", "angr": "0x400000", "ghidra": "0x100000", "...": "..."},
      "functions": {"grade": {"blocks": {"radare2": 3, "rizin": 8, "angr": 8, "ghidra": 9}, "edges": {"radare2": 2, "rizin": 11, "angr": 7, "ghidra": 12}, "...": "..."}},
      "findings": ["radare2 never reaches bytes 0x76-0x9a inside grade that the other graph builders decode", "..."]
    }
  },
  "ir": {"translator": [{"text": "subq $0x10, %rsp", "flags": {"pcodeLike": "eager", "vexLike": "thunk"}, "...": "..."}], "observed": {"...": "..."}}
}

کد code/main.py را به این ترتیب بخوانید:

  1. تابع read_text_symbols() هدر بخش‌ها و نمادهای STT_FUNC مربوط به .text را می‌خواند. جدول نمادها تنها داده‌ی واقعی (ground truth) این بستر است و متد TextSection.gaps() فواصل پدینگ را از روی آن استخراج می‌کند.
  2. توابع parse_objdump()، parse_r2_family()، parse_angr() و parse_ghidra() هر خروجی ثبت‌شده را به یک شیء ToolView تبدیل می‌کنند که شامل آدرس مبنا، دستورالعمل‌ها (به‌صورت نگاشت آفست به طول) و برای هر تابع یک FunctionView شامل بلوک‌ها، یال‌ها و متن دیکامپایل‌شده است. هر پارسر فقط واژگان اختصاصی همان ابزار را می‌شناسد و نه چیزی بیشتر.
  3. توابع کمکی majority() و ranges() در تمام مقایسه‌ها به کار می‌روند.
  4. تابع function_report() ابتدا نقاط شروع دستورات را با نظر اکثریت سازندگان گراف مقایسه می‌کند، سپس به سراغ تعداد بلوک‌ها و مجموعه‌ی یال‌ها رفته و موارد ناموجود در هر ابزار را مشخص می‌کند.
  5. تابع byte_report() وضعیت تک‌تک بایت‌ها را در دسته‌های کد درون نماد، پدینگ، نامشخص، ساختگی یا جاافتاده ثبت می‌کند و بایت‌های درون نمادها را که هیچ ابزار گرافی رمزگشایی نکرده معرفی می‌کند.
  6. تابع findings() نتایج را به جملات توصیفی تبدیل می‌کند و compare() گزارش کامل هر آبجکت را سرهم می‌کند.
  7. توابع decode_small()، to_pcode_like() و to_vex_like() بخش مترجم را تشکیل می‌دهند: سه نوع رمزگذاری در فرم ثبات، یکی با فلگ‌های بی‌درنگ و دیگری در قالب تانک.
  8. تابع observed_ir() مقادیر مربوط به کدهای واسط ثبت‌شده را می‌سنجد و figure_data() خط زمانی، جدول مراحل و شواهد نمایشی را آماده می‌کند.

سه دستور دیگر نیز تعبیه شده است: دستور python3 code/main.py compare FIXTURE.o گزارش یک آبجکت را چاپ می‌کند. دستور python3 code/main.py ir 4883ec10 دو فرم ترجمه‌شده‌ی یک دستور را نمایش می‌دهد. دستور python3 code/main.py figure-data داده‌های نمودار تصویری را چاپ می‌کند.

داده‌های آزمایشی یک بار توسط اسکریپت code/fixtures/capture_tools.py ذخیره شده‌اند که تمام دستورات و نسخه‌ها را در captures.json نگه می‌دارد. بستر و تست‌ها هیچ ابزاری را اجرا نمی‌کنند. اگر ابزارها را روی سیستم دارید، با اجرای اسکریپت capture فایل‌ها به‌روزرسانی می‌شوند؛ برای اطلاع از محیط مورد نیاز به راهنمای موجود در پوشه‌ی fixtures مراجعه کنید.

استفاده از ابزارهای واقعی

روی یک فایل آبجکت جدید، بستر آزمایشی را به این ترتیب به کار بگیرید:

  1. آبجکت را خودتان کامپایل کنید و دستور کامپایل را نگه دارید. پیش از باز کردن هر ابزاری، جدول نمادهای آن را بخوانید تا بدانید چند تابع وجود دارد و فواصل پدینگ در کجاست.
  2. هر ابزار را یک بار در حالت خط فرمان (headless) اجرا و خروجی را ذخیره کنید. خروجی خام و دستور دقیق اجرا را کنار فایل آبجکت نگه دارید. اختلافی که نتوان آن را دوباره بازخوانی کرد، یافته‌ی معتبری نیست.
  3. آدرس مبنا را بررسی کنید. یک تابع را در نظر بگیرید و مطمئن شوید همه‌ی ابزارها پس از کسر آدرس مبنای .text، آفست یکسانی برای آن نشان می‌دهند. اگر این‌طور نبود، متوقف شوید و ببینید آن ابزار چه چیزی را لود کرده است.
  4. اول از همه شروع دستورالعمل‌ها را بسنجید. اگر ابزارهای مبتنی بر گراف در این مرحله با هم اختلاف داشته باشند، اختلاف در مراحل بعدی ناشی از رمزگشایی است نه تحلیل.
  5. بلوک‌ها و یال‌ها را مقایسه کنید و پیش از قضاوت روی اعداد، یادداشت‌های هر ابزار را بخوانید. وجود یک switch_op، یک COMPUTED_JUMP یا یک گره غیرقابل‌حل، دلیل بیشتر تفاوت‌ها را روشن می‌کند.
  6. بایت‌ها را دسته‌بندی کنید. پدینگی که یک ابزار به آن کد نسبت می‌دهد، حاصل روش‌های اکتشافی آن است. بایت‌های درون نماد که نادیده گرفته شده‌اند، نشانه‌ی توقف در پیمایش کد هستند. بایت‌های درون نماد که هیچ ابزاری سراغشان نرفته، داده هستند.
  7. ابزارهایی را که اجرا نکرده‌اید یادداشت کنید. توصیف مبتنی بر مستندات ارزش خود را دارد، اما جنس شواهد آن با اجرای مستقیم متفاوت است.

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

آزمایش الف: یک تابع، چهار آدرس مبنا

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

cd phases/13-tools-and-protocols/37-the-reverse-engineering-landscape/code
python3 -q

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

from main import *
views = load_views("ledger-x86_64-O1.o")
{tool: hex(view.base) for tool, view in views.items()}
{tool: hex(view.functions["grade"].start + view.base) for tool, view in views.items()}

پیش از اجرای خط دوم، چهار آدرس خام را یادداشت کنید و آن‌ها را با جدول این درس تطبیق دهید. سپس به جای grade تابع grade_label را قرار دهید و جایگاه آن در Ghidra را به‌صورت دستی محاسبه کنید.

آزمایش ب: بررسی جدول پرش از چهار زاویه

report = compare("ledger-x86_64-O0.o")
report["functions"]["grade"]["blocks"]
report["functions"]["grade"]["edgesMissingPerTool"]["radare2"]
report["notes"]["rizin"]
report["notes"]["ghidra"]
report["bytes"]["radare2"]["misses"]

یال‌هایی را که radare2 جا انداخته بشمارید و هر کدام را با خطی از لیستینگ -O0 در درس 33 تطبیق دهید. سپس یادداشت Ghidra را بخوانید و برچسب switchD را در داده‌های ثبت‌شده‌ی Ghidra پیدا کنید.

آزمایش ج: فلگ‌های بی‌درنگ در برابر ساختار تانک

translate("48 83 ec 10")["pcodeLike"]
translate("48 83 ec 10")["vexLike"]
translate("83 f9 04")["vexLike"]
{row["name"]: (row["pcode"]["ops"], row["vex"]["statements"]) for row in observed_ir()["instructions"]}

ثابت cc_op را در هر دو خروجی پیدا کنید و توضیح دهید چرا در یکی برابر با 8 و در دیگری برابر با 7 است. سپس دستور translate("48 01 c8") را اجرا کنید و پیام خطای TranslateError را بخوانید.

آزمایش د: جدول تله در پنج ابزار

report = compare("sweep-trap.o")
report["functions"]["lookup"]["instructionStarts"]
report["bytes"]["dataInsideSymbols"]
report["functionStarts"]["angr"]

بایت‌های 31 c0 90 48 83 c0 01 را ابتدا به روش پیمایش خطی روی کاغذ رمزگشایی کنید، سپس به روش فرود بازگشتی بعد از دستور jmp بنویسید. این دو لیستینگ را با شروع‌های اضافی و جاافتاده‌ای که سیستم گزارش می‌دهد مقایسه کنید.

تمرین عملی

یک ابزار پنجم را ثبت و بستر ارزیابی را برای آن توسعه دهید.

الزامات:

  • یکی از ابزارهایی را که در این درس ثبت نشده و روی سیستمتان قابل اجراست انتخاب کنید: ابزار GNU objdump، ابزار LIEF با Capstone، یک خروجی دستی از نسخه‌ی Binary Ninja Free، یا Ghidra با تنظیمات متفاوتِ آنالایزر. خروجی آن را برای هر چهار آبجکت ثبت کرده و دستور و نسخه‌ی آن را در captures.json وارد کنید.
  • یک تابع پارسر بنویسید که این داده را به یک ToolView شامل آدرس مبنا، دستورالعمل‌ها، توابع، بلوک‌ها و یال‌ها تبدیل کند.
  • ابزار را به لیست TOOLS و در صورت تولید گراف به DESCENT_TOOLS اضافه کنید. دمو را اجرا کنید و تغییرات در نظر اکثریت را بررسی کنید.
  • تستی بنویسید که تعداد بلوک‌های این ابزار را روی ledger-x86_64-O1.o بسنجد و تست دیگری برای نحوه‌ی برخورد آن با جدول داده در فایل trap اضافه کنید.
  • یک بند توصیفی به درس اضافه کنید که بیان کند این ابزار با چه عناوینی از مراحل لودر، دیس‌اسمبلر و بازیابی CFG یاد می‌کند.

معیار موفقیت این است که اجرای python3 code/main.py ابزار جدید را در بخش captureTools نمایش دهد، برای همه‌ی توابع فایل ledger-x86_64-O1.o مقدار allDescentToolsAgree همچنان true بماند، و گزارش مربوط به sweep-trap.o رفتار ابزار جدید با بایت‌های 0x10 تا 0x14 را به‌روشنی نشان دهد.

خروجی این درس

این درس شامل فایل outputs/skill-the-reverse-engineering-landscape.md است. از آن برای اجرای ابزارهای مختلف روی آبجکتی که خودتان ساخته‌اید استفاده کنید، آدرس‌ها را به یک مبنا تقلیل دهید و در قالب گزارش‌هایی برای هر تابع نشان دهید در شروع دستورها، بلوک‌ها و یال‌ها کجا اتفاق نظر دارند و کدام بایت‌ها ساختگی یا جاافتاده هستند. این فرآیند مستلزم ثبت دقیق دستورات و نسخه‌ها، خواندن جدول نمادها از فایل و تفکیک ابزارهای اجراشده از ابزارهای توصیف‌شده بر اساس مستندات است.

ارزیابی و اعتبارسنجی

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

cd phases/13-tools-and-protocols/37-the-reverse-engineering-landscape
python3 code/main.py
python3 -m unittest discover -s code/tests -v

اعتبارسنجی باید موارد زیر را اثبات کند:

  • پارسر ELF چهار نماد تابع در ledger-x86_64-O1.o با اندازه‌های 16، 46، 31 و 63 و دو فاصله‌ی پدینگ پیدا کند.
  • داده‌های ثبت‌شده از پیمایش خطی برای چهار آبجکت به ترتیب شامل 86، 56، 84 و 11 دستور باشند.
  • ابزارهای radare2 و rizin برای فایل ledger-x86_64-O1.o دستورات، بلوک‌ها و یال‌های کاملاً یکسانی گزارش دهند و فقط radare2 خروجی متنی pdc داشته باشد.
  • ابزار angr دو تابع بدون نماد به نام‌های sub_40003e و sub_40005f بسازد.
  • بلوک‌های Ghidra برای تابع total همان پنج بلوک درس 33 باشند و انشعاب‌های فراخوانی جزو یال‌های گراف به شمار نیایند.
  • همه‌ی ابزارها پس از کسر مبنا، تابع grade را در آفست 0x10 قرار دهند.
  • همه‌ی ابزارها در تک‌تک توابع ledger-x86_64-O1.o در مورد شروع دستورالعمل‌ها اتفاق نظر کامل داشته باشند.
  • چهار ابزار مبتنی بر گراف به ترتیب 3، 3، 5 و 4 بلوک و 2، 3، 5 و 4 یال را با مجموعه‌ی یال‌های یکسان گزارش کنند.
  • تابع total در بهینه‌سازی -O2 در تمام ابزارهای گرافی دارای 10 بلوک و 13 یال باشد.
  • تابع grade در بهینه‌سازی -O0 در ابزارهای radare2، rizin، angr و Ghidra به ترتیب 3، 8، 8 و 9 بلوک و 2، 11، 7 و 12 یال داشته باشد و دلایل آن در یادداشت‌های هر ابزار موجود باشد.
  • تابع grade_label در بهینه‌سازی -O0 در تمام ابزارها یک بلوک باشد، به‌جز angr که بلوک را در محل دستور فراخوانی پایان می‌دهد.
  • ابزار angr دقیقاً یک تابع ساختگی در هر فاصله‌ی پدینگ و یکی روی جدول داده ایجاد کند و هیچ ابزار دیگری تابع ساختگی یا جاافتاده نداشته باشد.
  • پیمایش خطی شروع‌های 0x10، 0x12 و 0x13 را اضافه کرده و 0x14 را جا بیندازد، و هیچ سازنده‌ی گرافی بازه‌ی 0x10 تا 0x14 را رمزگشایی نکند.
  • گزارش‌ها بازه‌ی جاافتاده در radare2 و توابع ساختگی در angr را نام ببرند.
  • مانیفست (Manifest) برای هر 21 فایل ذخیره‌شده نسخه و دستور اجرا را ثبت کرده باشد.
  • سه ابزار تابع grade را دیکامپایل کنند و فقط Ghidra الگوی محاسباتی تقسیم را ساده‌سازی کند.
  • مترجم در یک حالت فلگ‌های CF، OF، SF، ZF، PF را بی‌درنگ بنویسد و در حالت دیگر یک تانک cc_op بسازد؛ برای cmp ثباتی را بازنویسی نکند و برای movslq فلگی تغییر ندهد.
  • مترجم از پذیرش دسترسی به حافظه، آپ‌کدهای متفرقه، بیت‌های اضافی REX و طول‌های اشتباه خودداری کند.
  • مقادیر cc_op مترجم برای اعداد 8 و 7 دقیقاً با مقادیر ثبت‌شده در VEX برابر باشند.
  • داده‌های ثبت‌شده برای سه دستور به ترتیب 9، 1 و 10 عملیات P-code و 7، 4 و 7 عبارت VEX باشند.
  • خط زمانی از 1973 تا 2026 به ترتیب مرتب باشد، پایپ‌لاین دارای هشت مرحله و شش ابزار با اصطلاح دقیق در هر خانه باشد، و دو ابزار تجاری برچسب اجرا نشدن داشته باشند.
  • تصویر تعاملی دقیقاً همان داده‌های تولیدشده توسط بستر آزمایشی را نمایش دهد.

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

حالت شکست (Failure mode) گزارش ضعیف چه می‌گوید آنچه بستر ارزیابی باید اثبات کند
مقایسه‌ی آدرس‌های خام ابزارهای مختلف «ابزارها بر سر محل تابع grade اختلاف دارند» رسیدن به یک آفست یکتا پس از تفریق مبنای هر ابزار، سنجیده‌شده روی یک تابع
تلقی تعداد بلوک‌ها به‌عنوان معیار حجم کد «Ghidra بیشترین حجم کد را کشف کرده است» انطباق کامل در نقاط شروع دستورالعمل‌ها، همراه با توضیح بلوک اضافه ناشی از برچسب
در نظر گرفتن یک ابزار به‌عنوان مرجع مطلق «خروجی objdump نشان می‌دهد بایت‌های جدول دستورالعمل هستند» ماهیت پیمایش خطی objdump؛ سه ابزار گرافی آن بایت‌ها را دست‌نخورده رها کرده‌اند
در نظر گرفتن توابع پدینگ به‌عنوان کشفیات جدید «angr توانسته هفت تابع بیشتر پیدا کند» همه‌ی آن‌ها در فواصل پدینگ یا روی داده‌ها قرار دارند و هیچ‌کدام نماد معتبری ندارند
گزارش توقف در پیمایش به‌عنوان کد مرده «چهار بلوک مربوط به caseها غیرقابل دسترسی هستند» 36 بایت کد که یک ابزار جا انداخته اما سه ابزار دیگر به آن رسیده‌اند
مقایسه‌ی تعداد یال‌ها بدون خواندن یادداشت‌ها «rizin و Ghidra در تحلیل grade اختلاف دارند» وجود مقاصد محاسباتی یکسان، همراه با شکسته‌شدن یک بلوک در محل برچسب
تلقی محاسبات دیکامپایلر به‌عنوان منطق برنامه «تابع مقدار ورودی را در عدد 1717986919 ضرب می‌کند» این الگو همان شیوه‌ی محاسباتی درس 34 است که یک ابزار آن را ساده کرده و دیگری رها کرده است
توصیف ابزار غیرمتن‌باز به‌گونه‌ای که گویی اجرا شده «Binary Ninja تعداد پنج بلوک را گزارش می‌دهد» نمودار و جدول باید به‌روشنی نشان دهند که بررسی صرفاً بر اساس مستندات بوده است
ثبت خروجی‌ها بدون ذکر دستورات اجرا «ما radare2 را اجرا کردیم» وجود مانیفست کامل شامل نسخه، دستور و تاریخ برای تک‌تک فایل‌ها
تلقی کدهای واسط بدون فلگ به‌عنوان تبدیل ناقص «لیفتر VEX فلگ‌ها را حذف کرده است» نگهداری وضعیت در تانکِ cc_op، cc_dep1، cc_dep2 و ارزیابی آن هنگام انشعاب

دستورالعمل اجرایی

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

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

خودآزمایی

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

  1. پیش از درسابزارهای radare2، rizin، angr و Ghidra همگی فایل ledger-x86_64-O1.o را باز کرده و چهار تابع را گزارش می‌دهند، با این حال تابع grade در سه تا از آن‌ها در آدرس‌های 0x08000050، 0x400010 و 0x100010 قرار دارد. چه چیزی این سه آدرس متفاوت را توجیه می‌کند؟
  2. حین درسدر فایل ledger-x86_64-O0.o هارنس برای grade در radare2 تعداد 3 بلاک، در rizin و angr تعداد 8 بلاک، و در Ghidra تعداد 9 بلاک را گزارش می‌دهد. کدام عبارت با شواهد ثبت‌شده مطابقت دارد؟
  3. حین درسکتابخانه pypcode دستور sub rsp, 0x10 را به نه عملیات که در CF ،OF ،SF ،ZF و PF می‌نویسند lift می‌کند، و pyvex آن را به هفت عبارت با PUT(cc_op) = 0x8 و بدون هیچ فلگی lift می‌کند. این تفاوت به چه معناست؟
  4. حین درسدر فایل sweep-trap.o، ابزار llvm-objdump تعداد 8 نقطه شروع دستور را داخل lookup لیست می‌کند در حالی که radare2، rizin و Ghidra تعداد 6 نقطه را گزارش می‌دهند. این دو نقطه شروع اضافی از کجا آمده‌اند؟
  5. پس از درسابزار Ghidra تابع grade را در سطح بهینه‌سازی O1- به صورت param_1 / 10 - 6 دیکامپایل می‌کند و angr همان بایت‌ها را به (a0 * 1717986919) >> 0x22 ترجمه می‌نماید. کدام مرحله از پایپ‌لاین این تفاوت را ایجاد کرده است؟
  6. پس از درسیکی از هم‌تیمی‌ها پیشنهاد می‌کند به ابزاری اعتماد کنیم که بیشترین تعداد بلاک‌های پایه را گزارش می‌دهد، زیرا بلاک بیشتر به معنای یافتن کد بیشتر است. کدام نتیجه ثبت‌شده این قاعده را رد می‌کند؟

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

چرا ابزارهای مختلف آدرس‌های متفاوتی برای یک تابع در فایل .o گزارش می‌دهند؟

چون فایل‌های آبجکت بازآرایی‌پذیر (relocatable) آدرس بارگذاری ثابتی ندارند و هر لودر آدرس مبنای پیش‌فرضی به بخش text. اختصاص می‌دهد. با تفریق آدرس مبنای هر ابزار، آفست تابع در همه یکسان خواهد بود.

تفاوت بازنمایی‌های واسط P-code و VEX در نحوه مدیریت فلگ‌ها چیست؟

کد P-code فلگ‌ها را بی‌درنگ (eager) در لحظه اجرای دستور محاسبه می‌کند، درحالی‌که VEX فلگ‌ها را ثبت نکرده و عملگر و عملوندها را در قالب تانک (thunk) ذخیره می‌کند تا تنها هنگام انشعاب محاسبه شوند.

چرا ابزار angr در تحلیل فایل‌های باینری توابع بدون نماد ایجاد می‌کند؟

تحلیل CFGFast در angr علاوه بر نمادها و مقاصد فراخوانی، بایت‌های نامشخص باقی‌مانده و حاشیه‌گذاری‌ها (padding) را نیز اسکن می‌کند که این روش اکتشافی گاهی منجر به ساخت توابع ساختگی در فواصل خالی می‌شود.

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

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