الثغرة الصغيرة التي فعلت الكثير: CVE-2024-30089 هي ثغرة دقيقة في النواة استخدمتها لاستغلال جهاز Windows 11 مُحدث بالكامل (مع تفعيل كافة ميزات الأمان القائم على المحاكاة الافتراضية وتخفيف مخاطر الأجهزة)، وقد حققتُ بها فوزي الأول في مسابقة Pwn2Own هذا العام.
في هذه المقالة، أوجز نهجي المباشر في صيد الأخطاء البرمجية: اختيار نقطة بداية واتباع المسار بصورة حدسية حتى يلفت انتباهي شيء ما. هذا الخطأ البرمجي لافت لأنه يمكن استغلاله بصورة موثوقة نتيجة خطأ منطقي. يحدث الخطأ في حالة محددة داخل نظام اتصال بين العمليات، ما يؤدي لاحقًا إلى use-after-free. يتطلب العثور على الخطأ مقارنة مسارات التعليمات البرمجية الخاصة بالبرنامج عبر حالاته المختلفة المحتملة، وهي العملية التي وصفتها بالتفصيل. ولا يقل إثارة للاهتمام أصل هذا الخطأ البرمجي ونهج Microsoft في تصحيحه. ويتناول هذا المنشور هذه الموضوعات أيضًا.
من الأسئلة الشائعة التي أتلقاها حول البحث عن الثغرات الأمنية هو كيف أبدأ. في الواقع، قد يكون اختيار الهدف والالتزام به من أصعب الخطوات في عملية البحث. توجد الثغرة الأمنية التي تمت مناقشتها هنا في Microsoft Kernel Streaming Service (mskssrv.sys). راجع منشور المدونة هذا للحصول على نظرة عامة على النظام الفرعي. في ذلك المنشور، أشرت إلى بعض خصائص النظام الفرعي MSKSSRV التي قد تجعله سطحًا جيدًا للهجوم، وتحديدًا آلية الاتصالات بين العمليات (IPC) الخاصة به.
إن قاعدة التعليمات البرمجية الخاصة بـ MSKSSRV صغيرة إلى حد ما، كما أن آخر ثغرة اكتشفتُها في هذا النظام الفرعي كان قد تم استغلالها أيضًا بشكل مستقل في هجمات فعلية كثغرة يوم الصفر. وسمعت أيضًا عن جهود إضافية من باحثين وشركات أخرى لمراجعة برنامج التشغيل. لهذا السبب، وقعت في البداية في الفخ الشائع حيث افترضت أنه لم يعد هناك المزيد من الثغرات المتبقية لاكتشافها في سطح الهجوم هذا. لكن، لأنني كنت قد اقترحت ذلك في مدونتي السابقة، قررت أن أثق بحدسي وأستمر في البحث.
من أفضل الطرق لاكتساب أفكار بحثية جديدة البقاء على اطلاع بالأبحاث الحالية. قرأت منشور مدونة ممتازًا على k0shl كان مصدر إلهامي للبحث عن نوع معين من الحشرات. في الثغرة التي اكتشفها k0shl، يتم تهيئة عدد مراجع الكائن وزيادته من دون آلية قفل مناسبة، ما يخلق نافذة يمكن أن يحدث خلالها use-after-free. ورغم أن الخطأ البرمجي لدى k0shl يقع في userland وليس في نواة، فإن أسلوب كتابة تعليمات برمجية في المكتبة المعرّضة للثغرة ذكّرني بتدقيقي السابق لبرنامج التشغيل MSKSSRV.
يتفاعل MS KS Server (MSKSSRV) مع عملية في userland عبر كائناتFSStreamReg. FSRegObject لا تنفذ دالة قفل ضمن vtable، وأن تنفيذ () base class ليس سوى تعليمات nop. وهذا يعني أنه لم يتم تنفيذ أي آلية قفل فعليًا لكائناتFSStreamReg
كتلة التعليمات البرمجية 1: FSRendezvousServer::Close، مسار القفل وإلغاء القفل لكائنات FSRegObjects
يوجد كائنان مشتقان من base class
كما ذكرت في منشوري السابق، فإن جانب مشاركة الكائنات بين العمليات في MSKSSRV مسار جدير بالاهتمام من ناحية الثغرات، لذلك قررت التعمق فيه. يوضح الرسم التالي آلية IPC في هذا النظام الفرعي:
الرسم التخطيطي 1: الاتصال بين العمليات في MS KS Server
يؤدي فتح مُعرّف ملف لجهاز MSKSSRV عبر CreateFile إلى إنشاء FILE_OBJECT يقابل هذا المُعرّف. وباستخدام هذا المُعرّف، يمكن لعملية تهيئة دفق جديد أو كائن سياق عبر إرسال IOCTL إلى الجهاز باستخدام دالّة DeviceIoControl. تحدد عملية التهيئة أي عملية بعيدة يمكنها تسجيل الكائن عبر تحديد معرف العملية من خلال المتغيرlpInBufferFSRegObject في
Irp->CurrentStackLocation->FileObject->FsContext2.
يتم تخزين المؤشر نفسه إلى كائن FSRegObject مرتين في FsContext2: مرة في الذي تستخدمه عملية التهيئة، ومرة في الذي تستخدمه عملية التسجيل. وبهذه الطريقة، يمكن مشاركة المراجع إلى كائن عبر العمليات. فعلى سبيل المثال، باستخدام كائن FSStreamReg يمكن لعدة عمليات الوصول إلى مخزن إطار الدفق المؤقت، كما هو موضح في الرسم التخطيطي 1. يتم تهيئة عدد مراجع إلى 1، ثم تتم زيادته مرة أخرى بعد اكتمال التهيئة. ويؤدي تسجيل إلى زيادة عدد مراجعه مرة أخرى، ليصبح المجموع ثلاثة مراجع لكل كائن.
الرسم التخطيطي 2: تهيئة كائنات FSContextReg وتسجيلها
قررت أن أعود إلى الجزء الذي دقّقته سابقًا بحثًا عن ثغرات تتعلق بآليات القفل. لاحظت أن الدالة
كتلة التعليمات البرمجية 2: إجراءات DispatchCleanup وDipatchCleanup لبرنامج تشغيل MS KS Server
يعالج روتينالإرسال نوعًا واحدًا أو أكثر من IRPs، وهي طلبات إدخال/إخراج مجمّعة. في Windows، عند إغلاق جميع مراجع المُعرّف لملف ما، يتلقى برنامج تشغيل نظام الملفات المعني طلب
في MSKSSRV، تُستدعى
كتلة التعليمات البرمجية 3: FSRendezvousServer::Close، فحوصات العملية على FSRegObjects
يتم تخزين معلومات خاصة بالعملية في
Irp->CurrentStackLocation->FileObject->FsContext2
في وقت التهيئة أو التسجيل. يحدد برنامج التشغيل الموارد الخاصة بالعملية (
كائنات، كائنات أحداث، وغيرها) التي يجب تحريرها عبر التحقق من معرف عملية الجهة المستدعية. وإذا كانت العملية هي عملية التهيئة أو عملية التسجيل، تُجرى عملية تنظيف إضافية لتلك الموارد الخاصة بالعملية.
لفت هذا انتباهي لأن إجراءات Dispatch تعمل عادة ضمن سياق عملية اعتباطي، مع بعض الاستثناءات. بعبارة أخرى، يختار النظام خيطًا لتنفيذ عمل Dispatch، واختيار الخيط يكون اعتباطيًا. اكتشفت أن
إضافة إلى ذلك، ومن ميزات نظام التشغيل Windows، يمكن مشاركة المُعرّفات مع عمليات أخرى (عبر وراثة عملية فرعية أو باستخدام دالّة DuplicateHandle ضمن واجهات برمجة تطبيقات النظام). وعبر مُعرّف الملف المشترك، يمكن للعملية الأخرى أيضًا التفاعل مع
الرسم التخطيطي 3: مشاركة مُعرّف جهاز MS KS Server
وبناءً على ذلك، قد يكون سياق العملية أثناء
الرسم التخطيطي 4: إغلاق عملية خارجية لآخر مُعرّف مرتبط بكائن FILE_OBJECT
لاحظت أن الدالة
كتلة التعليمات البرمجية 4: يمكن استدعاء FSRegObject::Release مرتين من FSRendezvousServer::Close
بوجه عام، ليس تجاوز عدد عمليات إلغاء المرجعية (dereferences) عدد المراجع (references) على كائن هو الطريقة الوحيدة لحدوث use-after-free. لكن في هذه الحالة، يمكننا الجزم بأنه إذا تم تحرير فإن عدد مراجع هذا الكائن يكون قد انخفض إلى الصفر. آخر مرة يتم فيها الوصول إلى صالح أثناء طلبات IRP تكون في استدعاء . لذلك، إذا كان حدوث use-after-free ممكنًا، فإن استدعاء سيحدث دائمًا بعد أن يكون الكائن قد حُرّر بالفعل. وأثناء هذا الاستدعاء، ستتم مرة أخرى عملية إلغاء مرجعية الكائن. ولهذا السبب، يعدّ إحصاء عدد عمليات إلغاء المرجعية مؤشرًا استدلاليًا جيدًا لاكتشاف حالات use-after-free في هذه الحالة تحديدًا.
ولم يبقَ سوى تتبع الحالات المحتملة للبرنامج مع تسجيل مواضع تحرير الكائن ومواضع الوصول إليه. وقمت بذلك عبر محاكاة منطق البرنامج ذهنيًا أثناء طلبات ، بحيث يبدأ كل سيناريو بدوال Dispatch المقابلة (كتلة التعليمات البرمجية 2)، لكل حالة محتملة.
يوضح ما يلي الحالات وفقًا للعملية التي تغلق المرجع الأخير لمُعرّف. يمثّل كل إدخال عدد عمليات إلغاء المرجعية (dereferences) لكائن FSContextReg التي تحدث إذا قامت العملية المعنية بإغلاق المُعرّف الأخير. ملاحظة: لا يوجد فرق وظيفي بين HANDLE #1 (initializing handle) وHANDLE #2 (registering handle)، لأن الحقل يشير إلى الذاكرة نفسها في كل يمثلهما المُعرّفان المقابلان.
عمليات إلغاء المرجعية لكائن FSContextReg لكل حالة محتملة من حالات IPC في MSKSSRV
نجاح! تؤدي الحالة الأخيرة إلى أربع عمليات إلغاء مرجعية: عمليتان من العملية الخارجية وعمليتان من عملية التهيئة (ومن عملية التسجيل أيضًا)، بينما يكون عدد المراجع في البداية ثلاثة فقط؛ ما يعني أن حدوث use-after-free ممكن. وكررت التمرين نفسه مع كائنات
وأثناء تنفيذ التمرين الذهني المشار إليه أعلاه، وجدت المشكلة. إذا كانت العملية هي عملية التهيئة أو عملية التسجيل، تُنفّذ عملية التنظيف المناسبة، ويتم ضبط المؤشر المخزّن في
كتلة التعليمات البرمجية 5: FSRegObject::CloseInitProcess يضبط مؤشر FSContext على NULL
وهذا يعني أن
لكن إذا كانت العملية المستدعية عملية خارجية، فلن يحدث تنظيف، وستُستدعى
الآن، إذا تم إغلاق المُعرّف الثاني بواسطة عملية قامت بتهيئة كائن FSContextReg وتسجيله معًا، فسيقوم الكائن بتنظيف موارد العملية المخزنة لديه، ما يجعله فارغًا. يؤدي ذلك إلى استدعاء
وبذلك يصبح المجموع أربع عمليات إلغاء مرجعية على كائن
الرسم التخطيطي 5: توضيح CVE-2024-30089
قد يتساءل القارئ: لماذا لا يمكن أن تكون عملية خارجية آخر من يغلق المُعرّفين معًا، ما دام ذلك يبدو أنه سيؤدي أيضًا إلى أربع عمليات إلغاء مرجعية؟ قبل تدمير الكائن وتحريره، يتم فصله من قائمة مخزنة في الكائن العام FSRendezvousServer يتم التحقق من أن المؤشر في عضو صالح في القائمة. في هذه الحالة، يتم تحرير الكائن عند الاستدعاء الثاني للكود في نهاية الدالّة. أثناء الاستدعاء الرابع للكود يكون الكائن قد فُصل بالفعل من القائمة، ما يجعله كائنًا غير صالح، وبالتالي لا يمكن استخدامه. لاستغلال الثغرة use-after-free، يجب أن يحدث ذلك بعد استرجاع الكائن والتحقق من صلاحيته في الكود يوضح مقتطف تعليمات برمجية أدناه ركيزة use-after-free التي يمكن الحصول عليها عبر الثغرة:
كتلة التعليمات البرمجية 6: مسار ركيزة UAF
في دليل التحديث الأمني لهذه الثغرة الأمنية، تشير درجة CVSS إلى أن "الاستغلال [هو] أكثر احتمالاً" كما أن تعقيد الهجوم لهذه الثغرة "منخفض". ورغم أن Microsoft لا تقدم تفسيرات تفصيلية لدرجاتها، فقد لاحظت بعض الأنماط أثناء مقارنة فروق التصحيحات لثغرات أخرى. ويرجح أن الثغرة حصلت على هذه الدرجة لأنها ناتجة عن خطأ منطقي، ما يجعل استثارتها ممكنًا بصورة موثوقة. ومن خلال اتباع الخطوات الموضحة في الرسم التخطيطي 5، يمكن للمخترق استثارة سيناريو use-after-free الموضح في كتلة التعليمات البرمجية 6 بصورة متكررة. ومع ذلك، لا يعني هذا أن استغلالها عمليًا أمر مباشر. وسيتم تناول شرح تفصيلي لخطوات الاستغلال في الجزء التالي من هذه السلسلة.
يُعد فهم كيفية حدوث الخطأ البرمجيّ أمرًا مهمًا لترسيخ نهج استباقي في ممارسات التطوير الآمن. ولتحديد كيفية إدخال الثغرة الأمنية، حلّلت إصدارات سابقة من برنامج التشغيل حصلت عليها من Winbindex، وبحثت عن أي اختلافات منطقية في الدالة
في قسم الثغرات الأمنية، ذكرت أن السبب النهائي لهذا الخطأ هو عدم ضبط
Irp->CurrentStackLocation->FileObject->FsContext2
إلى
إذا كانت عملية الاستدعاء خارجية. لدهشتي، رأيت السطر نفسه من التعليمات البرمجية في إصدار مبكر من mskssrv.sys:
كتلة التعليمات البرمجية 7: إصدار مبكر من FSRendezvousServer::Close، حيث تم ضبط FsContext2 على NULL
في كتلة التعليمات البرمجية المعروضة أعلاه، يتم ضبط
كتلة التعليمات البرمجية 8: ضبط FsContext على NULL ضمن فحص علم ميزة
يظهر في كتلة التعليمات البرمجية أعلاه فحص لعلامة الميزة
Feature_Servicing_TeamsUsingMediaFoundationCrashes
.
أعلام الميزات مكوّن في Windows يفعّل أو يعطّل وظائف وتجارب مختلفة، رغم محدودية المعلومات المتاحة عنها علنًا. في هذه التدوينة نناقش كيف استُخدمت أعلام الميزات في تصحيحات الثغرات. تُستخدم أعلام الميزات أحيانًا لاختبار وظيفة قبل اعتمادها رسميًا. في هذه الحالة، إذا كان ملف
Feature_Servicing_TeamsUsingMediaFoundationCrashes
عند تفعيل الميزة، لا يتم ضبط على NULL، ما يؤدي إلى حدوث الثغرة. لوحظ أن هذه الميزة مفعّلة افتراضيًا في تثبيتات Windows 10. في Windows 11، وكما يظهر في كتلة التعليمات البرمجية 1، لا يظهر هذا الشرط الخاص بعلم الميزة (feature flag)، ولا يتم ضبط المؤشر على ما يجعل النظام عرضة للثغرة أيضًا.
وبسبب اسم الميزة، بحثت في وظائف Microsoft Teams، وهو برنامج لمؤتمرات الفيديو. وأكدت أن التطبيق يمكنه استخدام وظائف MSKSSRV لمشاركة تدفقات الوسائط عبر العمليات. من المحتمل أن مشاركة مُعرّف الدفق كانت تتسبب في تعطل Teams. ومن الموضوعات المثيرة لمزيد من البحث دراسة كيفية مشاركة Teams لمُعرّفات جهاز MSKSSRV عبر العمليات، ولماذا قد يؤدي تنظيف المؤشرات بصورة صحيحة إلى تعطل التطبيق.
يركز هذا الجزء من السلسلة على الثغرة الأمنية نفسها، بما في ذلك تصحيحها. وكنت مهتمًا على نحو خاص بفحص تصحيح هذا الخطأ البرمجي، لأن الإصلاح الذي اقترحته بدا أنه يتسبب في أعطال داخل Microsoft Teams. ومن المهم التنبيه إلى أنني حتى هذه اللحظة لم أدرس بعد كيفية استخدام تطبيق فعلي لبرنامج التشغيل MSKSSRV عمليًا. وغياب هذا السياق يخلق ثغرات عند محاولة فهم سبب تصميم النظام بهذه الصورة. قد يتطلب التصحيح الكامل لهذا الخطأ البرمجي قدرًا من إعادة هيكلة تعليمات برمجية الأساسية، وقد يكشف مزيدًا من التفاصيل حول كيفية عمل نظام IPC كما هو مقصود. كما كنت آمل استخلاص بعض الرؤى حول ممارسات البرمجة الآمنة من مطوري Microsoft.
لخيبة أملي، لم تتم معالجة الخطأ المنطقي الذي تسبب في الثغرة الأمنية، رغم تصحيحه ضمن تحديثات الأمان لشهر يونيو 2024، بشكل مباشر. بدلاً من ذلك، تمت إضافة فحص رمز مميز قبل مسارات التعليمات البرمجية المعرضة للثغرة. اطّلع على الكود أدناه لطلب initialize context عبر IOCTL، الذي تتولى معالجته الدالة
كتلة التعليمات البرمجية 9: تبدأ دالّة IOCTL بفحص علامة الميزات (feature flag)، وتتحقق مما إذا كانت العملية المستدعية خادم إطار (frame server)
تبدأ الدالة أعلاه بالتحقق مما إذا كانت ميزة مّا مفعّلة. من المحتمل أن تكون هذه علامة الميزة المقابلة للتصحيح. إذا كانت الميزة مفعلة، فيجب أن تُعيد
لنلقِ نظرة على
كتلة التعليمات البرمجية 10: KsIsCurrentProcessFrameServer يجري فحص SID على access token الخاص بالخيط المستدعي
تتحقق هذه الدالة من الرمز المميز الخاص بالخيط المستدعي بمقارنته مع معرفي أمان محددين (SIDs). وترتبط SIDs برمز مميز في المجموعة . إذا كان أي من SIDs مفعّلًا في الرمز المميز الخاص بالخيط المستدعي، فيمكن تنفيذ كود الدالة المعرّض للثغرة.
بعد الاطلاع على ذلك، اشتبهت في أن الخطأ البرمجي Administrator to Kernel ما زال قائمًا. وفي النهاية، لم تُعالج مشكلات تلف الذاكرة مطلقًا. وأكدت ذلك عبر إجراء تعديل بسيط على الاستغلال الأصلي: يمكن لمستخدم Administrator تشغيل الخدمة ، وفتح مُعرّف للخدمة، ثم إنشاء عملية الاستغلال باستخدام هذا المُعرّف. وتمكنت من الحصول على نسخة أولية كاملة للقراءة والكتابة في نواة على نظام مطبّق عليه جميع التصحيحات.
ورغم أن Microsoft لا تعتبر الانتقال من المسؤول إلى النواة (Administrator to Kernel) حدًا أمنيًا، فقد استخدمت عناصر التهديد أخطاء برمجية مماثلة للحصول على نسخة أولية (primitive) للقراءة والكتابة في نواة، ثم استخدامها في تعمية EDR وعمليات rootkit. إذا كنت مهتمًا بمعرفة نوع الأشياء التي يمكن القيام بها باستخدام هذه النسخة الأولية، راجع محاضرتي في BlackHat مع FuzzySec.
ركّز هذا المنشور على جزء أبحاث الثغرات ضمن مشاركتي في Pwn2Own، والذي تمثل في العثور على ثغرة اليوم الأول في نواة يمكن استغلالها لزيادة الامتيازات. ويعرض هذا المنشور مسار الرحلة: الاستلهام من أبحاث أخرى، والفشل في العثور على خطأ برمجي، واختيار زاوية جديدة، ثم ملاحظة ما يثير الشك، وأخيرًا تحديد موضع الثغرة. الآن بعد تحديد خطأ برمجي ووجود نسخة أولية (primitive) لحالة use-after-free، يفترض أن الباقي سيكون مباشرًا، أليس كذلك؟ يبدو أن Microsoft ترى ذلك، إذ قيّمت هذا الخطأ البرمجي بأنه "Exploitation More Likely" مع تعقيد هجوم "low". هل هم على حق؟ سأتناول ذلك، واستراتيجية الاستغلال، وأكشف معنى عنوان السلسلة في الجزء التالي.
ابقَ على اطلاع دائم على أبرز الاتجاهات في مجالات الذكاء الاصطناعي، والأتمتة، والبيانات، وغيرها الكثير من خلال رسالة Think الإخبارية. راجع بيان الخصوصية لشركة IBM.
Andréa Piazza، تقديرًا للمخططات الرائعة
Emma Kirkpatrick، لشرح نموذج أمان Windows لي بصبر