لقد كان نموذج كائن العنصر (COM) حجر الأساس في تطوير Microsoft Windows منذ أوائل التسعينيات، وما زال شائعًا جدًا في أنظمة التشغيل والتطبيقات الحديثة لنظام Windows. والاعتماد على عناصر COM والتطوير المكثف للميزات على مرّ السنين أدَّى إلى إنشاء سطح هجوم واسع. في فبراير 2025، نشر James Forshaw (@tiraniddo) من Google Project Zero تدوينة توضِّح نهجًا جديدًا لاستغلال تقنية DCOM الموزعة، حيث يمكن استخدام كائنات COM المحتجزة لتنفيذ تعليمات NET. المُدارة ضمن سياق عملية DCOM على الخادم. يسلِّط Forshaw الضوء على عدة حالات استخدام لرفع الامتيازات وتجاوز آلية Protected Process Light (PPL).
استنادًا إلى بحث Forshaw، قام Mohamed Fakroud (@T3nb3w) في مطلع مارس 2025 بنشر تطبيق عملي للتقنية يُتيح تجاوز حماية PPL. أجريت أنا وJimmy Bayne (@bohops) بحثًا مشابهًا في فبراير 2025، ما قادنا إلى تطوير تقنية إثبات مفهوم للحركة الجانبية دون ملفات عبر استغلال كائنات COM المحتجزة.
يُعَد COM معيار واجهة ثنائية وطبقة خدمة وسيطة تسمح بالكشف عن عناصر متميزة وقابلة للتجزئة للتفاعل مع بعضها ومع التطبيقات، بغض النظر عن لغة البرمجة المستخدمة. على سبيل المثال، يمكن لعناصر COM المطوّرة بلغة ++C التفاعل بسهولة مع تطبيق NET.، ما يمكِّن المطورين من دمج وحدات برمجية متنوعة بشكل فعَّال. تُعَد DCOM تقنية تواصل عن بعد تمكِّن عملاء COM من التواصل مع خوادم COM عبر الاتصال بين العمليات (IPC) أو استدعاءات الإجراءات البعيدة (RPC). يعمل العديد من خدمات Windows على تنفيذ عناصر DCOM التي يمكن الوصول إليها محليًا أو عن بُعد.
عادةً ما يتم تسجيل فئات COM وتخزينها في سجل Windows. يتفاعل برنامج العميل مع خادم COM عن طريق إنشاء نسخة من فئة COM، والمعروفة باسم كائن COM. يوفر هذا الكائن مؤشرًا إلى واجهة موحَّدة. يستخدم العميل هذا المؤشر للوصول إلى أساليب الكائن وخصائصه، ما يجعل من السهل التواصل وأداء الوظائف بين العميل والخادم.
غالبًا ما تكون كائنات COM أهدافًا بحثية لتقييم التعرّض للثغرات الأمنية واكتشاف الميزات القابلة لإساءة الاستخدام. كائن COM المحتجز هو فئة من الأخطاء يحدث فيها أن يقوم عميل COM بإنشاء فئة COM في خادم DCOM خارج العملية، حيث يتحكم العميل في كائن COM من خلال مؤشر كائن يتم تمريره بالمرجعية. اعتمادًا على الحالة، قد يعرض متجه التحكم هذا أخطاء منطقية مرتبطة بالأمان.
توضِّح تدوينة Forshaw حالة استخدام لتجاوز حماية PPL، حيث يتم استغلال واجهة IDispatch الموجودة في فئة WaaSRemediation COM لإساءة استخدام كائن COM المحتجز وتنفيذ تعليمات NET. يتم تنفيذ فئة WaaSRemediation في خدمة WaaSMedicSvc، والتي تعمل كعملية svchost.exe محمية ضمن سياق NT AUTHORITY\SYSTEM. كان الشرح المتقن الذي قدَّمه Forshaw الأساس لأبحاثنا التطبيقية وتطوير تقنية إثبات مفهوم للحركة الجانبية دون ملفات.
بدأت رحلتنا البحثية باستكشاف فئة WaaSRemediation COM التي تدعم واجهة IDispatch. تُتيح هذه الواجهة للعملاء تنفيذ الربط المتأخر. عادةً ما تكون تعريفات الواجهة وأنواع الكائنات المستخدمة من قِبل عملاء COM محددة مسبقًا أثناء وقت الترجمة. بدلًا من ذلك، يُتيح الربط المتأخر للعميل اكتشاف الكائن واستدعاء أساليبه أثناء وقت التشغيل. تتضمن واجهة IDispatch الطريقة GetTypeInfo، التي تُرجع واجهة ITypeInfo. تحتوي واجهة ITypeInfo على طرق يمكن استخدامها لاكتشاف معلومات النوع للكائن الذي ينفِّذها.
إذا كانت فئة COM تستخدم مكتبة نوع، يمكن للعميل الاستعلام عنها عبر ITypeLib (المستخرجة من ITypeInfo-> GetContainingTypeLib) للحصول على معلومات النوع. بالإضافة إلى ذلك، قد تُشير مكتبات النوع أيضًا إلى مكتبات نوع أخرى للحصول على معلومات نوع إضافية.
وفقًا لمنشور المدونة الذي كتبه Forshaw، تُشير WaaSRemediation إلى مكتبة النوع WaaSRemediationLib، والتي بدورها تُشير إلى stdole (OLE Automation). تستخدم مكتبة WaaSRemediationLib فئتين من COM ضمن تلك المكتبة، هما StdFont وStdPicture. من خلال تنفيذ COM Hijacking على كائن StdFont عبر تعديل مفتاح السجل TreatAs، ستتم إعادة توجيه الفئة إلى فئة COM أخرى نختارها، مثل System.Object في .NET Framework. ومن الجدير بالذكر أن Forshaw أشار إلى أن StdPicture غير صالح، إذ يقوم هذا الكائن بالتحقق من الإنشاء خارج العملية، لذا ركَّزنا على استخدام StdFont.
تثير كائنات NET. اهتمامنا بسبب طريقة GetType الخاصة بـ System.Object. من خلال GetType، يمكننا إجراء انعكاس (.NET Reflection) للوصول في النهاية إلى Assembly.Load. رغم اختيار System.Object، فإن هذا النوع يُعَد جذر تسلسل الأنواع في NET. لذلك، يمكن استخدام أي كائن NET.
بعد إعداد المرحلة الأولى، كانت هناك قيمتان DWORD إضافيتان في مفتاح HKLM\Software\Microsoft.NetFramework مطلوبتان لجعل حالة الاستخدام المتصورة لدينا واقعًا:
بعد التأكد من إمكانية تحميل أحدث إصدار من CLR وNET. في اختباراتنا الأولية، علمنا أننا على المسار الصحيح.
مع تحويل تركيزنا إلى الجوانب البرمجية عن بُعد، استخدمنا في البداية Remote Registry للتلاعب بقيم مفاتيح السجل الخاصة بـ NetFramework. والسيطرة على كائن StdFont في الجهاز المستهدف. بعد ذلك، استخدمنا CoCreateInstanceEx بدلًا من CoCreateInstance لإنشاء كائن COM من نوع WaaSRemediation على الهدف البعيد والحصول على مؤشر إلى واجهة IDispatch.
وباستخدام مؤشر IDispatch، نستدعي وظيفة GetTypeInfo للحصول على مؤشر إلى واجهة ITypeInfo، التي تكون محتجزة داخل الخادم. تحدث استدعاءات وظائف الأعضاء بعد ذلك على جانب الخادم. بعد تحديد مرجع مكتبة النوع المضمَّن المهمة (stdole) واستنتاج مرجع كائن الفئة التالي المهم (StdFont)، استخدمنا في النهاية طريقة CreateInstance "القابلة للتنفيذ عن بُعد" على واجهة ITypeInfo لإعادة توجيه تدفق رابط كائن StdFont (عبر التلاعب السابق بـ TreatAs) لإنشاء System.Object.
نظرًا لأن AllowDCOMReflection مضبوط بشكل صحيح، يمكننا بعد ذلك إجراء انعكاس NET. عبر DCOM للوصول إلى Assembly.Load لتحميل مكتبة NET. داخل خادم COM. نظرًا لأننا نستخدم Assembly.Load عبر DCOM، فإن تقنية الحركة الجانبية هذه تعمل بالكامل دون ملفات، حيث تتم عملية نقل بايتات التجميع بواسطة آلية DCOM البعيدة. للحصول على شرح مفصَّل لتدفق هذه العملية التقنية من إنشاء الكائن إلى الانعكاس، يُرجى الاطِّلاع على الرسم البياني التالي:
كانت مشكلتنا الأولى والرئيسية هي استدعاء Assembly.Load_3 عبر IDispatch->Invoke. حيث يقوم Invoke بتمرير مصفوفة كائنات كوسائط إلى الدالة المستهدفة، وLoad_3 هو النسخة متعددة الاستخدامات من Assembly.Load التي تأخذ مصفوفة بايت واحدة. لذلك، كان علينا تغليف SAFEARRAY الخاص بالبايتات داخل SAFEARRAY آخر من نوع VARIANTs– في البداية، كنا نحاول تمرير SAFEARRAY واحد من البايتات فقط.
من المشكلات الأخرى كانت تحديد التحميل المتعدد الصحيح من Assembly.Load. تمت الاستعانة بالدوال المساعدة من كود CVE-2014-0257 الخاص بـ Forshaw، والذي تتضمَّن دالة GetStaticMethod. استخدمت هذه الوظيفة انعكاس NET. عبر DCOM للعثور على دالة ثابتة بناءً على مؤشر النوع واسم الدالة وعدد مَعلماتها. لدى Assembly.Load نسختان ثابتتان تأخذان وسيطًا واحدًا فقط؛ وبناءً عليه، اضطررنا لاستخدام حل مؤقت وغير مثالي. لاحظنا أن المثيل الثالث من Load التي تأخذ وسيطًا واحدًا كانت الخيار الصحيح لنا.
أحد أهم العيوب التي لاحظناها في هذه التقنية هو أن المنارة التي يتم تشغيلها يكون عمرها محدودًا بعمر عميل الـ COM؛ وفي هذه الحالة، بعمر تشغيل الملف التنفيذي المستخدم في التسليح "ForsHops.exe". (والاسم بالتأكيد تم اختياره بعناية). لذا، إذا قام ForsHops.exe بتنظيف مراجع COM الخاصة به أو أنهى عمله، فسيتم إنهاء المنارة التي كانت تعمل تحت عملية svchost.exe على الجهاز البعيد أيضًا. لقد جرّبنا حلولًا متعددة، مثل إبقاء الخيط الرئيسي في تجميعة NET. معلقًا إلى أجل غير مسمَّى، وتشغيل الشيل كود في خيط آخر، وجعل ForsHops.exe يترك خيط الاستغلال معلّقًا، لكن لم يكن أيٌّ من هذه الحلول أنيقًا.
في وضعه الحالي، يستمر ForsHops.exe في العمل حتى تُنهي المنارة نشاطها، وعندها يزيل عمليات التسجيل الخاصة به. هناك فرص للتحسين، لكن سنترك ذلك كتمرين للقارئ.
التوجيهات الخاصة بالكشف التي اقترحها Samir Bousseaden (@SBousseaden) بعد أن نشر Mohamed Fakroud تنفيذهم تنطبق أيضًا على أسلوب الحركة الجانبية هذا:
علاوةً على ذلك، نوصي بتنفيذ ضوابط إضافية على النحو التالي:
بالإضافة إلى ذلك، استخدِم قاعدة YARA التالية لإثبات المفهوم لاكتشاف النسخة القياسية القابلة للتنفيذ من ForsHops.exe:
قاعدة Detect_Standard_ForsHops_PE_By_Hash
يوسِّع تنفيذنا، بدرجة بسيطة، نهج إساءة استخدام COM الذي شرحه Forshaw، من خلال استخدام كائنات COM المحتجزة للحركة الجانبية بدلًا من التنفيذ المحلي لتخطّي PPL. لِذا يظل عرضة لنفس أساليب الكشف المطبّقة على التنفيذ المحلي.
يمكن العثور على كود للحركة الجانبية الخاصة بـ ForsHops.exe من خلال الرابط هنا.
شكر خاص لكلٍّ من Dwight Hohnstein (@djhohnstein) وSanjiv Kawa (@sanjivkawa)على ملاحظاتهم بشأن هذا البحث ومراجعتهم لمحتوى المقال.