الكشف عن الذاكرة للنواة في نظام التشغيل الحديث

يوجد تحت القاطع ترجمة الجزء الافتتاحي من المستند " الكشف عن ذاكرة Kernel Disclosure مع x86 Emulation and Taint Tracking ( Article Project Zero )" بواسطة Mateusz Jurczyk .


في الجزء المترجم من الوثيقة:


  • تفاصيل لغة البرمجة C (كجزء من مشكلة توسيع الذاكرة)
  • تفاصيل تشغيل نواة Windows و Linux (كجزء من مشكلة توسيع الذاكرة)
  • أهمية الكشف عن ذاكرة kernel وتأثيره على أمان نظام التشغيل
  • الأساليب والتقنيات الموجودة لاكتشاف ومقاومة الكشف عن ذاكرة النواة

على الرغم من أن المستند يركز على آليات الاتصال بين kernel المتميز لنظام التشغيل وتطبيقات المستخدم ، إلا أنه يمكن تعميم جوهر المشكلة لأي نقل للبيانات بين مجالات الأمان المختلفة: برنامج hypervisor هو جهاز ضيف ، وخدمة نظام مميز (daemon) هو تطبيق GUI ، عميل الشبكة هو خادم ، إلخ. .


KDPV


مقدمة


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


أنظمة التشغيل الحديثة التي تعمل على منصات x86 / x86-64 متعددة الخيوط وتستخدم نموذج خادم عميل يتم فيه تنفيذ تطبيقات وضع المستخدم (العملاء) بشكل مستقل واستدعاء نواة نظام التشغيل (الخادم) بقصد العمل مع مورد يديره النظام. تُسمى الآلية المستخدمة من قبل رمز وضع المستخدم ( الحلقة 3 ) لاستدعاء مجموعة محددة مسبقًا من وظائف kernel (الحلقة 0) مكالمات النظام أو (جلسات موجزة). يظهر استدعاء نظام نموذجي في الشكل 1:
الشكل 1: استدعاء النظام
الشكل 1: دورة حياة استدعاء النظام.


من المهم جدًا تجنب تسريب محتويات ذاكرة kernel عن غير قصد عند التفاعل مع برامج وضع المستخدم. هناك خطر كبير من الكشف عن بيانات النواة الحساسة. يمكن إرسال البيانات بشكل ضمني في معلمات الإخراج لمكالمات النظام الآمنة (من وجهات نظر أخرى).


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


خصائص لغة البرمجة C


في هذا القسم ، نلقي نظرة على العديد من جوانب اللغة C الأكثر أهمية لمشكلة توسيع الذاكرة.


حالة غير محددة من المتغيرات غير المهيأة


تبقى المتغيرات الفردية من الأنواع البسيطة (مثل char أو int) ، وكذلك أعضاء هياكل البيانات (المصفوفات ، والبنى ، والاتحادات) في حالة غير محددة حتى التهيئة الأولى (بغض النظر عما إذا كانت موضوعة على المكدس أو في كومة الذاكرة المؤقتة). اقتباسات ذات صلة من مواصفات C11 (ISO / IEC 9899: مسودة اللجنة 201x N1570 ، أبريل 2011):


6.7.9 التهيئة
...
10 إذا لم تتم تهيئة كائن له مدة تخزين تلقائية بشكل صريح ، فإن قيمته غير محددة .

7.22.3.4 وظيفة malloc
...
2 تقوم دالة malloc بتخصيص مساحة لكائن محدد حجمه حسب الحجم وقيمته غير محددة .

7.22.3.5 وظيفة realloc
...
2 تقوم دالة realloc بإلغاء تخصيص الكائن القديم المشار إليه بواسطة ptr وإرجاع المؤشر إلى كائن جديد بحجم محدد حسب الحجم. يجب أن تكون محتويات الشيء الجديد مماثلة لتلك الموجودة في الكائن القديم قبل التخصيص حتى الحجم الأصغر والقديم. أي بايت في الكائن الجديد يتجاوز حجم الكائن القديم لها قيم غير محددة .

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


