قريب جداً من صفر (يوم): استغلال خدمة تدفق Microsoft Kernel | IBM

لقطة لرجل ذو لحية يعمل في وقت متأخر في المكتب لإنهاء الموعد المحدد، وزميلان في العمل يجلسان خلفه

في الشهر الماضي، قامت Microsoft بإصلاح ثغرة في خادم Microsoft Kernel Streaming Server، وهو مكون نواة لنظام Windows يستخدم في المحاكاة الافتراضية ومشاركة أجهزة الكاميرات. تسمح الثغرة الأمنية CVE-2023-36802 للمهاجمين المحليين بتصعيد الامتيازات إلى SYSTEM.

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

سطح الهجوم

The Microsoft Kernel Streaming Server (mskssrv.sys) هو عنصر من خدمة Windows Multimedia Framework، من فئة Frame Server. تقوم الخدمة بتحويل جهاز الكاميرا إلى جهاز افتراضي وتسمح بمشاركة الجهاز بين تطبيقات متعددة.

استكشفت سطح الهجوم هذا بعد ملاحظة CVE-2023-29360، الذي كان مدرجاً في البداية كثغرة في برنامج تشغيل TPM. الخطأ موجود بالفعل في Microsoft Kernel Streaming Server. على الرغم من أنني لم أكن في ذلك الوقت على دراية بخادم MS KS Server، إلا أن اسم برنامج التشغيل هذا كان كافيًا لجذب اهتمامي. على الرغم من عدم معرفة أي شيء عن الغرض أو الوظيفة، اعتقدت أن خادم البث في النواة يمكن أن يكون مكانًا مثمرًا للبحث عن الثغرات الأمنية. دخلت بشكل أعمى، سعيت للإجابة على الأسئلة التالية:

  • بأي قدرة يمكن لتطبيق غير مميز التفاعل مع وحدة النواة هذه؟
  • ما نوع البيانات من التطبيق التي تعالجها الوحدة مباشرةً؟

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

أحدث الأخبار التقنية، مدعومة برؤى خبراء

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

شكرًا لك! أنت مشترك.

سيصلك محتوى الاشتراك باللغة الإنجليزية. ستجد رابط إلغاء الاشتراك في كل رسالة إخبارية. يمكنك إدارة اشتراكاتك أو إلغاء اشتراكك من هنا. لمزيد من المعلومات، راجع بيان خصوصية IBM.

التحليل الأولي

بدء التنفيذ داخل MS KS Server

أولاً، يجب أن نكون قادرين على الوصول إلى برنامج التشغيل من تطبيق مساحة المستخدم. يمكن الوصول إلى الدالة الضعيفة من روتين DispatchDeviceControl الخاص ببرنامج التشغيل؛ مما يعني أنه يمكن الوصول إليها عن طريق إصدار IOCTL إلى برنامج التشغيل. للقيام بذلك، يجب الحصول على اسم مستخدم لجهاز برنامج التشغيل عبر استدعاء إلى CreateFile باستخدام مسار الجهاز. عادةً ما يكون من السهل تحديد اسم/مسار الجهاز مباشرةً: ابحث عن استدعاء إلى IoCreateDevice في برنامج التشغيل وافحص المعلمة الثالثة التي تحتوي على اسم الجهاز.

دالة داخل MSkssrv.sys تستدعي IoCreateDevice بمؤشر Null لاسم الجهاز

دالة داخل MSkssrv.sys تستدعي IoCreateDevice بمؤشر Null لاسم الجهاز

في هذه الحالة، تكون معلمة اسم الجهاز هي Null. يشير اسم دالة الاستدعاء إلى أن mskssrv هو PnP برنامج تشغيل، والاتصال إلى IoAttachDeviceToDeviceStack يشير إلى أن كائن الجهاز الذي تم إنشاؤه هو جزء من مجموعة الأجهزة. وهذا يعني فعليا أن عدة برامج تشغيل يتم استدعاؤها عند إرسال طلب إدخال/إخراج إلى جهاز ما. بالنسبة لأجهزة PnP، يلزم وجود مسار واجهة الجهاز للوصول إلى الجهاز.

باستخدام مصحح نواة WinDbg يمكننا رؤية الأجهزة التي تنتمي إلى برنامج تشغيل mskssrv ومجموعة الأجهزة:

الإخراج من الأمرين! Drvobj و! devobj يعرض الأجهزة العلوية والسفلية

الإخراج من الأمرين! Drvobj و! devobj يعرض الأجهزة العلوية والسفلية

أعلاه نرى أن جهاز MSKSsrv متصل بكائن الجهاز السفلي الذي ينتمي إلى swenum.sys ويوجد جهاز علوي متصل ksthunk.sys.

من "إدارة الأجهزة" يمكننا العثور على معرف مثيل الجهاز الهدف:

