Phase 13: Tools & Protocols

تشريح البرنامج: قارئ 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) غير صالحة.

SourceWhat it allowsConditions
17 U.S.C. 1201(f)Circumventing an access control to analyze a programLawful copy, interoperability purpose, information not readily available, no infringement
Directive 2009/24/EC Art. 5(3)Observing, studying, and testing a running programA right to use the copy, acts within normal loading and running
Directive 2009/24/EC Art. 6DecompilationLicensee, interoperability purpose, information not readily available, confined to the needed parts
Sega v. AccoladeDisassembly as fair useThe only way to reach the ideas and functional elements, a legitimate reason
Sony v. ConnectixIntermediate copies during reverse engineeringA 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. . .

يجمع المصدر نفسه إلى ثلاث جداول مختلفة في ثلاثة مستويات تحسين. يظهر القارئ الفرق دون إزالة:

BuildWhere labels livesHow grade selectsHow grade_label reaches grade
-O0.data, five 8-byte pointers, five R_X86_64_64 recordsA jump table in .rodata: five R_X86_64_PC32 records pointing into .textA call with R_X86_64_PLT32 grade-0x4
-O1.rodata as labels.rel, five 32-bit relative entriesA value table at .rodata+0x14, one R_X86_64_PC32Inlined, no call
-O2Same as -O1Same as -O1Inlined, 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 0x238

LC_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يضع الملفين جانباً إلى جانبه:

IdeaELF, ledger-x86_64-O1.oMach-O, ledger-arm64-O1.o
File identitye_ident 7f 45 4c 46, class 64, data LSBmagic 0xfeedfacf
Machinee_machine 62, EM_X86_64cputype 0x100000c, CPU_TYPE_ARM64
File kinde_type 1, ET_RELfiletype 1, MH_OBJECT
Where the tables aree_shoff 0x428, 11 section headers5 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 1LC_SYMTAB symoff 1240, nsyms 17, names at stroff 1512
Symbol sizest_size: percent_change is 16 bytesNo 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 bytespercent_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في هذا الترتيب:

  1. ElfFile._read_header()يُحقق من السحر، والفئة، ترتيب البايت، وحجم الإدخال، ثم يفتح الرأس.
  2. ElfFile._read_sections()وread_cstring()اقرأ جدول عناوين القسم وافصل الاسماء من خلال e_shstrndx. . .
  3. ElfFile._read_symbols()الانقسامst_info، يُحلّ أسماء من خلال sh_linkويحافظ على إدراجات القسم المحجوز قابلة للقراءة.
  4. ElfFile._read_relocations()يَمْشي كلّ SHT_RELAالقسم، التقسيمr_info، و أنواع الأسماء من الجدول 4.9.
  5. ElfFile.function_span()وfunction_bytes()تحويل رمز إلى مجموعة ملفات والبايتات في ذلك.
  6. ElfFile.file_offset_map()يُصنف كل هيكل حسب التعويض ويملأ الفضاء غير النشط
  7. ElfFile._read_program_headers()وaddress_to_file_offset()معالجة المعدات التنفيذية
  8. MachOFileيقرأ الرأس، ويقوم بإرسال الأوامر،LC_SEGMENT_64مع أقسامهاLC_SYMTAB،LC_BUILD_VERSION، و إعادة التنقل في كل قطعة
  9. 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_shndx SHN_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

أساليب فشل الإنتاج

FailureWhat the weak reading reportsWhat 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 tableGarbage names or an exceptione_shstrndx names the table, here section 1
sh_size trusted for SHT_NOBITSA 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 addressA wrong offset under a random baseThe file offset from PT_LOAD is the stable coordinate
A Mach-O section looked up by sectname aloneTwo __const sections collapse into onesegname,sectname is the name
A size field expected in nlist_64An exception or zero bytesThe 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

قاعدة العمليات

اقرأ الرأس الذي يمكنك الوثوق به، اتبع تعويضاتها، وسمي كل حقل من المواصفات التي تحددها. احتفظ بمخرج الأداة التي مقارنتها بها كصيغة ملتزمة، بحيث يتم تشغيل المقارنة دون الأداة. عندما لا يكون لدى قيمة اسم في المواصفات، طبع الرقم. القارئ الذي يمكنك التحقق منه بايت بوايت هو الذي تقوم كل درس لاحقا على بناء عليه.

المزيد من القراءة

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.