على حد علمنا ، لا يوجد أي من ثلاثة مترجمين C الأكثر شيوعًا لنظامي التشغيل Windows و Linux (مترجم Microsoft C / C ++ ، gcc ، LLVM) ينشئ رمزًا يهيئ مسبقًا المتغيرات غير المبرمجة للمبرمج في المكدس في وضع البناء (أو ما يعادله). هناك خيارات المترجم لوضع علامات على إطارات المكدس مع وحدات بايت - علامات خاصة (/ RTCs في Microsoft Visual Studio ، على سبيل المثال) ولكن لا يتم استخدامها في إصدارات الإصدار لأسباب تتعلق بالأداء. ونتيجة لذلك ، ترث المتغيرات غير المهيأة على المكدس القيم القديمة لمناطق الذاكرة المقابلة.


ضع في اعتبارك مثالاً على التنفيذ القياسي لمكالمة نظام Windows الوهمية التي تضاعف عددًا صحيحًا للإدخال بمقدار اثنين وتعرض نتيجة الضرب (القائمة 1). من الواضح أنه في الحالة الخاصة (InputValue == 0) ، يظل OutputValue غير مهيأ ويتم نسخه مرة أخرى إلى العميل. يسمح لك هذا الخطأ بفتح أربعة بايت من ذاكرة مكدس kernel لكل مكالمة.


NTSTATUS NTAPI NtMultiplyByTwo(DWORD InputValue, LPDWORD OutputPointer) { DWORD OutputValue; if (InputValue != 0) { OutputValue = InputValue * 2; } *OutputPointer = OutputValue; return STATUS_SUCCESS; } 