تعرض إدارة الأجهزة معرف مثيل الجهاز والواجهة GUID

تعرض إدارة الأجهزة معرف مثيل الجهاز والواجهة GUID

لدينا الآن معلومات كافية للحصول على مسار واجهة الجهاز باستخدام مدير التكوين أو دالات SetupApi. باستخدام مسار واجهة الجهاز المسترد، يمكننا فتح اسم مستخدم للجهاز.

أخيرًا، نحن الآن قادرون على تشغيل تنفيذ التعليمات البرمجية داخل MSkssrv.sys. عند إنشاء الجهاز، يتم استدعاء دالة إنشاء إرسال PnP الخاصة ببرنامج التشغيل PnP. لتشغيل تنفيذ تعليمات برمجية إضافية، يمكننا إرسال IOCTLs للتحدث إلى الجهاز الذي سينفذ في دالة التحكم في جهاز الإرسال الخاصة ببرنامج التشغيل.

تصحيح الأخطاء في برنامج تشغيل شبح

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

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

التعليق في المنتدى

كما اتضح، يمكن إلغاء تحميل برامج تشغيل فلاتر PnP إذا لم يتم استخدامها لفترة من الوقت، وإعادة تحميلها عند الطلب عند الحاجة.

لقد حللت المشاكل التي كنت أواجهها عن طريق تعيين نقاط توقف بعد فتح اسم مستخدم الجهاز، ولكن قبل استدعاء DeviceIoControl، للتأكد من تحميل برنامج التشغيل مؤخرًا.

استطلاع سريع لوظائف برنامج التشغيل

برنامج التشغيل mskssrv هو برنامج تشغيل mskssrv ثنائي بحجم 72 كيلوبايت فقط ويدعم رموز التحكم في Device IO التي تستدعي الدوال التالية:

  • FSRendezvousServer::InitializeContext
  • FSRendezvousServer::InitializeStream
  • FSRendezvosServer:: RegistryContext
  • FSRendezvosServer:: RegistryStream
  • FSRendezvousServer::DrainTx
  • FSRendezvousServer::NotifyContext
  • FSRendezvosServer: PublicTx
  • FSRendezvousServer::PublishRx
  • FSRendezvosServer: ConsumeTx
  • FSRendezvosServer: ConsumeRx

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

نظرًا إلى أن برنامج التشغيل ليس كبيرًا جدًا ولا يوجد الكثير من IOCTLs، يمكنني إلقاء نظرة على كل وظيفة للحصول على فكرة عن الأجزاء الداخلية لبرنامج التشغيل. تعمل كل وظيفة IOCTL إما على كائن تسجيل سياق أو كائن تسجيل تدفق، والذي يتم تخصيصه وتهيئته عبر وظائف IOCTLs "التهيئة" المقابلة. يتم تخزين المؤشر إلى الكائن في Irp->CurrentStackLocation->FileObject->FsContext2. FileObject يشير إلى كائن ملف الجهاز الذي تم إنشاؤه لكل ملف مفتوح، وFsContext2 هو حقل مُخصص لتخزين بيانات تعريف كائن الملف.

الثغرة الأمنية

لاحظت هذا الخطأ أثناء محاولتي فهم كيفية التواصل مع برنامج التشغيل مباشرة، مع التخلي أولا عن تحليل عناصر وضع المستخدم، fsclient.dll و frameserver.dll. كدت أن أكون قد فاتني الخطأ، لأنني افترضت أن المطورين قاموا بإنشاء مثيل لفحص بسيط تم التغاضي عنه. لنلقِ نظرة على دالة نشر PublishRx IOCTL :

مقتطف تفكيك FSRendezvousServer::PublishRx

مقتطف تفكيك FSRendezvousServer::PublishRx

بعد استرجاع كائن التدفق من FsContext2، يتم استدعاء الدالة FSRendezvousServer::FindObject للتحقق من تطابق المؤشر مع كائن موجود في قائمتين مخزنتين في FSRendezvousServerالعالمي. في البداية، افترضت أن هذه الدالة سيكون لديها طريقة للتحقق من نوع الكائن المطلوب. ومع ذلك، تعيد الدالة TRUE إذا وجد المؤشر إما في قائمة كائنات السياق أو قائمة كائنات التدفق. لاحظ أنه لا يتم تمرير أي معلومات عن نوع الكائن المفترض أن يكون عليه إلى FindObject. هذا يعني أن كائن السياق يمكن تمريره ككائن تدفق. هذه نقطة ضعف ارتباك من نوع الكائن! يحدث في كل دالة IOCTL التي تعمل على كائنات التدفق. لإصلاح الثغرة، استبدلت Microsoft FSRendezvousServer::FindObject ب FSRendezvousServer::FindStreamObject، الذي يتحقق أولاً من أن الكائن كائن تدفق من خلال التحقق من حقل النوع.

