
سوف تركز هذه المقالة على ميزة أمان صغيرة تمت إضافتها في جنو إلى الإصدار 2.30 في ديسمبر 2018. باللغة الروسية ، تم ذكر هذا التحسن على opennet مع التعليق التوضيحي التالي:
وضع "-z رمز منفصل" ، مما يزيد من أمان الملفات القابلة للتنفيذ بتكلفة زيادة صغيرة في الحجم واستهلاك الذاكرة
دعونا معرفة ذلك. لشرح نوع مشكلة الأمان التي نتحدث عنها وما هو الحل ، دعنا نبدأ بالميزات العامة لاستغلال الثغرة الأمنية الثنائية.
استغلال قضايا التحكم في التدفق
يمكن للمهاجم نقل البيانات إلى البرنامج والتعامل معها بهذه الطريقة بمساعدة نقاط ضعف مختلفة: الكتابة عن طريق الفهرس خارج حدود مجموعة ، النسخ غير الآمن للسلاسل ، استخدام الكائنات بعد الإصدار. هذه الأخطاء نموذجية لرمز البرنامج C و C ++ ويمكن أن تؤدي إلى تلف الذاكرة مع بعض بيانات الإدخال للبرنامج.
نقاط الضعف تلف الذاكرةCWE-20: التحقق من صحة إدخال غير صحيح
CWE-118: وصول غير صحيح للمورد القابل للفهرسة ("خطأ في النطاق")
CWE-119: تقييد العمليات بشكل غير صحيح ضمن حدود ذاكرة مؤقتة
CWE-120: نسخة المخزن المؤقت دون التحقق من حجم الإدخال ("تجاوز سعة المخزن المؤقت الكلاسيكي")
CWE-121: تجاوز سعة المخزن المؤقت القائم على المكدس
CWE-122: تجاوز سعة المخزن المؤقت المستند إلى كومة الذاكرة المؤقتة
CWE-123: اكتب شرط ماذا
CWE-124: الاكتتاب المؤقت ("تجاوز سعة المخزن المؤقت")
CWE-125: قراءة خارج الحدود
CWE-126: قراءة المخزن المؤقت
CWE-127: العازلة تحت القراءة
CWE-128: خطأ الالتفاف
CWE-129: التحقق من صحة غير صحيح من فهرس الصفيف
CWE-130: المعالجة غير الصحيحة لطول معلمة التناقض
CWE-131: حساب غير صحيح من حجم المخزن المؤقت
CWE-134: استخدام سلسلة التنسيق الخارجي
CWE-135: حساب غير صحيح لطول سلسلة البايتات المتعددة
CWE-170: إنهاء باطل غير صحيح
CWE-190: تجاوز عدد صحيح أو التفاف
CWE-415: مزدوج مجاني
CWE-416: استخدم بعد الاستخدام مجانًا
CWE-476: NULL Pointer Dereference
CWE-787: الكتابة خارج الحدود
CWE-824: الوصول إلى مؤشر غير مهيأ
...
عنصر الاستغلال الكلاسيكي للثغرات المشابهة للفساد في الذاكرة هو الكتابة فوق مؤشر في الذاكرة. بعد ذلك ، سيتم استخدام المؤشر من قبل البرنامج لنقل التحكم إلى رمز آخر: لاستدعاء طريقة أو وظيفة فئة من وحدة نمطية أخرى ، للعودة من وظيفة. ونظرًا لأنه تم استبدال المؤشر ، فسيتم اعتراض المهاجم على عنصر التحكم - أي أنه سيتم تنفيذ التعليمات البرمجية التي أعدها. إذا كنت مهتمًا بالصيغ والتفاصيل الخاصة بهذه التقنيات ، نوصي بقراءة المستند .
تُعرف هذه اللحظة المشتركة لتشغيل مثل هذه المآثر ، وهنا تم وضع الحواجز على المهاجم منذ فترة طويلة:
- التحقق من سلامة المؤشرات قبل تمرير التحكم: ملفات تعريف الارتباط المكدس ، واقي التحكم في التدفق ، مصادقة المؤشر
- التوزيع العشوائي لعناوين المقاطع مع الكود والبيانات: التوزيع العشوائي لتخطيط مساحة العنوان
- منع تنفيذ التعليمات البرمجية خارج مقاطع التعليمات البرمجية: حماية المساحة القابلة للتنفيذ
بعد ذلك ، سوف نركز على حماية النوع الأخير.
حماية الفضاء القابلة للتنفيذ
ذاكرة البرنامج غير متجانسة وتنقسم إلى شرائح لها حقوق مختلفة: القراءة والكتابة والتنفيذ. يتم ضمان ذلك من خلال قدرة المعالج على تمييز صفحات الذاكرة بإشارات الوصول في جداول الصفحات. تعتمد فكرة الحماية على الفصل الصارم للرمز والبيانات: يجب وضع البيانات الواردة من المهاجم أثناء معالجتها في شرائح غير قابلة للتنفيذ (مكدس ، كومة) ، ورمز البرنامج نفسه - في مقاطع منفصلة غير قابلة للتغيير . وبالتالي ، يجب أن يحرم هذا المهاجم من القدرة على وضع وتنفيذ تعليمات برمجية غريبة في الذاكرة.
للتحايل على حظر تنفيذ التعليمات البرمجية في مقاطع البيانات ، يتم استخدام تقنيات إعادة استخدام الرمز. أي أن المهاجم ينقل التحكم إلى أجزاء التعليمات البرمجية (المشار إليها فيما يلي باسم الأدوات الذكية) الموجودة على الصفحات القابلة للتنفيذ. تقنيات من هذا النوع هي متفاوتة الصعوبة ، في ترتيب متزايد:
- تمرير التحكم إلى دالة تقوم بما يكفي للمهاجمين: إلى الدالة () system باستخدام وسيطة محكومة لتشغيل أوامر shell التعسفية (ret2libc)
- نقل التحكم إلى وظيفة أو سلسلة من الأدوات الذكية التي ستعطل الحماية أو تجعل جزءًا من الذاكرة قابلة للتنفيذ (على سبيل المثال ، استدعاء
mprotect()
) ، متبوعة بتنفيذ التعليمات البرمجية التعسفية - تنفيذ جميع الإجراءات المطلوبة باستخدام سلسلة طويلة من الأدوات
وبالتالي ، يواجه المهاجم مهمة إعادة استخدام التعليمات البرمجية الموجودة في وحدة تخزين واحدة أو أخرى. إذا كان هذا الأمر أكثر تعقيدًا من العودة إلى وظيفة واحدة ، فستكون هناك حاجة إلى سلسلة من الأدوات الذكية . للبحث عن الأدوات الذكية حسب القطاعات القابلة للتنفيذ ، هناك أدوات: ropper ، ropgadget .
ثقب READ_IMPLIES_EXEC
ومع ذلك ، في بعض الأحيان يمكن أن تكون مناطق الذاكرة مع البيانات قابلة للتنفيذ ، ومبادئ الفصل بين التعليمات البرمجية والبيانات المذكورة أعلاه تنتهك بوضوح. في مثل هذه الحالات ، يتم تجنب المهاجم عناء العثور على أدوات أو وظائف لإعادة استخدام الرمز. كان اكتشافًا مثيرًا للاهتمام من هذا النوع هو المكدس القابل للتنفيذ وجميع قطاعات البيانات على نفس "جدار الحماية الصناعي".
/proc/$pid/maps
:
00008000-00009000 r-xp 00000000 08:01 10 /var/flash/dmt/nx_test/a.out 00010000-00011000 rwxp 00000000 08:01 10 /var/flash/dmt/nx_test/a.out 00011000-00032000 rwxp 00000000 00:00 0 [heap] 40000000-4001f000 r-xp 00000000 1f:02 429 /lib/ld-linux.so.2 4001f000-40022000 rwxp 00000000 00:00 0 40027000-40028000 r-xp 0001f000 1f:02 429 /lib/ld-linux.so.2 40028000-40029000 rwxp 00020000 1f:02 429 /lib/ld-linux.so.2 4002c000-40172000 r-xp 00000000 1f:02 430 /lib/libc.so.6 40172000-40179000 ---p 00146000 1f:02 430 /lib/libc.so.6 40179000-4017b000 r-xp 00145000 1f:02 430 /lib/libc.so.6 4017b000-4017c000 rwxp 00147000 1f:02 430 /lib/libc.so.6 4017c000-40b80000 rwxp 00000000 00:00 0 be8c2000-be8d7000 rwxp 00000000 00:00 0 [stack]
هنا ترى بطاقة الذاكرة لعملية أداة الاختبار. تتكون الخريطة من مناطق الذاكرة - صفوف الجدول. أولاً ، انتبه إلى العمود الأيمن - فهو يشرح محتويات المنطقة (مقاطع التعليمات البرمجية ، بيانات مكتبات الوظائف أو البرنامج نفسه) أو نوعه (الكومة ، المكدس). على اليسار ، بالترتيب ، نطاق العناوين التي تشغلها كل منطقة ذاكرة ، علاوة على ذلك ، الوصول إلى إشارات حقوق: r (قراءة) ، w (كتابة) ، x (تنفيذ). تحدد هذه العلامات سلوك النظام عند محاولة قراءة الذاكرة وكتابتها وتنفيذها على هذه العناوين. في حالة انتهاك وضع الوصول المحدد ، يتم طرح استثناء.
لاحظ أن جميع الذاكرة الموجودة داخل العملية تقريبًا قابلة للتنفيذ: المكدس ، الكومة ، وجميع قطاعات البيانات. هذه مشكلة من الواضح أن وجود صفحات rwx للذاكرة سيجعل الحياة أسهل للمهاجمين ، لأنه سيكون قادرًا على تنفيذ التعليمات البرمجية الخاصة به بحرية في مثل هذه العملية في أي مكان تحصل عليه الكود عند نقل البيانات (الحزم ، الملفات) إلى مثل هذا البرنامج للمعالجة.
لماذا نشأ مثل هذا الموقف على جهاز حديث يدعم حظر تنفيذ التعليمات البرمجية على صفحات البيانات مع الأجهزة ، هل يعتمد أمن الشبكات التجارية والصناعية على الجهاز ، والمشكلة التي تم الصوت لها وحلها معروفان لفترة طويلة جدًا؟
يتم تحديد هذه الصورة بسلوك النواة أثناء تهيئة العملية (تخصيص مكدس ، كومة ، تحميل ELF الرئيسي ، وما إلى ذلك) وأثناء تنفيذ استدعاءات العملية النووية. السمة الرئيسية التي تؤثر على هذا هي علامة الشخصية READ_IMPLIES_EXEC
. تأثير هذه العلامة هو أن أي ذاكرة قابلة للقراءة تصبح قابلة للتنفيذ أيضًا. يمكن تعيين علامة على العملية الخاصة بك لعدة أسباب:
- يمكن طلب الإرث بشكل صريح بواسطة علامة البرنامج في رأس ELF لتنفيذ آلية شيقة جدًا: نقطة انطلاق على المكدس ( 1 ، 2 ، 3 )!
- يمكن أن تكون موروثة بواسطة العمليات التابعة من الوالد.
- يمكن تثبيته بواسطة النواة بشكل مستقل لجميع العمليات! أولاً ، إذا كانت البنية لا تدعم الذاكرة غير القابلة للتنفيذ. ثانيا ، فقط في حالة ، لدعم بعض العكازات القديمة الأخرى. هذا الكود في النواة 2.6.32 (ARM) ، التي كان لها عمر طويل للغاية. كان هذا مجرد قضيتنا.
مساحة للعثور على الأدوات في صورة ELF
مكتبات الوظائف والبرامج التنفيذية في تنسيق ELF. يقوم مترجم gcc بترجمة بنيات اللغة إلى رمز الجهاز ووضعه في قسم واحد ، والبيانات التي يعمل بها هذا الرمز في أقسام أخرى. هناك العديد من الأقسام ويتم تجميعها بواسطة رابط ld في مقاطع. وبالتالي ، يحتوي ELF على صورة برنامج لها تمثيلان: جدول المقاطع وجدول المقاطع.
$ readelf -l /bin/ls Elf file type is EXEC (Executable file) Entry point 0x804bee9 There are 9 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 RE 0x4 INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x1e40c 0x1e40c RE 0x1000 LOAD 0x01ef00 0x08067f00 0x08067f00 0x00444 0x01078 RW 0x1000 DYNAMIC 0x01ef0c 0x08067f0c 0x08067f0c 0x000f0 0x000f0 RW 0x4 NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 GNU_EH_FRAME 0x018b74 0x08060b74 0x08060b74 0x00814 0x00814 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x01ef00 0x08067f00 0x08067f00 0x00100 0x00100 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got
هنا ترى تعيين المقاطع إلى مقاطع في صورة ELF.
يتم استخدام جدول القسم بواسطة الأدوات المساعدة لتحليل البرامج والمكتبات ، ولكن لا يتم استخدامه بواسطة اللوادر لعرض إسقاط ELF في ذاكرة المعالجة. يصف جدول القسم بنية ELF بمزيد من التفاصيل من جدول القطعة. يمكن أن تكون عدة أقسام داخل قطعة واحدة.
يتم إنشاء صورة ELF في الذاكرة بواسطة لوادر ELF بناءً على محتويات جدول القطعة . لم يعد يتم استخدام جدول القسم لتحميل ELF في الذاكرة.
ولكن هناك استثناءات لهذه القاعدة.على سبيل المثال ، في الطبيعة ، يوجد تصحيح لمطوري دبيان لمحمّل ELF ld.so لبنية ARM ، والذي يبحث عن قسم ".ARM.attributes" خاص مثل SHT_ARM_ATTRIBUTES ولا يتم تحميل ثنائيات مع جدول قسم منشور في مثل هذا النظام ...
يشتمل مقطع ELF على علامات تحدد أذونات المقطع في الذاكرة. تقليديًا ، تم تصميم معظم برامج GNU / Linux بطريقة تم إعلان PT_LOAD
( PT_LOAD
للتحميل في الذاكرة) في جدول المقاطع - كما في القائمة أعلاه:
قطعة مع أعلام RE
1.1. رمز ELF القابل للتنفيذ : مقاطع .init
و. .fini
و .fini
1.2. بيانات غير .symtab
للتغيير في ELF: .symtab
، .rodata
أعلام RW
الجزء
2.1. البيانات المتغيرة في ELF: مقاطع .got
، .data
، .bss
، .bss
إذا كنت تهتم بتكوين الجزء الأول وإشارات الوصول الخاصة به ، يصبح من الواضح أن مثل هذا التخطيط يوسع مساحة البحث عن الأدوات الذكية لتقنيات إعادة استخدام الكود. في ELFs الكبيرة مثل libcrypto ، يمكن أن تحتل جداول الخدمة والبيانات الثابتة الأخرى ما يصل إلى 40٪ من الشريحة القابلة للتنفيذ . يتم تأكيد وجود شيء مشابه لأجزاء من التعليمات البرمجية في هذه البيانات من خلال محاولات تفكيك هذه الملفات الثنائية مع كمية كبيرة من البيانات في الجزء القابل للتنفيذ دون جداول الأقسام والرموز. يمكن اعتبار كل تسلسل من وحدات البايت في هذا الجزء القابل للتنفيذ مفردًا مفيدًا للجزء المهاجمة من رمز الجهاز والقفزة - يكون هذا التسلسل من وحدات البايت مع جزء على الأقل من سطر رسالة تصحيح الأخطاء من البرنامج ، أو جزء من اسم الوظيفة في جدول الرموز أو الرقم الثابت لخوارزمية التشفير ...
رؤوس PE القابلة للتنفيذتشبه الرؤوس والجداول القابلة للتنفيذ في بداية الجزء الأول من صورة ELF وضع Windows قبل حوالي 15 عامًا. كان هناك عدد من الفيروسات التي تصيب الملفات ، وكتابة التعليمات البرمجية الخاصة بهم في رأس PE ، والتي كانت قابلة للتنفيذ هناك أيضًا. تمكنت من حفر مثل هذه العينة في الأرشيف:

