تشريح البرنامج: قارئ ELF من الصفر
Type: Build
Languages: Python (stdlib), C (fixture only)
Prerequisites: Phase 0 · 01 (Dev Environment) with clang installed, Python 3.10 or later, hexadecimal arithmetic
Time: ~120 minutes
أهداف التعلم
- أوضح قاعدة الإذن لكل هدف في هذا المسار والحدود الأربعة في 17 يو إس سي 1201 ((f).
- اقرأ رأس ELF، جدول رأس القسم، وكلا الجداول الثنائية
struct.unpack. . . - فك الرمز
.symtabالإدخالات و.relaسجلات مع الأسماء من gABI و x86-64 psABI. - أعيد البايتات الدقيقة للعمل المسمى و خريطة كل ملف تعويض إلى الهيكل الذي يملكه.
- قراءة عناوين البرنامج و خريطة عنوان افتراضي إلى ملف تعويض من خلال
PT_LOAD. . . - اقرأ أوامر الحمل من كائن ماخ او وسمي فكرة ELF التي تتطابق كل واحد.
قبل أن تبدأ: الاختيار والقانون
كل هدف في الدروس 32 إلى 36 هو برنامج كتبته و تجمعهcode/fixtures/ledger.cلا توجد ترخيص للقراءة، لا توجد حماية للعبور، ولا ثالثين تفتحون رموزهم. لا تغطي المسار أبدا حماية البرمجيات، أو فحص الترخيصات، أو البرامج التي لا تملكها.
القاعدة موجودة لأن القانون يعامل التفكيك كنسخة. في شركة "سيغا إنتربرايزز" المحدودة (ف) أكوليد، إك.*، 977 ف.2د 1510 (9th Cir. 1992) ، قضت المحكمة أن النسخة الوسطى التي تمت خلال تفكيكها هي نسخ بموجب قانون حقوق الطبع والنشر. ثم أعلنت أنه "حيث يكون التفكيك هو الطريقة الوحيدة للحصول على الوصول إلى الأفكار والعناصر الوظيفية التي تجسد في برنامج كمبيوتر يحمي حقوق الطبع والنشر، وحيث يكون هناك سبب مشروع لطلب هذا الوصول، فإن التفكيك هو استخدام عادل للعمل يحمي حقوق الطبع والنشر، كموضوع قانوني".
القضية الثانية هي Sony Computer Entertainment, Inc. بمقابلة شركة Connectix Corp.، 203 F.3d 596 (9th Cir. 2000). نسخ Connectix نظام البيولوجيا البيولوجية للاستشعار بينما كانت تبني محاكاة، وقالت المحكمة إن هذه النسخ تستخدم بشكل عادل. كلتا القضايا حماية الدراسة. لا أحد من النسخ المنسخة محمية في المنتج النهائي.
قانونان يحددون الحدود 17 قانون الولايات المتحدة 1201 ((و) يسمح لشخص "حصل على الحق في استخدام نسخة من برنامج كمبيوتر" بتحايل ضوابط الوصول "لأغراض الوحيدة لتحديد وتحليل عناصر البرنامج اللازمة لتحقيق التفاعل بين برنامج كمبيوتر تم إنشاؤه بشكل مستقل مع برامج أخرى". أربعة قواعد الحد: نسخة قانونية، الغرض من التشغيل، العناصر التي لم تكن متاحة "بسهولة" بالفعل، والتحليل الذي لا ينتهك خلافا. القسم 1201 ((f) هو استثناء من قاعدة مكافحة التجاوز فقط. السؤال عن النسخ يبقى مع الاستخدام العادل.
في الاتحاد الأوروبي، تغطي المادة 5*3 من التوجيه 2009/24/EC الدراسة. يمكن للشخص الذي يحق له استخدام نسخة "الملاحظة أو الدراسة أو اختبار عمل البرنامج من أجل تحديد الأفكار والمبادئ التي تستند إلى أي عنصر من عناصر البرنامج". ينطبق هذا الحق أثناء تحميل البرنامج أو عرضه أو تشغيله أو إرساله أو تخزينه.
المادة 6 تسمح بتفكيكها دون إذن حامل الحقوق عندما يكون "لا غنى عنه للحصول على المعلومات اللازمة لتحقيق قابلية التشغيل لبرنامج كمبيوتر تم إنشاؤه بشكل مستقل". الشروط تتماشى مع 1201 ((f). يقوم صاحب الترخيص بأعمال، ولم تكن المعلومات متاحة بسهولة، والعمل يبقى ضمن الأجزاء اللازمة. المادة 6 ((2) تحظر استخدام النتيجة لأي هدف آخر أو لبرنامج مشابه بشكل كبير في التعبير. المادة 8 تجعل شروط العقد التي تتعارض مع المادة 6 أو المادة 5 ((3) غير صالحة.
| Source | What it allows | Conditions |
|---|---|---|
| 17 U.S.C. 1201(f) | Circumventing an access control to analyze a program | Lawful copy, interoperability purpose, information not readily available, no infringement |
| Directive 2009/24/EC Art. 5(3) | Observing, studying, and testing a running program | A right to use the copy, acts within normal loading and running |
| Directive 2009/24/EC Art. 6 | Decompilation | Licensee, interoperability purpose, information not readily available, confined to the needed parts |
| Sega v. Accolade | Disassembly as fair use | The only way to reach the ideas and functional elements, a legitimate reason |
| Sony v. Connectix | Intermediate copies during reverse engineering | A non-infringing final product |
هذه الملاحظات تأتي من النظام الأساسي والمراجعات، وهي ليست نصائح قانونية. عندما لا يكون هدف مستقبلي لك، الحصول على إذن كتابي قبل فتح الملف والاحتفاظ بالترخيص مع الأدلة الخاصة بك.
المشكلة
لقد قمت بتجميعهاledger.cو ركضتllvm-objdump -tعلى الكائن. قوائم الخروج percent_changeفي القيمة0000000000000000مع حجم0000000000000010أي 16 بايت من الملف البايت 1768 هي هذه الوظيفة؟R_X86_64_PC32 .rodata+0x10في التكافؤ0x36و الأربعة بايت في تلك الملفة هي صفر. ما الذي سوف تصبح، ولماذا هو الإضافة0x10عندما تبدأ الطاولة في0x14- ماذا ؟
رد على أسئلة التخطيط مع inspect_binary_layout، و هذه الأداة موجودة فقط على لينكس x64 مع أدوات pwntools 4.15.0 خلف REA_PWNTOOLS_PYTHONعلى هذا ماك هو غائب.address_to_file_offsetيحتاج إلى جلسة Ghidra ونسخة كاملة من البايتات الملف الأصلية مع مطابقة SHA-256. inspect_artifactيصف ما يحتوي عليه الحاوية حسب المسار والحجم المحتوى. فإنه لا يفتح أبدا الجداول داخل ملف كائن. لذلك يمكنك كتابة القارئ بنفسك في حوالي 950 سطر من بيثون مع struct.unpackلا يوجد إعتماد، الاختبارات تقارن كل جدول معllvm-objdumpالنص الذي تم التقاطه مرة واحدة في الأجهزة
الملف هو الجداول التي تشير إلى الجداول
النظام V gABI، الفصل 4، الشكل 4-1، يرسم نظرين من ملف واحد. يحتوي عرض الرابط على عنوان ELF ، وجداول رأس البرنامج الاختياري ، والقسمات ، وجداول رأس القسم المطلوب. يحتوي منظر التنفيذ على عنوان ELF ، وجداول عنوان البرنامج المطلوبة ، والقطع ، وجداول عنوان القسم الاختياري. يضيف gABI جملة واحدة تحكم القارئ بأكمله: "القائمة ELF فقط لديها موقع ثابت في الملف". كل هيكل آخر يتم العثور عليه من خلال تعويض مخزن في هيكل آخر.
كما أن gABI يصلح أنواع القياس ويقول أن "ELF لا تستخدم حقل البيت". Elf64_AddrوElf64_Offهي 8 بايت، Elf64_Halfهو 2Elf64_Wordهو 4 وElf64_Xwordهو 8. psaBI x86-64، القسم 4. 1. 1، يتطلب ELFCLASS64،ELFDATA2LSB،e_machine EM_X86_64(62) ، وe_flags0 - الـ "انديان الصغير" ذو عرض ثابتstruct.unpackمع<المُقبل يقرأ كل سجل في مكالمة واحدة
أول 64 بايت من ledger-x86_64-O1.o:
text00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0100 3e00 0100 0000 0000 0000 0000 0000 ..>.............
00000020: 0000 0000 0000 0000 2804 0000 0000 0000 ........(.......
00000030: 0000 0000 4000 0000 0000 4000 0b00 0100 ....@.....@.....اقرأها ضد صفحة "ELF Header"e_identهو 16 بايت 7f 45 4c 46(السحر) ، الفئة 2 (64 بت) ، البيانات 1 (قليل-endian) ، النسخة 1، نظام التشغيل ABI 0 (النظام V) ، والشباك.
إذنe_type 0x0001هوET_REL، كائن قابل للتنقلe_machine 0x003eهو 62EM_X86_64. .e_entryهو صفر لأن كائن ليس لديه نقطة دخول. e_phoffهو صفر لأن كائن ليس لديه عناوين برنامج. e_shoffهو0x428، تعديل ملف جدول عنوان القسم. الصف الأخير يحتوي علىe_ehsize 0x40،e_phentsize0e_phnum0e_shentsize 0x40،e_shnum 0x0b(١١ عنوان) ، وe_shstrndx 0x0001. . .
القارئ يحول هذا الصف إلى مكالمة واحدة:
pythonfields = struct.unpack_from("<HHIQQQIHHHHHH", data, 16)سلسلة التنسيق هي رقم gABI القراءة من اليسار إلى اليمين: اثنين من نصف الكلمات، كلمة، ثلاثة قيم 8-byte، كلمة، وستة نصف الكلمات. يرفض القارئ ملف يختلف السحر، أو الفئة، أو ترتيب البايت، ويرفض e_shentsizeأوe_phentsizeقيم ليست 64 و 56 عرض خاطئ سوف تجعل كل تعويض لاحقا خاطئا، لذلك الشيك يقع في الأعلى.
عناوين القسم و جداول حبلين
كل عنوان قسم هو Elf64_Shdrمن 64 بايت (GABI الشكل 4-8): sh_name،sh_type،sh_flags،sh_addr،sh_offset،sh_size،sh_link،sh_info،sh_addralignوsh_entsize. سلسلة التنسيق هي<IIQQQQIIQQ. .sh_nameهو تعويض في القسم الذي يعد مؤشر e_shstrndxو ينتهي الاسم عند أول صفر بايتe_shstrndxهو 1، وسمي هذا القسم.strtab، لذا طاولة واحدة تخدم كل من أسماء القسم والأسماء الرمزية.
القارئ يطبع 11 رأس من -O1 الكائن:
textIdx Name Type Flags Offset Size Addr Link Info Align
0 NULL 0x0 0x0 0x0 0 0 0
1 .strtab STRTAB 0x368 0xbe 0x0 0 0 1
2 .text PROGBITS AX 0x40 0x9f 0x0 0 0 16
3 .rela.text RELA I 0x2a8 0x48 0x0 10 2 8
4 .rodata PROGBITS A 0xe0 0x50 0x0 0 0 8
5 .rela.rodata RELA I 0x2f0 0x78 0x0 10 4 8
6 .rodata.str1.1 PROGBITS AMS 0x130 0xa 0x0 0 0 1
7 .comment PROGBITS MS 0x13a 0x2f 0x0 0 0 1
8 .note.GNU-stack PROGBITS 0x169 0x0 0x0 0 0 1
9 .llvm_addrsig LLVM_ADDRSIG E 0x368 0x0 0x0 10 0 1
10 .symtab SYMTAB 0x170 0x138 0x0 1 9 8الفائدة 0 هي الرأس الصفري المخصص. .textتحتوي على 159 بايت من الشفرة عند تعويض الملف 0x40، مباشرة بعد الرأس، مع العلاماتA(مخصصة في الذاكرة) وX(مفعول) .rodataتحتوي على 80 بايت من البيانات القراءة فقط. .rodata.str1.1يحمل الخمسة سلاسل العلامةF\0D\0C\0B\0A\0، 10 بايت، مع العلامات M(مدمجة) و S(الخطوط)
.commentيحمل طابع المُجمّع البييت 47\0Apple clang version 17.0.0 (clang-1700.6.3.2)\0. ..llvm_addrsigلديه نوع0x6fff4c03، داخل النطاق المحدد للنظام التنفيذي SHT_LOOSإلىSHT_HIOSأن الاحتياطيات gABI، و LLVM ELF.hاسمها، علمهاEهوSHF_EXCLUDE، لذا يُسقطه رابط
- نعم
sh_linkوsh_infoالعمود هي المؤشرات بين الجداول..rela.text- نعمsh_info2 وsh_link10: يصلح القسم 2 و يأخذ رموزه من القسم 10..symtab- نعمsh_link1، جدول السلاسل لأسماء الرمز، وsh_info9، مؤشر الرمز الأول غير المحلي.sh_addrهو صفر في كل سطر لأن كائن ليس لديه عناوين بعد.
قواعد gABI اثنتان تسمح للقارئ رسم خريطة كاملة للملف. القسمات "مواصلة ولا تتداخل" ، ويمكن للملف "حتي تحتوي على مساحة غير نشطة". يقوم القارئ بتنظيم كل هيكل حسب التداخل ويملأ الثقوب:
text 0x0 +64 header ELF header
0x40 +159 section .text
0xdf +1 gap inactive space
0xe0 +80 section .rodata
0x130 +10 section .rodata.str1.1
0x13a +47 section .comment
0x169 +7 gap inactive space
0x170 +312 section .symtab
0x2a8 +72 section .rela.text
0x2f0 +120 section .rela.rodata
0x368 +190 section .strtab
0x426 +2 gap inactive space
0x428 +704 table section header tableالفجوات الثلاثة تصل إلى 10 بايت، وكل واحد هو التوجه. .rodata- نعمsh_addralign8، لذلك يبدأ في 0xe0بدلاً من0xdf. ..symtabلديه التوجه 8 ، لذلك سبعة بايت لا تستخدم بعد .commentطاولة الرأس الخاصة بالقطعة نفسها مرتبطة بـ 8.
قسم النوع SHT_NOBITSلا تشغل أي بايتات ملف على الإطلاق، لذلك تخطي الخريطة لهم. لا شيء في هذا الكائن له هذا النوع..bssفي برنامج متصل يفعل، والقارئ الذي يثق sh_sizeهناك يقرأ ما بعد الملف.
الرموز: الالتزام، النوع، القسم، القيمة، الحجم
- نعم
Elf64_Symهو 24 بايت (GABI الرسم 4-18):st_name،st_info،st_other،st_shndx،st_value،st_size، صيغة<IBBHQQ. .st_infoيحتوي على حقلين: الارتباط في الأربعة البيتات العليا والنوع في الأربعة السفلية.STB_LOCAL0STB_GLOBAL1،STB_WEAK2، والأنواعSTT_NOTYPE0STT_OBJECT1،STT_FUNC2،STT_SECTION3،STT_FILE4، القطع الـ2 المنخفضةst_otherالحفاظ على المرئيةSTV_DEFAULT0 هنا في جميع أنحاء.
textIdx Value Size Bind Type Section Name
0 0x0 0 LOCAL NOTYPE *UND*
1 0x0 0 LOCAL FILE *ABS* ledger.c
2 0x0 20 LOCAL OBJECT .rodata labels.rel
3 0x0 0 LOCAL SECTION .rodata .rodata
4 0x2 2 LOCAL OBJECT .rodata.str1.1 .L.str.1
5 0x4 2 LOCAL OBJECT .rodata.str1.1 .L.str.2
6 0x6 2 LOCAL OBJECT .rodata.str1.1 .L.str.3
7 0x8 2 LOCAL OBJECT .rodata.str1.1 .L.str.4
8 0x0 0 LOCAL SECTION .rodata.str1.1 .rodata.str1.1
9 0x0 16 GLOBAL FUNC .text percent_change
10 0x10 46 GLOBAL FUNC .text grade
11 0x40 31 GLOBAL FUNC .text total
12 0x60 63 GLOBAL FUNC .text grade_labelالمؤشر 0 هو الرمز غير المحدد المحفوظ. st_shndxيطلق على القسم الذي يحدد الرمز، مع ثلاثة قيم محجوزة: SHN_UNDEF0SHN_ABS 0xfff1لسم الملف، و SHN_COMMON 0xfff2رموز القسم لها اسم فارغ، لذلكllvm-objdumpيطبخ اسم القسم الخاص بهم ، والقارئ يفعل الشيء نفسه. قاعدة gABI التي تقول "كل الرموز مع ربط STB_LOCAL يسبق الرموز الضعيفة والعالمية" تظهر كالتسعة الأولى من الصفوف ، و .symtabتسجل 9 في sh_infoلهذا السبب
في جسم قابل للتنقلst_valueهو تعويض داخل قسم الرمز، لأن sh_addrهو صفر. تعويض الملف من وظيفة هوsh_offset + st_value - sh_addr، وطولها هوst_size:
pythonsection = elf.sections[symbol.st_shndx]
file_offset = section.sh_offset + (symbol.st_value - section.sh_addr)
code = elf.data[file_offset : file_offset + symbol.st_size]لأجلpercent_changeهذا هو0x40 + 0، وال16 بايت هي85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3الحجم مهم بقدر ما هو البدايةgradeينتهي في.textالتكفيف0x3e. البايتين66 90التي تتبعها هي nopالمُجمّع أضاف حتى totalيبدأ في0x40هذه البايتين لا تنتمي إلى أي رمزst_sizeو الدروس 36 تُخفي هذه الأشياء قبل أن تقارن إعادة البناء.
عمليات نقل: ما تعنيه الصفر
- نعم
Elf64_Relaهو 24 بايت (GABI الرسم 4-23):r_offset،r_infoووقعتr_addend، صيغة<QQq. غابي ينقسمr_infoمع ماكروين:ELF64_R_SYM(i)هوi >> 32وELF64_R_TYPE(i)هوi & 0xffffffffفي ملف قابل للتنقلr_offsetهو تعويض داخل القسم المسمى من قبل الجدولsh_info. أسماء النوع والصيغ تأتي من x86-64 psABI، الجدول 4.9. هناكSهو قيمة الرمزAالإضافةPالمكان الذي يتم تصحيحه، وLإدخال PLT.R_X86_64_64هوS + A،R_X86_64_PC32هوS + A - PوR_X86_64_PLT32هوL + A - P. . .
الكائن -O1 لديه ثمانية سجلات:
text .rela.text 0x36 R_X86_64_PC32 .rodata+0x10
.rela.text 0x84 R_X86_64_PC32 .rodata+0x24
.rela.text 0x93 R_X86_64_PC32 .rodata-0x4
.rela.rodata 0x0 R_X86_64_PC32 .rodata.str1.1
.rela.rodata 0x4 R_X86_64_PC32 .L.str.1+0x4
.rela.rodata 0x8 R_X86_64_PC32 .L.str.2+0x8
.rela.rodata 0xc R_X86_64_PC32 .L.str.3+0xc
.rela.rodata 0x10 R_X86_64_PC32 .L.str.4+0x10إزالة أول واحد.0x36يقع بداخلهgrade، والتي تمتد0x10إلى0x3e. التعليمات في0x33هو48 8d 0d 00 00 00 00، أleaمن عنوان RIP-سريع، وتحركها أربعة بايتات يقع في 0x36. المعالج يضيف التحول إلى عنوان التعليمات التالية0x3a. . .
المُجمّع يريد النتيجة أن تكون.rodata + 0x14، حيث جدول خمس مدخلات من قيم 32 بت 1, 2, 3, 4, 4يبدأ. لذا يجب أن تكون القيمة المخزنة(.rodata + 0x14) - 0x3a. الصيغةS + A - PمعP = 0x36يعطي.rodata + A - 0x36. تحديد العطيات المتساويةA = 0x14 - 4 = 0x10. الإضافة تحمل تعويض الهدف ناقص أربعة بايت بين التنقل و نهاية التعليمات.
هذا الجدول هو كيف قام Clang بتجميع switchفيgradeفي -O1، الرمز يحسبscore / 10مع مضاعفة ب 0x66666667و 2 تحولات، خصم 6، مقارنة مع 4، وتصفية الجدول. لا يوجد رمز للجدول. المرجع هو رمز القسم .rodataبالإضافة إلى إضافة، و فقط البناء الأذرع64 من نفس المصدر يسميها l_switch.table.grade. السجل الثاني ، في0x84فيgrade_label، نقاط في الجدول الثاني من قيم 8 بايت في .rodata + 0x28الثالثة، في0x93، تشير إلى.rodata + 0x0، بدايةlabels.rel. . .
الخمسة.rela.rodataسجلات ملءlabels.relكل مدخل يجلس في التسويق4iويحصل على الإضافة4i، لذاS + A - Pيقلل إلى عنوان السلسلة ناقص قاعدة الجدول. grade_labelيقرأ إدخال مع movslq، يضيف قاعدة الجدول مرة أخرى، ويكون له المؤشر. السجل الأول يسمى رمز القسم.rodata.str1.1مع إضافة 0 لأن السلسلة Fهو أول بايت في هذا القسم..L.str.1إلى.L.str.4، علامات المحليّة للمُجمّعD،C،BوA. . .
يجمع المصدر نفسه إلى ثلاث جداول مختلفة في ثلاثة مستويات تحسين. يظهر القارئ الفرق دون إزالة:
| Build | Where labels lives | How grade selects | How grade_label reaches grade |
|---|---|---|---|
| -O0 | .data, five 8-byte pointers, five R_X86_64_64 records | A jump table in .rodata: five R_X86_64_PC32 records pointing into .text | A call with R_X86_64_PLT32 grade-0x4 |
| -O1 | .rodata as labels.rel, five 32-bit relative entries | A value table at .rodata+0x14, one R_X86_64_PC32 | Inlined, no call |
| -O2 | Same as -O1 | Same as -O1 | Inlined, total grows to 140 bytes |
في -0switchهو طاولة قفزة حقيقية التي تُؤشر خمس إدخالات إلى.textفي+0x91،+0x8c،+0x87،+0x82و+0x86- المكالمة إلىgradeيحمل نفس الشيء-0x4الإضافة لأي سببleaعند -O1 رأى المحسن أن كل حالة تعود ثابتة واستبدل القفزات بطاولة النتائج.
عناوين البرنامج: وجهة نظر المُحمل
كائن قابل للتنقل ليس لديه عناوين برنامج، لذا e_phnumهو 0، ويتم تنفيذها في كائن أو كائن مشترك، ولا يقرأ المحمول أي شيء آخر.Elf64_Phdrهو 56 بايت (GABI الشكل 5-1): p_type،p_flags،p_offset،p_vaddr،p_paddr،p_filesz،p_memsz،p_align، صيغة<IIQQQQQQ- الـ (جابي) يضع ثلاثة قواعدPT_LOADإدخالات، تظهر في الصعودp_vaddrالنظام، و p_memszأكبر من p_fileszيعني صفر ملء.p_vaddrهو متطابق مع p_offsetالمودولp_align. . .
كل هدف في هذا المسار هو برنامج قمت بإنشائه، ولا يوجد رابط على هذا ماك ينتج إدارة لينكس منledger.oإذاً ، المُثبتtiny-x86_64يأتي منcode/fixtures/build_tiny_elf.py، الذي يكتب 736 بايت مع struct.packالملف يحتوي على رأس ELF، رأس برنامجين، 36 بايت_start، 3 بايت .rodata، جدول رمزي، وستة عناوين قسم._startيكتبok\nفي لينكس x86-64 يعمل. هنا هو إصلاح لسهولة عنوان البرنامج.
textProgram headers:
PT_LOAD r-x offset=0x0 vaddr=0x400000 filesz=0x2e0 memsz=0x2e0 align=0x1000
PT_GNU_STACK rw- offset=0x0 vaddr=0x0 filesz=0x0 memsz=0x0 align=0x10
entry 0x4000b0 is file offset 0xb0 through PT_LOADالـ"فردي"PT_LOADيقوم بتخريط الملف بأكمله بـ 736 بايت في 0x400000القارئ يقوم بتخريط عنوان إلى ملف تعويض عن طريق العثور على PT_LOADالذي يحتوي عليه والحوسبةp_offset + (address - p_vaddr). لـ نقطة الدخول التي هي0 + (0x4000b0 - 0x400000) = 0xb0، بداية.text. . .
هذا هو الحساب وراء رياaddress_to_file_offsetبالنسبة لاهداف غيدرة، هذه الأداة تعود{address, file_offset}. مقدم " هوبر " أيضاً يعودprovider_file_offsetوimage_base_file_offset. تحتاج الأداة إلى نسخة أصلية كاملة من الملف، و يجب أن يطابق SHA-256 من تلك النسخة الهدف.inspect_binary_layoutيضيف سطر للحفاظ عليه: "ال عنوان المرتبط ليس عنوان وقت التشغيل". تحميل قابلة للتنفيذ مستقلة عن الموقع في قاعدة تختارها النجم، وتكون تعويض الملف هو الإحدى الإحداثيات المستقرة.
تعبر عناوين البرنامج أيضًا عن سبب استمرار تشغيل جهاز تنفيذية تم تجنبه. يمكن لشركة ربط إزالة جدول عناوين القسم بالكامل ، و e_shoffيصبح الصفر أداة التي تمشي في أقسام ثم لا ترى شيئا.
نفس الأفكار في ماخ-او
لا تنشر شركة آبل مواصفات بروتوكولية لمش-او. العناوين في مصدر XNU هي المواصفات: EXTERNAL_HEADERS/mach-o/loader.h،nlist.hوreloc.h- الجهازledger-arm64-O1.oهو نفس الشيءledger.cبنيت معclang -target arm64-apple-macos -O1 -fno-asynchronous-unwind-tables -c. . .
أmach_header_64هو 32 بايت: magic،cputype،cpusubtype،filetype،ncmds،sizeofcmds،flags،reservedالسحر هو0xfeedfacf،MH_MAGIC_64. .cputypeهو0x0100000c،CPU_TYPE_ARM64. .filetypeهو 1MH_OBJECT. .ncmdsهو 5 و sizeofcmdsهو 616: أوامر الحمل تشغل 616 بايت بعد الرأس، وكل واحد يبدأ مع {cmd, cmdsize}. العلم0x2000هوMH_SUBSECTIONS_VIA_SYMBOLS. . .
textLoad commands:
0 LC_SEGMENT_64 cmdsize=472 at 0x20
1 LC_BUILD_VERSION cmdsize=24 at 0x1f8
2 LC_LINKER_OPTIMIZATION_HINT cmdsize=16 at 0x210
3 LC_SYMTAB cmdsize=24 at 0x220
4 LC_DYSYMTAB cmdsize=80 at 0x238LC_SEGMENT_64(0x19) هو إجابة ماخ-او لكل من الجداول ELF.segment_command_64- نعمsegname،vmaddr،vmsize،fileoff،filesize،maxprot،initprot،nsectsوflags- إذنnsectsسجلات النوع section_6480 بايت لكل واحد، اتبعوا نفس الأوامر.loader.hتعليق علىMH_OBJECTيقول القسمات تعيش "في قطاع واحد غير مسمى دون ملابس قطاع".fileoff648filesize456، و 5 أقسام
text Idx Section Addr Size Offset Reloff Nreloc Type
1 __TEXT,__text 0x0 0xd0 648 1104 6 S_REGULAR
2 __DATA,__const 0xd0 0x28 856 1152 5 S_REGULAR
3 __TEXT,__cstring 0xf8 0xa 896 0 0 S_CSTRING_LITERALS
4 __TEXT,__const 0x108 0x40 912 0 0 S_REGULAR
5 __LD,__compact_unwind 0x148 0x80 976 1192 4 S_REGULARكل قسم يسمي قطاعه الأم في segname، لذا فجزأين يدعى__constيختلفون لأن واحد هو__DATA,__constوالآخر__TEXT,__const. عمليات الإنتقال هي لكل قسم من خلالreloffوnreloc، حيث ELF لديها منفصلة.rela.Xقسم لكل هدف
كل واحدrelocation_infoهو 8 بايت.r_addressيأتي أولاً. حقل 32 بت يتبع، معبأة مثل r_symbolnum(24 بت)r_pcrel(1) r_length(2) ، r_extern(1) و r_type(4) الستة __textالسجلات هي ثلاثة أزواج منARM64_RELOC_PAGE21وARM64_RELOC_PAGEOFF12، زوج واحد لكل جدول مرجعية.adrpبالإضافةaddهل ما واحد x86-64 leaلا، لذلك كل مرجع يحتاج إلى سجلين.__DATA,__constالسجلات هيARM64_RELOC_UNSIGNEDمعr_length3، لذلك كل واحد يصلح 8 بايت المؤشر المطلق.
عند -O1 تم الحفاظ على قاعدة الـ 64labelsكمرشحات بسيطة حيث بناء x86-64 صنع جدول نسبي.
LC_SYMTAB0x2) يحتفظsymoff1240، nsyms17stroff1512،strsize176 و 1512 + 176 = 1688، حجم الملفnlist_64هو 16 بايت: n_strx،n_type،n_sect،n_desc،n_value. .n_typeهو قليلا من الحقل.N_TYPE(قناع)0x0e) = N_SECT(0xe) تعني المحدد في القسم الذي يعد رقم 1n_sect. .N_EXT(0x01) يمثل رمز خارجي.
nlist_64ليس لديه حقل حجم. للحصول على 24 بايت من_percent_changeالقارئ يأخذ العنوان التالي للرمز في نفس القسم_gradeفي0x18الرموز C تحمل علامة قيادية_هنا، لذلك الوظيفة هي _percent_change. . .
LC_DYSYMTAB(0xb) يسجل نفس القاعدة المحلية أولاً مثل ELF sh_info: ilocalsym0nlocalsym13، iextdefsym13، nextdefsym4LC_BUILD_VERSION0x32) سجلات platform1 (macOS) ، minos26.0 و sdk26.2 كما هو مغلفXXXX.YY.ZZهذه هي السبب في أن جهاز arm64 يتم تعيينها كبايت. إنشاء على جهاز Mac مع SDK آخر ينتج ملفًا مختلفًا.
القارئcompare_formatsيضع الملفين جانباً إلى جانبه:
| Idea | ELF, ledger-x86_64-O1.o | Mach-O, ledger-arm64-O1.o |
|---|---|---|
| File identity | e_ident 7f 45 4c 46, class 64, data LSB | magic 0xfeedfacf |
| Machine | e_machine 62, EM_X86_64 | cputype 0x100000c, CPU_TYPE_ARM64 |
| File kind | e_type 1, ET_REL | filetype 1, MH_OBJECT |
| Where the tables are | e_shoff 0x428, 11 section headers | 5 load commands, 616 bytes after the header |
| The code | .text at 0x40, size 0x9f, flags AX | __TEXT,__text at 648, size 0xd0, in the unnamed segment |
| Symbol names | .symtab (section 10), names in .strtab through sh_link 1 | LC_SYMTAB symoff 1240, nsyms 17, names at stroff 1512 |
| Symbol size | st_size: percent_change is 16 bytes | No field: next symbol minus this one, 24 bytes |
| Relocations | .rela.text, 3 records, R_X86_64_PC32 | __text reloff 1104, 6 records, PAGE21 and PAGEOFF12 |
| Function bytes | percent_change: 16 bytes at 0x40 | _percent_change: 24 bytes at 0x288 |
" ريا " تقرأ " ماخ " او "inspect_macho، الذي يديرotool -h -l،nm -gjU،dyld_info -imports -exportsوdwarfdump --uuidمن خلالxcrunقرائك لا يحتاجوا إليها، ويقرأ نفسهاsymoffوstroffهذاotool -lبصماتopen_binaryأداة ريا يكتشف ELF، MZ، PE، والقليل أو عالمية Mach-O من البايت السحرية.e_machineهذا هو أول 20 بايت من ما بنيت هنا.format: raw-binaryالمزود في Ghidra يرفض ثنائي عالمي و يطلب قطعة نحيفة. قارئك يفعل الشيء نفسه عندما يرى0xcafebabe. . .
بناءها
تشغيل قراءة المكتبة القياسية:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.pyالظهور يقرأledger-x86_64-O1.oويقوم بطبع العنوان، والأقسام، والرموز، والتحويلات، وخريطة تعويض الملف. ثم يقوم بطبع السلاسل الأربعةledger-arm64-O1.oويقوم بطبع جدول المقارنة. ينتهي به عنوان البرنامج منtiny-x86_64- الخطوط المتوقعة:
textpercent_change: 16 bytes at file offset 0x40: 85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3
grade: 46 bytes at file offset 0x50: 48 63 c7 48 69 c0 67 66 66 66 ...
entry 0x4000b0 is file offset 0xb0 through PT_LOADمرر مسار لقراءة ملف واحد: python3 code/main.py code/fixtures/ledger-x86_64-O0.oالقارئ يختار ELF أو Mach-O من أول أربعة بايت
اقرأcode/main.pyفي هذا الترتيب:
ElfFile._read_header()يُحقق من السحر، والفئة، ترتيب البايت، وحجم الإدخال، ثم يفتح الرأس.ElfFile._read_sections()وread_cstring()اقرأ جدول عناوين القسم وافصل الاسماء من خلالe_shstrndx. . .ElfFile._read_symbols()الانقسامst_info، يُحلّ أسماء من خلالsh_linkويحافظ على إدراجات القسم المحجوز قابلة للقراءة.ElfFile._read_relocations()يَمْشي كلّSHT_RELAالقسم، التقسيمr_info، و أنواع الأسماء من الجدول 4.9.ElfFile.function_span()وfunction_bytes()تحويل رمز إلى مجموعة ملفات والبايتات في ذلك.ElfFile.file_offset_map()يُصنف كل هيكل حسب التعويض ويملأ الفضاء غير النشطElfFile._read_program_headers()وaddress_to_file_offset()معالجة المعدات التنفيذيةMachOFileيقرأ الرأس، ويقوم بإرسال الأوامر،LC_SEGMENT_64مع أقسامهاLC_SYMTAB،LC_BUILD_VERSION، و إعادة التنقل في كل قطعةcompare_formats()اسم نفس الفكرة في كلتا الملفين مع القيم القراءة من كل واحد.
استخدمها
استيراد القارئ من الدروس اللاحقة مع from main import ElfFileبعد إضافة هذه الدروسcodeدليل sys.pathالدروس 33 تطلب ذلكfunction_bytesوrelocations_for(".text"). الدروس 35 تسجل انتاجها كدليل مراقب. الدروس 36 تطلب من ذلك للتعويضات الإنتقالية لتخفيض قبل مقارنة البايت.
قم بتجميع C الخاص بك إلى كائن مع نفس clang -target x86_64-unknown-linux-gnu -cإضافة اسم التنظيم فقط عندما يردها الجدول x86-64 psABI جدول 4.9 إضافة نوع قسم فقط عندما يثبتها gABI أو سلسلة الأدوات الإنتاجية عندما يلتقي القارئ بقيمة لا يوجد لها اسم لها، فإنه يطبخ الرقم في السبع عشرية بدلا من التخمين.
المختبر التفاعلي
المختبر (أ): الرأس يدوياً
منcodeالإرشادات، مفتوحة Python:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch/code
python3 -qأركض
pythonimport struct
from main import ElfFile, FIXTURES
data = (FIXTURES / "ledger-x86_64-O1.o").read_bytes()
data[:4], data[4], data[5]
struct.unpack_from("<HHIQQQIHHHHHH", data, 16)
elf = ElfFile(data)
elf.header.e_shoff, elf.header.e_shnum, elf.sections[elf.header.e_shstrndx].nameتُظهر الـ " توبل "e_type1، e_machine62، و e_shoff1064, الذي هو 0x428السطر الأخير يظهر أن القسم 1.strtab، يحمل أسماء القسم
المختبر ب: جدول القيم وراء المفتاح
pythonrodata = elf.section_bytes(elf.section(".rodata"))
[int.from_bytes(rodata[i : i + 4], "little") for i in range(0x14, 0x28, 4)]
[int.from_bytes(rodata[i : i + 8], "little") for i in range(0x28, 0x50, 8)]
[r for r in elf.relocations_for(".text") if r.symbol == ".rodata"]كل من الجداول قراءة1, 2, 3, 4, 4تحقق منهم ضدledger.c: grade(65)الحسابات65 / 10 - 6 = 0ويقرأ المدخل 0 ، وهو 1 ، قيمة case 6. .grade(100)يقرأ المدخل 4 ، وهو 4 ، القيمة المشتركة من case 10وcase 9. . .
المختبر "ج": ثلاثة بناءات من مصدر واحد
pythonfor level in ("O0", "O1", "O2"):
obj = ElfFile.from_path(FIXTURES / f"ledger-x86_64-{level}.o")
print(level, [s.name for s in obj.sections if s.name in (".data", ".rodata")])
print(level, sorted({r.type_name for r in obj.relocations}))
print(level, {s.name: s.st_size for s in obj.symbols if s.type == "FUNC"})كائن -0 لديه .dataالقسم و ثلاثة أنواع نقل..dataو نوع واحدtotalهو 66 بايت في -O0، 31 في -O1، و 140 في -O2.
المختبر D: نفس الوظيفة في ماخ-او
pythonfrom main import MachOFile
macho = MachOFile.from_path(FIXTURES / "ledger-arm64-O1.o")
[(c.name, c.cmdsize) for c in macho.load_commands]
macho.function_span("_percent_change")
macho.function_bytes("_percent_change")[:4].hex()فترة الاختلاف هي 24 بايت في ملف تعويض 648. أول أربعة بايت هي a0000034، شكل التدريس الصغير0x340000a0، الذيllvm-objdump -dبصمات مثلcbz w0, 0x14هذا هوbefore == 0الحارس، نفس الاختبار الذي يرمز بناء x86-6485 ff 74 09. . .
مختبر التدريب
تمديد القارئ إلى برنامج متصل. على آلة لينكس x86-64، بناء clang -O1 -shared -fPIC code/fixtures/ledger.c -o libledger.soوclang -O1 code/fixtures/ledger.c -o ledger_mainمعmainتضيف، ثم تشغيل القارئ على كليهما.
المتطلبات:
- اقرأ
.dynsymمع نفسهاElf64_Symالتخطيط، منذSHT_DYNSYMهو 11 والسجل غير متغير. - اقرأ
.dynamicكـElf64_Dynسجلاتd_tagوd_val، والاسمDT_NEEDED،DT_STRTAB،DT_SYMTAB،DT_RELAوDT_JMPRELمن صفحة gABI "قسم الديناميكي". - قم بإدراج الرموز غير المحددة
st_shndxSHN_UNDEFمثل الواردات، والـR_X86_64_JUMP_SLOTالسجلات في.rela.pltكمواقع المكالمات التي تصل إليهم - خريطة كل
PT_LOADالقسم إلى القسمات التي تحتوي عليها، باستخدام قاعدة gABI أن "قسم ملف كائن يحتوي على قسم واحد أو أكثر". - إثبت ذلك
address_to_file_offsetأن بايت كل وظيفة محددة في الكائن المشترك هي بايتfunction_bytesيعود - القبض
llvm-objdump -h -t -r -pوllvm-objdump -Tالنص لكلتا الملفين فيcode/fixtures/ويقارن القارئ معهم في الاختبارات
النجاح يعني أن قائمة الاستيراد وخريطة القطاع، وكل مجموعة من الوظائف تتفق مع إصدار الأداة المسجلة، دون تشغيل الأداة خلال الاختبارات.
درساً أساسياً
هذا الدرس يُنتجoutputs/skill-program-anatomy-elf-from-scratch.md. استخدمها مع كلود، كورسور، كودكس، OpenClaw، هرمز، أو أي وكيل يقرأ SKILL.md. تحويل المهارة إلى كائن مرتب أو قابل للتنفيذ الذي قام المستخدم ببناءه في تقرير التخطيط. يعطي التقرير مجموعة الملفات لكل هيكل، رمز، وإعادة التنظيم، مع التحقق من أداة المخرجة المسجلة.
تحقق من ذلك
إشغال التجربة والسلسلة التحديدية:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.py
python3 -m unittest discover -s code/tests -vالفندق يثبت:
- كل إندكس قسم، الاسم، الحجم، العنوان، والنوع يطابق المقبض عليه
llvm-objdump -hنص لثلاثة كائنات x86-64 و التنفيذية الصغيرة - كل قيمة رمزية، والربط، والنوع، والقسم، والحجم، والاسم يطابقون ما تم التقاطه
llvm-objdump -tالنص - كل تعويض نقل، اسم النموذج، الرمز، والإضافة يطابق ما تم التقاطه
llvm-objdump -rالنص، بما في ذلكR_X86_64_PLT32وR_X86_64_64في -0 - كل من رؤساء البرنامج
tiny-x86_64يطابقون المعتقلينllvm-objdump -pالنص، والكائنات لا تملك أي percent_changeهو 16 بايت في تعويض الملف0x40مع البايتات المتوقعة، وكل وظيفة تقع داخل.text- خريطة تعويض الملف تغطي كل بايت من كل جهاز مرة واحدة ، مع ثلاثة فجوات التوجه من -O1 الكائن في مواقعها
- نقطة دخول
tiny-x86_64خرائط لتعويض الملف0xb0من خلالPT_LOAD، و كائن قابل للتنقل يرفض رسم الخرائط - الرأس "ماك او" كل أمر تحميل، القطاع، الخمسة أقسام
LC_SYMTABوLC_BUILD_VERSIONيطابقون المعتقلين--private-headersالنص - الرموز " ماخ " و 15 إعادة التنقل تتطابق مع المقبوضين
llvm-objdumpالنص، و_percent_changeهو 24 بايت عند 648 - السحر الخطأ، فئة 32 بت، ملف الكبير منديان، جدول قصير، ثنائي عالمي، وملف مصدر C كل رفع.
FormatError build_tiny_elf.pyيعيد إعادة التعبير عن الالتزامtiny-x86_64بايت مقابل بايت- الوحدة الرقمية
site/figures-re-32.jsيحمل البايتات الدقيقة منledger-x86_64-O1.o
أساليب فشل الإنتاج
| Failure | What the weak reading reports | What the reader must prove |
|---|---|---|
st_value used as a file offset | "percent_change is at 0" | Offset is sh_offset + st_value - sh_addr, here 0x40 |
| Padding counted as code | "grade is 48 bytes" | st_size is 46 and the 66 90 after it belongs to no symbol |
| Section names read from the wrong table | Garbage names or an exception | e_shstrndx names the table, here section 1 |
sh_size trusted for SHT_NOBITS | A read past the end of the file | .bss has no file bytes and the map skips it |
| Zero bytes read as the operand | "lea 0(%rip)" | The .rela.text record at 0x36 names .rodata+0x10 |
| Addend read as the target offset | "the table starts at 0x10" | S + A - P with the instruction end gives 0x14 |
| Sections walked on a stripped executable | "the file has no code" | PT_LOAD maps every address the loader uses |
| A linked address treated as a runtime address | A wrong offset under a random base | The file offset from PT_LOAD is the stable coordinate |
A Mach-O section looked up by sectname alone | Two __const sections collapse into one | segname,sectname is the name |
A size field expected in nlist_64 | An exception or zero bytes | The next symbol in the same section ends the function |
| A universal binary read as one Mach-O | "magic 0xcafebabe is unknown" | Refuse it and ask for a thinned slice |
| The arm64 object rebuilt on another Mac | "the fixture changed" | LC_BUILD_VERSION records the SDK, so the committed bytes are the fixture |
قاعدة العمليات
اقرأ الرأس الذي يمكنك الوثوق به، اتبع تعويضاتها، وسمي كل حقل من المواصفات التي تحددها. احتفظ بمخرج الأداة التي مقارنتها بها كصيغة ملتزمة، بحيث يتم تشغيل المقارنة دون الأداة. عندما لا يكون لدى قيمة اسم في المواصفات، طبع الرقم. القارئ الذي يمكنك التحقق منه بايت بوايت هو الذي تقوم كل درس لاحقا على بناء عليه.
المزيد من القراءة
- System V gABI, chapter 4: Object Filesو ELF Header،Sections،Symbol TableوRelocationالصفحات
- System V gABI, chapter 5: Program HeaderوDynamic Linking
- System V ABI, AMD64 Architecture Processor Supplementالجدول 4.9 أنواع الإنتقال
- Apple XNU
loader.h،nlist.hوreloc.h - 17 U.S.C. 1201وDirective 2009/24/EC
- Sega v. Accolade, 977 F.2d 1510وSony v. Connectix, 203 F.3d 596
- rea 6.5.0
docs/binary-diagnostics.mdوdocs/ghidra-provider.md
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.