الاستغلال

بدائي

نظرًا إلى أن كائنات تسجيل السياق أصغر من (0x78 بايت) كائنات تسجيل التدفق (0x1d8 بايت)، يمكن تنفيذ عمليات كائنات التدفق على ذاكرة خارج الحدود:

رسم توضيحي لثغرة أمنية تتعلق بالارتباك في نوع الكائن

رسم توضيحي لثغرة أمنية تتعلق بالارتباك في نوع الكائن

بخاخ المسبح

من أجل الاستفادة من الثغرة الأمنية البدائية، نحتاج إلى القدرة على التحكم في الذاكرة الخارجة عن الحدود التي يتم الوصول إليها. ويمكن القيام بذلك عن طريق تشغيل تخصيص العديد من الكائنات في نفس منطقة ذاكرة الكائن الضعيف. تسمى هذه التقنية بالرش بالركام أو حوض السباحة. يتم تخصيص الكائن الذي يكون عرضة للإثارة والثغرات الأمنية في تجمع كومة ذاكرة تخزين مؤقت منخفضة غير مقسمة إلى صفحات. يمكننا استخدام التقنية الكلاسيكية التي ابتكرها Alex Ionescu لرش المخازن المؤقتة التي تمنح التحكم الكامل في محتويات الذاكرة أسفل رأس DATA_QUEUE_ENTRY بحجم 0x30 بايت. وبالرش باستخدام هذه التقنية، يمكننا الحصول على تخطيط الذاكرة الموضح في الرسم البياني:

رسم توضيحي لرشاشات حوض السباحة غير المقسم إلى صفحات

باستخدام طريقة رشّ حوض السباحة المختارة، يُمكن التحكّم في الحقول الموجودة في إزاحات الكائناتضمن النطاقات 0xC0-0x10F و 0x150-0x19F. راجعتُ مجددًا دوال IOCTL لكائنات التدفق للبحث عن عناصر الاستغلال. بحثتُ عن الأماكن التي يُمكن فيها الوصول إلى حقول الكائنات القابلة للتحكم والتلاعب بها.

ثابت الكتابة

لقد وجدت ثابتًا جيدًا للكتابة حيث يكون بدائيًا (write-where primitive) في  PublishRx IOCTL.. يمكن استخدام هذا البدائي لكتابة قيمة ثابتة عند عنوان ذاكرة عشوائي. لنلقِ نظرة على مقتطفات من الوظيفة FSStreamReg::PublishRx:

مقتطف فك تجميع FSStreamReg::PublishRx

مقتطف فك تجميع FSStreamReg::PublishRx

يحتوي كائن الدفق على رأس قائمة في الإزاحة 0X188 الذي يصف قائمة من كائنات FSFrameMdl. في مقتطف فك التجميع أعلاه، يتم تكرار هذه القائمة وإذا كانت قيمة العلامة في كائن FSFrameMdl تتطابق مع العلامة في المخزن المؤقت للنظام الذي تم تمريره من التطبيق، يتم استدعاء الدالة FSFrameMdl::UnmapPages.

باستخدام بدائية الاستغلال المذكورة أعلاه، يمكن التحكم في قائمة FSFrameMdlist وبالتالي كائن FsFrameMdl الذي يشير إليه pFrameMdl بشكل كامل. دعونا الآن نلقي نظرة على UnmapPages:

فك تجميع FSFrameMdl:UnmapPages

فك تجميع FSFrameMdl:UnmapPages

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

على الرغم من أنني اخترت استخدام بدائية الكتابة الثابتة، إلا أن استغلال بدائي مفيد آخر يظهر أيضًا في هذه الدالة. يمكن التحكم في كل من الوسيطات BaseAddress وMemoryDescriptorList لاستدعاء MmUnmapLockedPages. يمكن استخدام هذا لإلغاء تعيين التعيين عند عنوان افتراضي عشوائي وبناء بدائي use-after-free.

مشكلة الشحن

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

مقتطف فك تجميع FSStreamReg::PublishRx

مقتطف فك تجميع FSStreamReg::PublishRx

في عملية فك التحويل البرمجي أعلاه، يُعدّ bPagesUnmaps متغيرًا منطقيًا يتم تعيينه في حالة استدعاء FSFrameMdl:UnmapPages. إذا كان الأمر كذلك، فسيتم استرداد الإزاحة 0X1a8 لكائن الدفق، وإذا لم يكن الأمر كذلك، يُستدعى KySetEvent عليه.