كما ترون ، يتم ضغط جسم الفيروس مباشرة بعد جدول القسم في منطقة رؤوس PE. في عرض ملف على الذاكرة الافتراضية ، يوجد عادة حوالي 3 كيلو بايت من المساحة الحرة هنا. بعد وجود جسم الفيروس ، توجد مساحة فارغة ثم يبدأ القسم الأول برمز البرنامج.
ومع ذلك ، بالنسبة لنظام Linux ، كانت هناك أعمال أكثر إثارة للاهتمام في مشهد VX: الانتقام .
الحل
- المشكلة الموضحة أعلاه معروفة منذ وقت طويل .
- تم إصلاح 12 كانون الثاني (يناير) 2018 : المفتاح `ld -z - رمز منفصل: تتم إضافة رأس مقطع" إنشاء رمز منفصل "PT_LOAD" في الكائن ، وهذا يحدد مقطع ذاكرة يجب أن يحتوي على إرشادات فقط ويجب أن يكون في صفحات منفصلة تمامًا عن أي بيانات أخرى. لا تنشئ شريحة "PT_LOAD" منفصلة للرمز في حالة استخدام شفرة noseparate. "). تم إصدار الميزة في الإصدار 2.30 .
- علاوة على ذلك ، تم تضمين هذه الميزة بشكل افتراضي في الإصدار التالي 2.31 .
- موجودة في حزم
binutils
جديدة ، على سبيل المثال ، في مستودعات Ubuntu 18.10. تم بالفعل تجميع العديد من الحزم مع هذه الميزة الجديدة ، والتي صادفها باحث ElfMaster ووثقها
نتيجة للتغييرات في خوارزمية التخطيط ، يتم الحصول على صورة ELF جديدة:
$ readelf -l ls Elf file type is DYN (Shared object file) Entry point 0x41aa There are 11 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00000034 0x00000034 0x00160 0x00160 R 0x4 INTERP 0x000194 0x00000194 0x00000194 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x00000000 0x00000000 0x01e6c 0x01e6c R 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x14bd8 0x14bd8 RE 0x1000 LOAD 0x017000 0x00017000 0x00017000 0x0bf80 0x0bf80 R 0x1000 LOAD 0x0237f8 0x000247f8 0x000247f8 0x0096c 0x01afc RW 0x1000 DYNAMIC 0x023cec 0x00024cec 0x00024cec 0x00100 0x00100 RW 0x4 NOTE 0x0001a8 0x000001a8 0x000001a8 0x00044 0x00044 R 0x4 GNU_EH_FRAME 0x01c3f8 0x0001c3f8 0x0001c3f8 0x0092c 0x0092c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x0237f8 0x000247f8 0x000247f8 0x00808 0x00808 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .data.rel.ro .dynamic .got
أصبحت الحدود بين الرمز والبيانات الآن أكثر دقة. يحتوي الجزء القابل للتنفيذ فقط على مقاطع التعليمات البرمجية فقط: .init و .plt و .plt.got و .text و .fini.
بالضبط ما تم تغييره داخل دينار؟كما تعلم ، يتم وصف بنية ملف ELF الإخراج بواسطة البرنامج النصي رابط . يمكنك رؤية النص الافتراضي مثل هذا:
$ ld --verbose GNU ld (GNU Binutils for Ubuntu) 2.26.1 * * * using internal linker script: ================================================== /* Script for -z combreloc: combine and sort reloc sections */ /* Copyright (C) 2014-2015 Free Software Foundation, Inc. * * *
توجد العديد من البرامج النصية الأخرى ldscripts
الأساسية المختلفة ومجموعات الخيارات في دليل ldscripts
. تم إنشاء برامج نصية جديدة للخيار separate-code
.
$ diff elf_x86_64.x elf_x86_64.xe 1c1 < /* Default linker script, for normal executables */ --- > /* Script for -z separate-code: generate normal executables with separate code segment */ 46a47 > . = ALIGN(CONSTANT (MAXPAGESIZE)); 70a72,75 > . = ALIGN(CONSTANT (MAXPAGESIZE)); > /* Adjust the address for the rodata segment. We want to adjust up to > the same address within the page on the next page up. */ > . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
هنا يمكنك أن ترى أنه تمت إضافة توجيه لإعلان شريحة جديدة مع أقسام للقراءة فقط تتبع مقطع التعليمات البرمجية.
ومع ذلك ، بالإضافة إلى البرامج النصية ، تم إجراء تغييرات على مصادر الرابط. وهي في الوظيفة _bfd_elf_map_sections_to_segments
- راجع الالتزام . الآن ، عند تحديد مقاطع للأقسام ، ستتم إضافة شريحة جديدة عندما يختلف القسم حسب علامة SEC_CODE
عن القسم السابق.
الخاتمة
كما في السابق ، نوصي بأن لا ينسى المطورون استخدام إشارات الأمان المضمّنة في برنامج التحويل البرمجي والرابط عند تطوير البرامج. مثل هذا التغيير البسيط يمكن أن يعقد حياة المهاجم بشكل كبير ، ويجعل حياتك أكثر هدوءًا.