چیتشیت
ابزارهای تحلیل باینری از صفر
Binary Analysis Tools from Scratch
خواندن ELF و Mach-O، رمزگشایی x86-64، ساخت CFG، ارتقا به C و اثبات آن. فقط کتابخانهی استاندارد Python.
فهرست مطالب
از بایتها تا C تأییدشده
معماریاجرای درسها
دستورها| دستور | وظیفه |
|---|---|
`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
مقایسه| عامل | پیمایش خطی | نزول بازگشتی | نزول با جدول |
|---|---|---|---|
| دستورالعملها | 34 | 25 | 33 |
| بلاکها | 9 | 3 | 8 |
| یالها | 7 | 2 | 11 |
| شکست | دادهها به عنوان کد رمزگشایی شدند | در jmpq *%rax متوقف میشود | هیچ |
| کاربرد | توافق با اوراکل | کدام بایتها اجرا میشوند | CFG کامل |
تحلیل یک آبجکت جدید
گامبهگام- اوراکلابتدا رمزگشا را با
llvm-objdump -dمقایسه کن. - نزولاز هر نماد تابع پیمایش را آغاز کن.
unresolvedرا بخوان. - جدولهااصطلاح جدول و بازآدرسدهی آن را برای هر پرش غیرمستقیم تطبیق بده.
- بایتهابایتهایی که فقط پیمایش مدعی آنهاست پدینگ یا داده هستند.
- CFGآن را با جدولهای بازیابیشده بساز. هر یال بازگشتی یک حلقه است.
دستورالعمل به IR
دستورها| دستور | وظیفه |
|---|---|
testl %edi, %edi | flags = test(edi, edi) |
je 0xd | if je(flags) goto 0xd else goto 0x4 |
xorl %eax, %eax | eax = 0 |
cltd | edx = signfill(eax) |
movslq %edi, %rax | rax = sext64(edi) |
sarq $0x22, %rcx | rcx = rcx >>s 34 |
`pushq %rbp` and frame moves | هیچ، مدیریت فریم |
retq | return 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-inspector | mini-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.json | python3 -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 یک مقدار ثابت برمیگرداند. یک جدول مقادیر جایگزین پرشها شد.
- آیا قبولی در رفتار معادل بودن را اثبات میکند؟
- خیر. این فقط ماتریس تعریفشده را روی یک میزبان پوشش میدهد.