تتوافق هذه الإزاحة مع الذاكرة والنقاط خارج الحدود داخل POOL_HEADER، وهي بنية البيانات التي تفصل تخصيصات المخزن المؤقت في المجموعة. يشير على وجه الخصوص إلى حقل ProcessBilled، والذي يُستخدم لتخزين مؤشر إلى كائن _EPROCESS للعملية التي تعتبر "مشحونة" بالتخصيص. ويُستخدم هذا لحساب عدد تخصيصات المجموعة التي يمكن أن تتضمنها عملية معينة. لا يتم "الشحن" من جميع تخصيصات المجموعة من  العملية، وتلك التي ليس لديها حقل ProcessBilled معينة إلى Null في POOL_HEADER. بالإضافة إلى ذلك، مؤشر EPROCESS المخزن في ProcessBilled يتم فعليا XOR باستخدام ملف تعريف ارتباط عشوائي، لذا لا يحتوي ProcessBilled على مؤشر صالح.

وهذا يمثل صعوبة في ذلك، لأن المخازن المؤقتة NpFr تُحمّل تكاليفها على عملية الاستدعاء، وبالتالي يتم تعيين ProcessBled. عند تشغيل بدائية الاستغلال المطلوب، سيتم تعيين bPagesUnmapped إلى TRUE. إذا تم تمرير مؤشر غير صالح إلى keSetEvent، فسوف يتعطل النظام. ولذلك، من الضروري التأكد من أن POOL_HEADER للتخصيص غير المشحون. في هذه المرحلة، لاحظت أن كائن تسجيل السياق (Creg) نفسه غير مشحون. ومع ذلك، لا يسمح هذا الكائن بالتحكم في محتويات الذاكرة عند تعويض FSFrameMdl . لذلك، يجب رش كل من كائنات NpFR و Creg ، كما يجب أيضًا ترتيبها بشكل صحيح.

تسرب في حوض السباح - لا رش وعليك بالدعاء!

على عكس تخصيصات المجموعات الكبيرة (أحواض السباحة)، لا يمكنك تسريب العنوان من تخصيصات مجموعات LFH عبر NtQuerySystemInformation. بالإضافة إلى ذلك، ترتيب التوزيع عشوائي. لذلك، لا توجد طريقة لمعرفة ما إذا كانت المخازن المؤقتة المجاورة للكائن الضعيف بالترتيب الصحيح لتشغيل البدائي يستغل وتجنب تعطل النظام. ولحسن الحظ، يمكن استخدام الثغرة الأمنية لإحداث تسرب لمجموعة المخازن المؤقتة المجاورة. لنلقِ نظرة على دالة IOCTL لـ ConsumeTx:

مقتطف تفكيك FSRendezvousServer::ConsumeTx

مقتطف تفكيك FSRendezvousServer::ConsumeTx

أعلاه، تُستدعى الدالة FSStreamReg: InstStats:

فك تجميع FSStreamReg: :GetStats

فك تجميع FSStreamReg: :GetStats

هنا، يتم نسخ محتويات الذاكرة الخارجة عن الحدود لكائن الدفق الضعيف إلى SystemBuffer الذي يتم إرجاعه إلى تطبيق مساحة المستخدم المستدعي. يمكن استخدام الإصدار الأولي من تسرب معلومات المجموعة هذا لإجراء فحص للتوقيع على المخازن المؤقتة المجاورة للكائن المعرض للخطر. يمكن إجراء فحص للعديد من الكائنات المعرضة للخطر حتى يتم تحديد موقع الكائن داخل تخطيط الذاكرة المطلوب. بمجرد تحديد الكائن المطلوب، يكون تخطيط الذاكرة كما يلي:

تخطيط CVE-2023-36802 منخفض التجزئة لحوض السباحة

تخطيط CVE-2023-36802 منخفض التجزئة لحوض السباحة

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

الاستغلال في البرية

بعد إبلاغ MSRC عن المشكلة، تم اكتشاف استغلال الثغرة في البرية.

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

الخاتمة

كشف تحليل التصحيح بأثر رجعي عن إضافة جزء كبير من التعليمات البرمجية الجديدة إلى MSkssrv.sys في بناء 1809 من Windows 10. غالبًا ما تكون مراقبة إضافات التعليمات البرمجية الجديدة مفيدة للعثور على الثغرات الأمنية.

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

وقد تم اكتشاف هذه الثغرة الأمنية أثناء محاولة التفاعل مع سطح هجوم غير مألوف. إن وجود "معرفة قريبة جدًا من الصفر" يمكن أن يعني أيضًا وجود عقلية جديدة لكسرها.

Mixture of Experts | 12 ديسمبر، الحلقة 85

فك تشفير الذكاء الاصطناعي: تقرير إخباري أسبوعي

انضمّ إلى نخبة من المهندسين والباحثين وقادة المنتجات وغيرهم من الخبراء وهم يقدّمون أحدث الأخبار والرؤى حول الذكاء الاصطناعي، بعيدًا عن الضجيج الإعلامي.