لطالما كان نموذج كائن العنصر (COM) حجر الزاوية في تطوير Microsoft Windows منذ أوائل التسعينيات ولا يزال سائدًا جدًا في أنظمة تشغيل وتطبيقات Windows الحديثة. وقد أدى الاعتماد على عناصر COM والتطوير الشامل للمزايا عبر السنين إلى توفير نطاق واسع للهجوم. في فبراير 2025، أصدر James Forshaw (@tiraniddo) من Google Project Zero منشور مدونة يوضح فيه نهجًا جديدًا لاستغلال تقنية الاتصال عن بُعد الموزعة (DCOM)، حيث يمكن استخدام كائنات COM المحاصرة لتنفيذ كود .NET المدار في سياق عملية DCOM على الخادم. يسلط Forshaw الضوء على عدة حالات استخدام لزيادة الامتيازات وتخطي نظام العمليات المحمية (PPL).
استنادًا إلى بحث Forshaw،نشر محمد فاروق ( @T3nb3w ) تطبيقًا للتقنية لتخطي إجراءات حماية PPL في أوائل مارس 2025. أجريت أنا و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). تستخدم WaaSRemediationLib فئتين من فئات COM من تلك المكتبة، وهما StdFont وStdPicture. ومن خلال تنفيذ اختراق COM على كائن StdFont عبر تعديل مفتاح التسجيل TreatAs، ستشير الفئة إلى فئة COM أخرى نختارها، مثل System.Object في إطار عمل .NET. وتجدر الإشارة إلى أن Forshaw يشير إلى أن StdPicture غير قابل للتطبيق، وذلك لأن هذا الكائن يُجري فحصًا لعملية إنشاء مثيل خارج العملية، لذلك ركزنا على استخدام StdFont.
تُعد كائنات .NET مثيرة للاهتمام بالنسبة إلينا بسبب الدالة GetType الموجودة في System.Object . من خلال الدالة GetType، يمكننا تنفيذ انعكاس .NET للوصول في النهاية إلى Assembly.Load. وبينما اخترنا System.Object، يصادف أن يكون هذا النوع هو أصل التسلسل الهرمي للأنواع في .NET. لذلك، يمكن استخدام أي كائن من كائنات .NET.
مع إعداد المرحلة الأولية، كانت هناك قيمتان DWORD أخريان ضمن مفتاح HKLM\Software\Microsoft\.NetFramework مطلوبتان لجعل حالة الاستخدام المتصورة حقيقة واقعة:
عندما تأكدنا من إمكانية تحميل أحدث إصدار من CLR و.NET ضمن جهود الاختبار الأولية التي بذلناها، أدركنا أننا نسير على المسار الصحيح.
بعد تحويل انتباهنا للتركيز على جوانب البرمجة عن بُعد، استخدمنا أولاً السجل البعيد للتلاعب بقيم مفاتيح تسجيل .NetFramework واختطاف كائن StdFont على الجهاز المستهدف. بعد ذلك، استبدلنا الدالة CoCreateInstance بالدالة CoCreateInstanceEx لإنشاء مثيل لكائن WaaSRemediation COM على الهدف البعيد والحصول على مؤشر يُوصل إلى واجهة 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 أخرى من نوع VARIANT –في البداية، ظللنا نحاول تمرير SAFEARRAY واحدة من وحدات البايت.
كانت المشكلة الأخرى هي العثور على الحمل الزائد المناسب للدالة Assembly.Load . الدوال المساعدة مقتبسة من كود الثغرة CVE-2014-0257 الذي نشره Forshaw، والذي تضمن الدالة GetStaticMethod . استخدمت هذه الدالة انعكاس NET. عبر DCOM للعثور على دالة ثابتة بناءً على مؤشر النوع واسم الدالة وعدد معلماتها. تحتوي Assembly.Load على اثنين من الأحمال الزائدة الثابتة التي تأخذ كل منهما وسيطة واحدة؛ وعلى هذا النحو، انتهى بنا الأمر إلى استخدام حل مبتكر. لقد لاحظنا أن المثيل الثالث من الدالة Load التي تأخذ وسيطة واحدة كان هو اختيارنا الصحيح.
واحدة من أكبر العيوب التي لاحظناها في هذه التقنية هي أن الإشارة التي تظهر سيكون عمرها محدودًا بمدة بقاء عميل COM؛ وفي هذه الحالة، مقيدة بعمر تطبيق الملف الثنائي المستخدم في الهجوم "ForsHops.exe" (وهو اسم مختار بعناية، بالطبع). لذا، إذا مسح ForsHops.exe مراجع COM الخاصة به أو أُغلق، فسينطبق ذلك أيضًا على الإشارة التي كانت تعمل ضمن svchost.exe على الجهاز البعيد. لقد جربنا حلولاً مختلفة، مثل جعل تجميع .NET يُعلق سلسلته الرئيسية إلى أجل غير مسمى، وتنفيذ كود shellcode في سلسلة أخرى، وجعل 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) على تقديم التعليقات حول هذا البحث وتقديم تقييمات لمحتوى منشور المدونة.