چیت‌شیت

ابزارهای تحلیل باینری از صفر

Binary Analysis Tools from Scratch

خواندن ELF و Mach-O، رمزگشایی x86-64، ساخت CFG، ارتقا به C و اثبات آن. فقط کتابخانه‌ی استاندارد Python.

  • ۱۲ بخش
  • ۳ درس مرتبط
  • کد و دستورها عیناً از دوره‌ی اصلی
فهرست مطالب
  1. از بایت‌ها تا C تأییدشده
  2. اجرای درس‌ها
  3. قالب‌های رکورد ELF64
  4. حساب آدرس
  5. خواندن یک آبجکت در Python
  6. پیمایش یا نزول: نمره‌دهی در -O0
  7. تحلیل یک آبجکت جدید
  8. دستورالعمل به IR
  9. شکست‌ها و اصلاحات
  10. دو پروژه
  11. دستورات پروژه
  12. پرسش‌ها

از بایت‌ها تا C تأییدشده

معماری
بایت‌هابایت‌های تابعدستورالعمل‌هامتن Cفایل آبجکتledger.c built by clangخواننده‌ی ELFبخش‌ها، نمادها، relaرمزگشاREX، ModRM، SIBCFGبلاک‌ها، دامینیتورهاارتقادهندهIR، متغیر فلگ‌هاSSAphi، تغییر نامساده‌سازیجایگزینی، کد مردهساختاردهیif، while، do while

اجرای درس‌ها

دستورها
دستوروظیفه
`python3 code/main.py` (32)هدر، بخش‌ها، نمادها، بازآدرس‌دهی‌ها، نگاشت
`python3 code/main.py <object>` (32)یک فایل را می‌خواند. ELF یا Mach-O را انتخاب می‌کند
`python3 code/main.py` (33)یک گزارش JSON برای هر مجموعه‌داده‌ی نمونه
python3 code/main.py disasm <object>فهرست‌بندی به همراه مقصدهای بازآدرس‌دهی
python3 code/main.py cfg <object> <function>تحلیل یک تابع
`python3 code/main.py` (34)IR، SSA، شبه‌کد در کنار Ghidra
python3 -m unittest discover -s code/tests -vمجموعه‌ی آزمون درس‌های 32 و 34
python3 code/tests/test_main.pyمجموعه‌ی آزمون درس 33

قالب‌های رکورد ELF64

کار و کد
وظیفهکد
هدر پس از e_ident، آفست 16"<HHIQQQIHHHHHH"
هدر بخش، 64 بایت<IIQQQQIIQQ
نماد، 24 بایت<IBBHQQ
رکورد Rela، 24 بایت<QQq
هدر برنامه، 56 بایت<IIQQQQQQ
اندیس نمادِ r_info`ELF64_R_SYM(i)` is `i >> 32`
نوع r_info`ELF64_R_TYPE(i)` is `i & 0xffffffff`
نماد Mach-O، 16 بایت`nlist_64` has no size field

حساب آدرس

مفهوم‌ها
بایت‌های تابع
آفست فایل sh_offset + st_value - sh_addr است. طول st_size است.
R_X86_64_PC32
لینکر مقدار S + A - P را درون فیلد می‌نویسد.
R_X86_64_64
S + A. R_X86_64_PLT32: L + A - P.
مقصد RIP
مقدار افزوده به علاوه‌ی 4. مقدار .rodata+0x10 روی یک lea یعنی .rodata+0x14.
PT_LOAD
آفست فایل p_offset + (address - p_vaddr) است.
اندازه‌ی Mach-O
نماد بعدی در همان بخش منهای این نماد.
تقسیم بر 10
0x66666667 با شیفت 34. خطای 6 حداکثر 8 است.

خواندن یک آبجکت در Python

کد
python
from main import ElfFile, MachOFile, FIXTURES
elf = ElfFile.from_path(FIXTURES / "ledger-x86_64-O1.o")
elf.header.e_shoff, elf.header.e_shnum  # 0x428 و 11 هدر
elf.function_span("percent_change")     # 16 بایت در 0x40
elf.function_bytes("percent_change")    # 85 ff 74 09 ...
elf.relocations_for(".text")            # 3 رکورد R_X86_64_PC32
elf.file_offset_map()                   # 3 شکاف هم‌ترازی
tiny = ElfFile.from_path(FIXTURES / "tiny-x86_64")
tiny.address_to_file_offset(0x4000b0)   # 0xb0 از طریق PT_LOAD
macho = MachOFile.from_path(FIXTURES / "ledger-arm64-O1.o")
macho.function_span("_percent_change")  # 24 بایت در 648

پیمایش یا نزول: نمره‌دهی در -O0

مقایسه
عاملپیمایش خطینزول بازگشتینزول با جدول
دستورالعمل‌ها342533
بلاک‌ها938
یال‌ها7211
شکستداده‌ها به عنوان کد رمزگشایی شدنددر jmpq *%rax متوقف می‌شودهیچ
کاربردتوافق با اوراکلکدام بایت‌ها اجرا می‌شوندCFG کامل

تحلیل یک آبجکت جدید

