مفاهيم متقدمة متعلقة بصيغة ملفات ELF القابلة للتنفيذ
تُعدّ صيغة ملفات ELF (Executable and Linkable Format) من أكثر صيغ الملفات القابلة للتنفيذ شيوعاً على أنظمة يونكس وLinux الحديثة. صُممت هذه البنية لتكون مرنة ومحمولة، وتدعم مختلف المعالجات المعمارية، وتُستخدم ليس فقط للملفات القابلة للتنفيذ، بل أيضًا للملفات القابلة للربط الديناميكي والمكتبات المشتركة وملفات النواة.
لتحقيق فهم عميق ومتقدم حول صيغة ELF، من الضروري تحليل بنيتها الداخلية وتفاعلها مع النواة، آليات التحميل، معالجة الرموز (symbols)، وموضوعات متعلقة بالأمان مثل تقنيات الحماية من الاستغلال والهندسة العكسية.
الهيكل الداخلي لملف ELF
يتكون ملف ELF من ثلاثة أجزاء أساسية:
-
رأس الملف ELF Header
-
جدول ترويسات المقاطع Program Header Table
-
جدول ترويسات الأقسام Section Header Table
1. رأس الملف ELF Header
يحتوي على معلومات عامة حول الملف مثل:
-
معرف الملف (magic number)
-
نوع الملف (تنفيذي، مكتبة مشتركة، ملف rel، إلخ)
-
المعمارية المستهدفة (x86, x86_64, ARM, RISC-V)
-
نقطة الدخول إلى البرنامج (Entry Point)
-
إزاحة جدول رؤوس البرنامج
-
إزاحة جدول رؤوس الأقسام
-
حجم كل من هذه الجداول وعدد مدخلاتها
2. جدول رؤوس البرنامج (Program Header Table)
هذا الجدول يحتوي على معلومات حول كيفية تحميل البرنامج في الذاكرة. كل مدخل في هذا الجدول يُمثّل جزءًا من الملف يجب تحميله أو تفسيره بطريقة معينة.
أنواع المداخل:
-
PT_LOAD: يُمثل جزءًا يجب تحميله في الذاكرة.
-
PT_DYNAMIC: يحتوي على معلومات الربط الديناميكي.
-
PT_INTERP: يُحدد المسار إلى محمل الربط الديناميكي (ld.so).
-
PT_NOTE: يحتوي على معلومات حول ABI أو تصحيح الأخطاء.
-
PT_TLS: يمثل معلومات حول تخزين TLS.
3. جدول رؤوس الأقسام (Section Header Table)
يُستخدم بشكل أساسي خلال مرحلة الربط وليس خلال تنفيذ البرنامج. يضم هذا الجدول أقسام مثل:
-
.text: كود البرنامج
-
.data: البيانات القابلة للتعديل
-
.bss: البيانات المُهيّأة إلى صفر
-
.symtab / .dynsym: جداول الرموز
-
.strtab / .dynstr: جداول السلاسل النصية للرموز
-
.rel/.rela: جداول إعادة التوطين (Relocation)
الربط الديناميكي ودور الـ PLT و GOT
عند تنفيذ برنامج يستخدم مكتبات ديناميكية، لا يتم تحميل جميع عناوين الدوال في البداية، بل تُستخدم آلية تُعرف بـ “الربط الكسول” (Lazy Binding) لتحقيق الأداء الأفضل.
Global Offset Table (GOT)
هي بنية بيانات تحتوي على عناوين الدوال والرموز الديناميكية. في البداية، يتم ملء مدخلات GOT بعناوين توجيهية إلى نقطة في الـ PLT.
Procedure Linkage Table (PLT)
تحتوي على تعليمات تُوجه نداءات الدوال الخارجية إلى المدخل المناسب في GOT. إذا لم يكن العنوان محملاً بعد، يتجه التنفيذ إلى محمل الربط ld-linux.so لملء العنوان في GOT.
تعمل PLT وGOT معًا لتوفير آلية فعالة لتأجيل ربط الرموز حتى لحظة الاستدعاء الأول، مما يقلل من زمن تحميل البرنامج.
إعادة التوطين (Relocation)
عندما يُربط برنامج بشكل ديناميكي، يجب تعديل بعض العناوين داخل الكود والبيانات لتتناسب مع العنوان الذي تُحمّل فيه هذه الكيانات في الذاكرة. هناك نوعان رئيسيان من معلومات إعادة التوطين:
-
Rel: تحتوي فقط على الإزاحة والنوع، وتُستخدم مع قيم في الذاكرة صفرية.
-
Rela: تحتوي على الإزاحة والنوع والقيمة المضافة (Addend)، وهي أكثر شيوعًا في الأنظمة التي تستخدم تنسيق ELF64.
عملية إعادة التوطين تتم خلال الربط أو التحميل، ويقوم بها محمل النظام أو linker مثل ld-linux.so.
امتيازات الأمان في ملفات ELF
Address Space Layout Randomization (ASLR)
إحدى آليات الحماية التي تُطبق على ملفات ELF. تقوم ASLR بإعادة ترتيب توزيع أقسام البرنامج (stack، heap، المكتبات الديناميكية، إلخ) في الذاكرة في كل تنفيذ. هذا يجعل استغلال الثغرات أصعب لأن المهاجم لا يمكنه التنبؤ بمواقع معينة في الذاكرة.
Stack Canaries
عبارة عن قيمة تُضاف بين متغيرات الدالة في المكدس وعنوان الإرجاع. في حال وجود تجاوز للمكدس (Stack Overflow)، تتغير هذه القيمة مما يسمح لكاشف الاستغلال بمقاطعة التنفيذ.
NX Bit (No Execute)
تُستخدم لمنع تنفيذ صفحات معينة من الذاكرة (مثل stack وheap). يتم تحديد ذلك في جدول رؤوس البرنامج باستخدام علم PF_X.
RELRO (Relocation Read-Only)
تُستخدم لجعل بعض أجزاء من جدول GOT غير قابلة للكتابة بعد الانتهاء من الربط الديناميكي، مما يمنع المهاجمين من إعادة توجيه الاستدعاءات.
الأنواع:
-
Partial RELRO: يطبّق فقط حماية جزئية على بعض أجزاء GOT.
-
Full RELRO: يُجمد كامل جدول GOT ويمنع تعديله أثناء التنفيذ.
الجدول التالي يوضح مقارنة بين أقسام ELF الرئيسية ووظائفها:
| اسم القسم | الوصف | يُستخدم أثناء | قابل للتنفيذ؟ | قابل للكتابة؟ |
|---|---|---|---|---|
| .text | كود الآلة التنفيذي | التشغيل | نعم | لا |
| .data | بيانات قابلة للتعديل | التشغيل | لا | نعم |
| .bss | بيانات مُهيّأة للصفر | التشغيل | لا | نعم |
| .rodata | بيانات ثابتة غير قابلة للتعديل | التشغيل | لا | لا |
| .symtab | جدول الرموز الكامل | الربط/التصحيح | لا | لا |
| .dynsym | جدول الرموز الديناميكي | التشغيل | لا | لا |
| .rel.text / .rela.text | إعادة توطين للكود | التشغيل/الربط | لا | لا |
| .plt | جدول ربط إجراءات ديناميكي (PLT) | التشغيل | نعم | لا |
| .got | جدول تعويضات عامة (GOT) | التشغيل | لا | نعم |
أدوات تحليل ملفات ELF
العمل مع ملفات ELF يتطلب مجموعة من الأدوات الاحترافية:
-
readelf: لفحص بنية ملف ELF
-
objdump: لتفكيك الكود وتحليل الأقسام
-
nm: لعرض الرموز
-
ldd: لعرض تبعيات المكتبات الديناميكية
-
strace/ltrace: لتعقب الاستدعاءات النظامية والدوال
-
gdb: مصحح أخطاء قوي للتنفيذ والتحليل الديناميكي
مثال على استخدام readelf:
bashreadelf -h my_binary readelf -S my_binary readelf -l my_binary
الهندسة العكسية وتحليل الملفات الخبيثة
معرفة بنية ELF ضرورية أيضًا في مجال الأمن السيبراني، خصوصًا في تحليل البرمجيات الخبيثة. تُستخدم تقنيات مثل:
-
تحليل الـ .text لفهم كيفية عمل البرنامج.
-
مراجعة الـ .got و .plt لمعرفة نداءات النظام أو المكتبات.
-
استخراج الرموز من .dynsym.
-
تحديد تقنيات التشفير أو الحماية المستخدمة.
تطورات حديثة في صيغة ELF
مع تطور الأنظمة، تم إدخال تحسينات كثيرة على ملفات ELF، أبرزها:
-
دعم التنسيقات المتقدمة مثل DWARF للـ Debugging.
-
دعم معمارية RISC-V وARM64.
-
استخدام تقنيات ضغط الأقسام لتوفير المساحة.
-
دعم أقسام TLS لتوفير متغيرات خاصة بكل خيط تنفيذ.
-
دعم Lazy Binding المحسن باستخدام تقنية STT_GNU_IFUNC.
الخلاصة التقنية
فهم بنية ملفات ELF لا يقتصر على المبرمجين منخفضي المستوى، بل يمتد إلى مجالات تحليل الأداء، أمان البرمجيات، بناء الأنظمة، والحوسبة السحابية. من خلال استكشاف المكونات الداخلية مثل رؤوس البرامج والأقسام، ومعالجة الرموز والربط الديناميكي، يستطيع المهندس أو الباحث بناء أدوات فعالة لتحليل البرامج، كشف الثغرات، أو تحسين الحماية.
تُعد صيغة ELF مثالًا على عبقرية التصميم الذي يوازن بين الكفاءة، القابلية للتوسعة، ودعم التوافقية عبر مختلف المعماريات والمنصات.
المراجع:
-
System V Application Binary Interface – AMD64 Architecture Processor Supplement
-
“Linkers and Loaders” by John R. Levine