سرد الرمز 1: توسيع الذاكرة من خلال متغير محلي غير مهيأ.


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


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


 typedef struct _SYSCALL_OUTPUT { DWORD Sum; DWORD Product; DWORD Reserved; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtArithOperations( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; OutputStruct.Product = InputValue * 2; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; } 

قائمة 2: توسيع الذاكرة من خلال مجال هيكل محجوز.


محاذاة الهياكل والبايتات الحشو


تعتبر تهيئة جميع حقول بنية الإخراج بداية جيدة لتجنب توسيع الذاكرة. ولكن هذا لا يكفي لضمان عدم وجود وحدات بايت غير محددة في التمثيل ذي المستوى المنخفض. دعنا نعود إلى مواصفات C11:


6.5.3.4 حجم مشغلي و Alignof
...
4 [...] عند تطبيقه على مُعامل له بنية أو نوع اتحاد ، تكون النتيجة هي إجمالي عدد وحدات البايت في هذا الكائن ، بما في ذلك المساحة المتروكة الداخلية واللاحقة .

6.2.8 محاذاة الأشياء
1 أنواع الكائنات الكاملة لها متطلبات محاذاة تضع قيودًا على العناوين التي يمكن تخصيص كائنات من هذا النوع لها . المحاذاة هي قيمة عددية متكاملة محددة من قبل التنفيذ تمثل عدد البايتات بين العناوين المتتالية التي يمكن تخصيص كائن معين عندها. [...]

6/7/2/1 محددات الهيكل والنقابات
...
17 قد يكون هناك حشو غير مسمى في نهاية هيكل أو اتحاد .

أي أن جامعي لغة C لبنيات x86 (-64) يستخدمون المحاذاة الطبيعية لحقول الهياكل (التي لها نوع بدائي): كل حقل من هذا القبيل يتم محاذاته بواسطة N بايت ، حيث N هو حجم الحقل. بالإضافة إلى ذلك ، يتم أيضًا محاذاة الهياكل والصلات بالكامل عندما يتم الإعلان عنها في صفيف ، ويتم استيفاء متطلبات محاذاة الحقول المتداخلة. لضمان المحاذاة ، يتم إدراج وحدات بايت الحشو الضمنية في الهياكل عند الضرورة. على الرغم من أنه لا يمكن الوصول إليها مباشرة في شفرة المصدر ، إلا أن هذه البايت ترث أيضًا القيم القديمة من مناطق الذاكرة ويمكنها نقل المعلومات إلى وضع المستخدم.


في المثال الموجود في القائمة 3 ، يتم إرجاع بنية SYSCALL_OUTPUT إلى رمز الاستدعاء. يحتوي على 4 و 8 حقول بايت ، مفصولة بـ 4 بايت حشو ، ضرورية لعنوان حقل LargeSum ليصبح مضاعفًا لـ 8. على الرغم من حقيقة أن كلا الحقلين تمت تهيئتهما بشكل صحيح ، إلا أنه لم يتم تعيين بايتات الحشو بشكل صريح ، مما يؤدي مرة أخرى إلى توسيع ذاكرة مكدس النواة. يوضح الشكل 2 الموقع المحدد للهيكل في الذاكرة.


 typedef struct _SYSCALL_OUTPUT { DWORD Sum; QWORD LargeSum; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtSmallSum( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; OutputStruct.LargeSum = 0; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; } 

قائمة 3: توسيع الذاكرة عن طريق محاذاة الهيكل.


الشكل 2: محاذاة الهيكل
الشكل 2: تمثيل البنية في الذاكرة مع وضع المحاذاة في الاعتبار.


التسريبات من خلال المحاذاة شائعة نسبيًا ، نظرًا لأن العديد من معلمات الإخراج لمكالمات النظام يتم تمثيلها بواسطة الهياكل. تكون المشكلة حادة بشكل خاص لمنصات 64 بت ، حيث يزيد حجم المؤشرات ، و size_t ، والأنواع المماثلة من 4 إلى 8 بايت ، مما يؤدي إلى ظهور الحشو الضروري لمحاذاة حقول هذه الهياكل.


نظرًا لأنه لا يمكن معالجة وحدات بايت الحشو في التعليمات البرمجية المصدر ، فمن الضروري استخدام memset أو وظيفة مشابهة لإعادة تعيين منطقة الذاكرة بالكامل للهيكل قبل تهيئة أي من حقولها ونسخها إلى وضع المستخدم ، على سبيل المثال:


  memset(&OutputStruct, 0, sizeof(OutputStruct)); 

ومع ذلك ، فإن Seacord RC في كتابها "معيار CERT C Coding Standard ، الإصدار الثاني: 98 قواعد لتطوير أنظمة آمنة وموثوقة وآمنة. Addison-Wesley Professional" 2014 ينص على أن هذا ليس حلاً مثاليًا لأن حشو البايتات ) قد لا تزال ترسيتها بعد استدعاء memset ، على سبيل المثال ، كأثر جانبي للعمليات مع الحقول المجاورة. يمكن تبرير القلق من خلال البيان التالي في المواصفات C:


6.2.6 تمثيلات الأنواع
6.2.6.1 عام
...
6 عندما يتم تخزين القيمة في كائن من البنية أو نوع الاتحاد ، بما في ذلك في كائن عضو ، فإن وحدات بايت تمثيل الكائن التي تتوافق مع أي بايتات حشو تأخذ قيمًا غير محددة . [...]

ومع ذلك ، من الناحية العملية ، لم يقم أي من المترجمين C الذين قمنا باختبارهم بقراءته أو كتابته خارج مناطق الذاكرة للحقول المعلنة بشكل صريح. يبدو أن هذا الرأي يشاركه مطورو أنظمة التشغيل التي تستخدم memset.


النقابات والمجالات بأحجام مختلفة


الصلات هي بنية لغة C معقدة أخرى في سياق الاتصال برمز اتصال أقل امتيازًا. ضع في اعتبارك كيف تصف مواصفات C11 تمثيل النقابات في الذاكرة:


6.2.5 الأنواع
...
20 يمكن إنشاء أي عدد من الأنواع المشتقة من أنواع الكائنات والوظائف ، على النحو التالي: [...] يصف نوع الاتحاد مجموعة غير متداخلة من كائنات الأعضاء ، لكل منها اسم محدد اختياريًا وربما نوعًا مميزًا.

6/7/2/1 محددات الهيكل والنقابات
...
6 كما نوقش في 6.2.5 ، الهيكل هو نوع يتكون من تسلسل من الأعضاء ، يتم تخصيص تخزينه في تسلسل مرتب ، والاتحاد هو نوع يتكون من تسلسل من الأعضاء الذين يتداخل تخزينهم .
...
16 حجم الاتحاد يكفي لاحتواء أكبر عدد من أعضائه . يمكن تخزين قيمة عضو واحد على الأكثر في كائن نقابي في أي وقت.

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


 typedef union _SYSCALL_OUTPUT { DWORD Sum; QWORD LargeSum; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtSmallSum( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; } 

قائمة الرمز 4: توسيع الذاكرة عن طريق التهيئة الجزئية للنقابة.


الشكل 3: محاذاة صلة
الشكل 3: تمثيل الاتحاد في الذاكرة مع المحاذاة.


اتضح أن الحجم الإجمالي لاتحاد SYSCALL_OUTPUT هو 8 بايت (بسبب حجم حقل LargeSum الأكبر). ومع ذلك ، تقوم الدالة بتعيين قيمة الحقل الأصغر فقط ، تاركة 4 بايتات زائدة غير مهيأة ، مما يؤدي لاحقًا إلى حدوث تسرب لتطبيق العميل الخاص بهم.


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


حجم غير آمن


كما هو موضح في القسمين السابقين ، فإن استخدام عامل التشغيل sizeof يمكن أن يساهم بشكل مباشر أو غير مباشر في الكشف عن ذاكرة kernel ، مما يتسبب في نسخ بيانات أكثر من التهيئة السابقة.


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


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


تفاصيل نظام التشغيل


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


إعادة استخدام الذاكرة الديناميكية


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


في الفقرات التالية ، نقدم نظرة عامة موجزة عن الموزعين المستخدمين في نواة Windows و Linux ، وخصائصهم الأكثر أهمية.


نوافذ
الوظيفة الرئيسية لمدير تجمع Windows kernel هي ExAllocatePoolWithTag ، والتي يمكن استدعاؤها مباشرة أو من خلال أحد الأصداف المتاحة: ExAllocatePool {∅، Ex، WithQuotaTag، WithTagPriority}. لا تقوم أي من هذه الوظائف بمسح محتويات الذاكرة التي تم إرجاعها ، إما بشكل افتراضي أو من خلال أي إشارات إدخال. على العكس من ذلك ، لديهم جميعًا التحذير التالي في وثائق MSDN الخاصة بهم:


ملاحظة الذاكرة التي تخصصها الدالة غير مهيأة. يجب أن يكون برنامج تشغيل وضع kernel أول صفر من هذه الذاكرة إذا كان سيجعلها مرئية لبرنامج وضع المستخدم (لتجنب تسريب محتويات ذات امتيازات محتملة).

يمكن أن يحدد رمز الاتصال أحد الأنواع الستة الرئيسية للتجمعات: NonPagedPool و NonPagedPoolNx و NonPagedPoolSession و NonPagedPoolSessionNx و PagedPool و PagedPoolSession. لكل منها منطقة منفصلة في مساحة العنوان الظاهري ، وبالتالي يمكن إعادة استخدام مناطق الذاكرة المخصصة فقط داخل نفس نوع التجمع. يعد تكرار إعادة استخدام أجزاء الذاكرة مرتفعًا جدًا ، وعادةً ما يتم إرجاع المناطق التي تحتوي على صفر فقط إذا لم يتم العثور على سجل مناسب في قوائم lookaside ، أو كان الطلب كبيرًا جدًا بحيث تتطلب صفحات جديدة من الذاكرة. وبعبارة أخرى ، لا توجد حاليًا أي عوامل تمنع الكشف عن ذاكرة التجمع في Windows ، ويمكن استخدام كل هذا الخطأ تقريبًا لتسريب البيانات الحساسة من أجزاء مختلفة من النواة.


لينكس
يحتوي نواة Linux على ثلاث واجهات رئيسية لتخصيص الذاكرة ديناميكيًا:


  • kmalloc - وظيفة شائعة تستخدم لتخصيص كتل الذاكرة ذات الحجم التعسفي (المستمر في كل من مساحة العنوان الظاهرية والمادية) ، تستخدم تخصيص ذاكرة البلاطة .
  • kmem_cache_create و kmem_cache_alloc - آلية متخصصة لتخصيص كائنات ذات حجم ثابت (الهياكل ، على سبيل المثال) ، تستخدم أيضًا تخصيص ذاكرة البلاطة .
  • vmalloc هي وظيفة تخصيص نادراً ما تُستخدم تُرجع مناطق لا تكون استمراريتها مضمونة على مستوى الذاكرة الفعلية.

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


  • تحتوي وظيفة kmalloc على نظير من kzalloc ، مما يضمن مسح الذاكرة التي تم إرجاعها.
  • يمكن تمرير علامة __GFP_ZERO الاختيارية إلى kmalloc و kmem_cache_alloc وبعض الوظائف الأخرى لتحقيق نفس النتيجة.
  • يقبل kmem_cache_create مؤشرًا إلى وظيفة مُنشئ اختياري يتم استدعاؤها لتهيئة كل كائن مسبقًا قبل إعادته إلى رمز الاستدعاء. يمكن تنفيذ المُنشئ كغلاف حول memset لإخراج منطقة ذاكرة معينة.

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


صفائف حجم ثابت


يمكن الوصول إلى عدد من موارد نظام التشغيل من خلال أسماء الاختبار الخاصة بهم. مجموعة متنوعة من الموارد المسماة في Windows كبيرة جدًا ، على سبيل المثال: الملفات والأدلة ومفاتيح وقيم مفاتيح التسجيل والنوافذ والخطوط والمزيد. بالنسبة لبعضها ، يكون طول الاسم محدودًا ويتم التعبير عنه بواسطة ثابت ، مثل MAX_PATH (260) أو LF_FACESIZE (32). في مثل هذه الحالات ، غالبًا ما يبسط مطورو kernel الشفرة من خلال الإعلان عن الحد الأقصى لحجم المخازن المؤقتة ونسخها ككل (على سبيل المثال ، باستخدام sizeof الكلمة الأساسية) بدلاً من العمل فقط مع الجزء المقابل من السطر. هذا مفيد بشكل خاص إذا كانت السلاسل أعضاء في هياكل أكبر. يمكن نقل هذه الأشياء بحرية في الذاكرة دون القلق بشأن إدارة المؤشرات إلى الذاكرة الديناميكية.


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


 NTSTATUS NTAPI NtGetSystemPath(PCHAR OutputPath) { CHAR SystemPath[MAX_PATH]; NTSTATUS Status; Status = RtlGetSystemPath(SystemPath, sizeof(SystemPath)); if (NT_SUCCESS(Status)) { RtlCopyMemory(OutputPath, SystemPath, sizeof(SystemPath)); } return Status; } 

سرد 5: توسيع الذاكرة عن طريق تهيئة المخزن المؤقت للسلسلة جزئيا.


يتم عرض منطقة الذاكرة التي تم نسخها إلى مساحة المستخدم في هذا المثال في الشكل 4.


الشكل 4: ذاكرة التخزين المؤقت للسلسلة التي تمت تهيئتها جزئيًا
الشكل 4: ذاكرة المخزن المؤقت للخط الذي تمت تهيئته جزئيًا.


يجب أن يُرجع التنفيذ الآمن المسار المطلوب فقط ، وليس المخزن المؤقت بأكمله المستخدم للتخزين. يوضح هذا المثال مرة أخرى كيف يمكن أن يكون تقدير حجم البيانات باستخدام عامل التشغيل sizeof (المستخدم كمعلمة لـ RtlCopyMemory) غير صحيح تمامًا فيما يتعلق بالمقدار الفعلي للبيانات التي يجب أن تمررها النواة إلى منطقة المستخدم.


حجم إخراج استدعاء النظام التعسفي


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


 NTSTATUS NTAPI NtMagicValues(LPDWORD OutputPointer, DWORD OutputLength) { if (OutputLength < 3 * sizeof(DWORD)) { return STATUS_BUFFER_TOO_SMALL; } LPDWORD KernelBuffer = Allocate(OutputLength); KernelBuffer[0] = 0xdeadbeef; KernelBuffer[1] = 0xbadc0ffe; KernelBuffer[2] = 0xcafed00d; RtlCopyMemory(OutputPointer, KernelBuffer, OutputLength); Free(KernelBuffer); return STATUS_SUCCESS; } 

الإدراج 6: توسيع الذاكرة من خلال المخزن المؤقت للإخراج من حجم عشوائي.


الغرض من استدعاء النظام هو تزويد رمز الاتصال بثلاث قيم خاصة 32 بت ، تحتل ما مجموعه 12 بايت. على الرغم من صحة التحقق من حجم المخزن المؤقت الصحيح في بداية الدالة ، يجب أن ينتهي استخدام وسيطة OutputLength عند هذا الحد. مع العلم أن المخزن المؤقت للإخراج كبير بما يكفي لحفظ النتيجة ، يمكن للنواة تخصيص 12 بايت من الذاكرة ، وملئها ، ونسخ المحتويات مرة أخرى إلى المخزن المؤقت لوضع المستخدم المقدم. بدلاً من ذلك ، يقوم استدعاء النظام بتخصيص كتلة تجمع (علاوة على ذلك ، بطول يتحكم فيه المستخدم) ونسخ الذاكرة المخصصة بالكامل إلى مساحة المستخدم. اتضح أن جميع البايتات ، باستثناء الـ 12 الأولى ، غير مهيأة ومفتوحة خطأ للمستخدم ، كما هو موضح في الشكل 5.


الشكل 5: الذاكرة العازلة التعسفية
الشكل 5: ذاكرة عازلة ذات حجم تعسفي.


المخطط الذي تمت مناقشته في هذا القسم شائع بشكل خاص لنظام Windows. يمكن أن يوفر خطأ مماثل للمهاجم بدائية مفيدة للغاية لتوسيع الذاكرة:


  • التحسين المستخدم غالبًا في مكالمات نظام Windows هو استخدام المخازن المؤقتة القائمة على المكدس للأحجام الصغيرة وتخصيص الذاكرة من التجمعات للأحجام الأكبر. إلى جانب التسرب المماثل ، يمكن أن يسهل هذا توسيع كومة kernel والمسبح مع ثغرة واحدة.
  • القدرة على التحكم في حجم البيانات التي تم الكشف عنها من التجمع تمنح المهاجم القدرة على التحكم في نوع المعلومات السرية. يصبح هذا ممكنًا ، نظرًا لآليات تخصيص الذاكرة الحديثة ، والتي تميل إلى تخزين مناطق الذاكرة مؤقتًا لطلبات تخصيص الذاكرة اللاحقة بنفس حجم المخزن المؤقت. وهذا يجعل من الممكن قراءة بيانات محددة تم إصدارها مؤخرًا بواسطة مكون نواة آخر (في الحالة العامة - غير ذي صلة).

, . , , .


,


, . , Windows .



, , . , : AddressSanitizer , PageHeap Special Pool . , , - . , . , , , , , . , ( ).


, , , . , .


, API
API, Windows (Win32/User32 API). API , , , . , , , , . .



, . , . , , , . , , .


, , . , KASLR (Kernel Address Space Layout Randomization ), . : Windows, Hacking Team 2015 ( Juan Vazquez. Revisiting an Info Leak ) (derandomize) win32k.sys, . , Matt Tait' Google Project Zero ( Kernel-mode ASLR leak via uninitialized memory returned to usermode by NtGdiGetTextMetrics ) MS15-080 (CVE-2015-2433).



(/) , , (control flow), : , , , , StackGuard Linux /GS Windows . , . , , .


(/)
(/) , , , : , , , . , , . . , ( , ) , , .



KDPV # 2


Microsoft Windows



2015 Windows. 2015 Matt Tait win32k!NtGdiGetTextMetrics. Windows Hacking Team. , , , 0-day Windows.


2015, WanderingGlitch (HP Zero Day Initiative) ( Acknowledgments – 2015 ). Ruxcon 2016 ( ) "Leaking Windows Kernel Pointers" .


, 2017 fanxiaocao pjf IceSword Lab (Qihoo 360) "Automatically Discovering Windows Kernel Information Leak Vulnerabilities" , , 14 2017 (8 ). Bochspwn Reloaded, , . VMware (Bochs) . , Bochspwn Reloaded, .


, , 2010-2011 , win32k: "Challenge: On 32bit Windows7, explain where the upper 16bits of eax come from after a call to NtUserRegisterClassExWOW()" "Subtle information disclosure in WIN32K.SYS syscall return values" . Windows 8, 2015 Matt Tait , : Google Project Zero Bug Tracker .



( ), , 2017 - Windows -, : Joseph Bialek — "Anyone notice my change to the Windows IO Manager to generically kill a class of info disclosure? BufferedIO output buffer is always zero'd" . , IOCTL- .


, Visual Studio 15.5 POD- , "= {0}", . , padding- () .


Linux


Windows, Linux , 2010 . , ( ) ( ) . , Windows Linux , — , .



, Linux . "Linux kernel vulnerabilities: State-of-the-art defenses and open problems" 2010 2011 28 . 2017- "Securing software systems by preventing information leaks" Lu K. 59 , 2013- 2016-. . : Rosenberg Oberheide 25 , Linux 2009-2010 , . Linux c grsecurity / PaX-hardened . Vasiliy Kulikov 25 2010-2011 , Coccinelle . , Mathias Krause 21 2013 50 .


, , Linux. — -Wuninitialized ( gcc, LLVM), . kmemcheck , Valgrind' . , . , KernelAddressSANitizer KernelMemorySANitizer . KMSAN syzkaller ( ) 19 , .


Linux. 2014 — 2016 Peir´o Coccinelle , Linux 3.12: "Detecting stack based kernel information leaks" International Joint Conference SOCO14-CISIS14-ICEUTE14, pages 321–331 (Springer, 2014) "An analysis on the impact and detection of kernel stack infoleaks" Logic Journal of the IGPL. , . 2016- Lu UniSan — , , : , . , 20% (350 1800), 19 Linux Android.


— (multi-variant program execution), , . , . , KASLR, -, . , 2006 DieHard: probabilistic memory safety for unsafe languages, 2017 — BUDDY: Securing software systems by preventing information leaks. John North "Identifying Memory Address Disclosures" 2015- . , SafeInit (Comprehensive and Practical Mitigation of Uninitialized Read Vulnerabilities) , , . , , , Linux.



, . , : , . , , - , . .


CONFIG_PAGE_POISONING CONFIG_DEBUG_SLAB, -. -, . , , , Linux.


grsecurity / PaX . , PAX_MEMORY_SANITIZE , slab , ( — ). , PAX_MEMORY_STRUCTLEAK , ( ), . padding- (), 100% . , — PAX_MEMORY_STACKLEAK, . , , . (Kernel Self Protection Project) STACKLEAK .


Linux:


Secure deallocation, Chow , 2005

Chow, Jim and Pfaff, Ben and Garfinkel, Tal and Rosenblum, Mendel. Shredding Your Garbage: Reducing Data Lifetime Through Secure Deallocation. In USENIX Security Symposium, pages 22–22, 2005.


, , ( ) . Linux .


Split Kernel, Kurmus Zippel, 2014

Kurmus, Anil and Zippel, Robby. A tale of two kernels: Towards ending kernel hardening wars with split kernel. In Proceedings of the 2014 ACM SIGSAC Conference on Computer and Communications Security, pages 1366–1377. ACM, 2014.


, .


SafeInit, Milburn , 2017

Milburn, Alyssa and Bos, Herbert and Giuffrida, Cristiano. SafeInit: Comprehensive and Practical Mitigation of Uninitialized Read Vulnerabilities. In Proceedings of the 2017 Annual Network and Distributed System Security Symposium (NDSS)(San Diego, CA), 2017.


, , .


UniSan, Lu , 2016

Lu, Kangjie and Song, Chengyu and Kim, Taesoo and Lee, Wenke. UniSan: Proactive kernel memory initialization to eliminate data leakages. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security, pages 920–932. ACM, 2016.


SafeInit , , , , .


, Linux .


( )


, , ( ). : (), , , , ( - ) . , . , , .


, :


  • Bochspwn Reloaded – detection with software x86 emulation
  • Windows bug reproduction techniques
  • Alternative detection methods
  • Other data sinks
  • Future work
  • Other system instrumentation schemes

, :) , .

Source: https://habr.com/ru/post/ar415685/


All Articles