گام‌به‌گام
  1. اوراکلابتدا رمزگشا را با llvm-objdump -d مقایسه کن.
  2. نزولاز هر نماد تابع پیمایش را آغاز کن. unresolved را بخوان.
  3. جدول‌هااصطلاح جدول و بازآدرس‌دهی آن را برای هر پرش غیرمستقیم تطبیق بده.
  4. بایت‌هابایت‌هایی که فقط پیمایش مدعی آن‌هاست پدینگ یا داده هستند.
  5. CFGآن را با جدول‌های بازیابی‌شده بساز. هر یال بازگشتی یک حلقه است.

دستورالعمل به IR

دستورها
دستوروظیفه
testl %edi, %ediflags = test(edi, edi)
je 0xdif je(flags) goto 0xd else goto 0x4
xorl %eax, %eaxeax = 0
cltdedx = signfill(eax)
movslq %edi, %raxrax = sext64(edi)
sarq $0x22, %rcxrcx = rcx >>s 34
`pushq %rbp` and frame movesهیچ، مدیریت فریم
retqreturn rax

شکست‌ها و اصلاحات

عیب‌یابی
  • «percent_change در 0 است»

    علت
    st_value به عنوان آفست فایل استفاده شده
    راه‌حل
    از sh_offset + st_value - sh_addr استفاده کن، در اینجا 0x40
  • «grade برابر 48 بایت است»

    علت
    پدینگ 66 90 به عنوان کد شمرده شد
    راه‌حل
    st_size برابر 46 است. پدینگ متعلق به هیچ نمادی نیست
  • «جدول در 0x10 شروع می‌شود»

    علت
    مقدار افزوده به عنوان آفست مقصد خوانده شد
    راه‌حل
    4 بایت را به انتهای دستورالعمل اضافه کن: 0x14
  • یک فهرست‌بندی موجه روی یک جدول داده

    علت
    پیمایش خطی داده را به عنوان کد رمزگشایی می‌کند
    راه‌حل
    نزول از نماد هیچ بایت داده‌ای را ادعا نمی‌کند
  • «jmp rax» و بلاک‌های مرده

    علت
    پرش غیرمستقیم بدون جدول
    راه‌حل
    مقصدها را از .rela.rodata و بررسی مرزها بخوان
  • دیکامپایلر پارامتر سومی گزارش می‌دهد

    علت
    idivl در %rdx می‌نویسد و عرض بازگشتی یک حدس است
    راه‌حل
    خواندن رجیستر آرگومان قبل از نوشتن روی آن، نشان‌دهنده‌ی یک پارامتر است

دو پروژه

مقایسه
عاملbinary-inspectormini-decompiler
می‌سازدبازرس ایستا برای ELF، PE/COFF و Mach-Oدیکامپایلر برای زیرمجموعه‌ای از x86-64
ورودیآبجکت ELF64، آبجکت COFF، ایمیج PE32+، آبجکت Mach-O 64آبجکت ELF64 ET_REL کامپایل‌شده با clang
خروجیگزارش JSON، نقشه‌ی بایتی HTML، تفاوت‌های بازنگری ساختC ساختاریافته و report.json
اجراpython3 cli.py inspect <file> --json report.jsonpython3 -m minidec verify <object> --source <source>
بررسیامتیازدهی به نقاط شروع تابع با دقت و بازخوانیبایت‌های ماسک‌شده و یک ماتریس رفتار
خروج2 با error: <message> در فایل نامعتبر0 در pass یا behaviour-only، در غیر این صورت 1

دستورات پروژه

دستورها
دستوروظیفه
python3 scripts/project_test.py <project> --init learning-artifacts/<project>فضای کاری را می‌سازد. فاز 1 عمداً شکست می‌خورد
python3 scripts/project_test.py <project> --all --path learning-artifacts/<project> --strictسیستم نمره‌دهی تجمعی را اجرا می‌کند
python3 cli.py inspect <stripped> --truth <unstripped> --html report.htmlنقاط شروع تابع را در مقایسه با ساخت unstripped امتیازدهی می‌کند
python3 cli.py diff <old> <new> --json diff.jsonرشد بخش‌ها، نمادهای جابه‌جاشده، ایمپورت‌های تغییریافته
python3 -m minidec decompile <object> --ir --no-division-idiomsنمایش IR و SSA، حفظ ضرب جادویی
python3 -m minidec verify <object> --source <source> --opt O1 --out <dir>کامپایل مجدد، مقایسه‌ی بایت‌ها، اجرای ماتریس

پرسش‌ها

پرسش و پاسخ
چرا اوراکل با پیمایش روی تله هم‌نظر است؟
ابزار llvm-objdump هم یک پیمایش خطی است.
چه زمانی یک یال، یال بازگشتی است؟
مقصد آن بر مبدأ آن تسلط (dominate) دارد. این یال نشان‌دهنده‌ی یک حلقه است.
چرا do while در total در -O1؟
هدر همان بلاک حاوی یال بازگشتی نیز هست.
چرا هیچ جدول پرشی در grade در -O1 وجود ندارد؟
هر case یک مقدار ثابت برمی‌گرداند. یک جدول مقادیر جایگزین پرش‌ها شد.
آیا قبولی در رفتار معادل بودن را اثبات می‌کند؟
خیر. این فقط ماتریس تعریف‌شده را روی یک میزبان پوشش می‌دهد.