يتم تعريف الفريق الأحمر الحديث من خلال قدرته على اختراق نقاط النهاية واتخاذ الإجراءات لإكمال الأهداف. ولتحقيق الهدف الأول، تقوم العديد من الفرق بتنفيذ خيار الأوامر والتحكم (C2) المخصص لها أو استخدام خيار مصدر مفتوح. بالنسبة للأخير، هناك تدفق مستمر من أدوات ما بعد الاستغلال التي تستفيد من ميزة مختلفة في Windows وActive Directory وتطبيقات الجهات الخارجية. وقد اعتمدت آلية تنفيذ هذه الأدوات، على مدار السنوات العديدة الماضية، اعتمادًا كبيرًا على تنفيذ تجميعات .NET في الذاكرة.
على الرغم من كونها جزءًا كبيرًا من ترسانة الفريق الأحمر الحديثة، فإن الأساليب المستخدمة في تنفيذ تجميعات .NET على نقطة نهاية معرضة للخطر ظلت راكدة إلى حد كبير. في منشور المدونة هذا، سنناقش كيف يمكن للفرق الحمراء إدخال أدوات تنفيذ .NET الخاصة بها إلى هذا العقد.
ابقَ على اطلاع دائم على أبرز الاتجاهات في مجالات الذكاء الاصطناعي، والأتمتة، والبيانات، وغيرها الكثير من خلال رسالة Think الإخبارية. راجع بيان الخصوصية لشركة IBM.
منذ فترة ليست ببعيدة، اعتمدت العديد من الفرق الحمراء على PowerShell لأدوات ما بعد الاستغلال. اتخذت Cobalt Strike خطوة لتغيير ذلك في عام 2018 من خلال تنفيذ وحدة تنفيذ التجميع (execute-assembly). كان تنفيذ التجميع يؤدي إلى إنشاء عملية تضحية وحقن ملف DLL عاكس فيها، والذي كان يحمِّل وقت تشغيل اللغة المشتركة (CLR) وينفذ تجميع .NET المقدم بواسطة المشغل.
أدى ذلك إلى نقل الكثير من أدوات ما بعد الاستغلال إلى تجميعات .NET. بعد فترة من الزمن، بدأ المدافعون في إنشاء عمليات كشف لسلوك "الشوكة والتشغيل" الخاص بتجميع التنفيذ، أي حقن DLL العاكس. لتجاوز ذلك، طور شون جونز، أيضا من فريق IBM Adversary Simulation، ملف كائن منارة التجميع (BOF) لعملية التنفيذ الداخلي . وقد سمح ذلك للمشغلين بالانتقال بعيدًا عن سلوك "التفرّع والتشغيل" الخاص بتجميع التنفيذ والبقاء ضمن عملية الزرع. منذ ذلك الحين، تبنت العديد من إطارات عمل C2 هذا السلوك بشكل أصلي.
إذا لم تكن على دراية مسبقا بكيفية استضافة CLR وتنفيذ تجميعات .NET، أنصحك بقراءة منشور المدونة المتوفر رابط له أعلاه.
تستفيد تقنية التجميع المنفذ من ميزة Windows تعرف باسم “استضافة CLR غير المدارة“. وقت التنفيذ المشترك للغات، أو CLR، هو وقت التشغيل لـ .NET. يمكن للمستخدمين كتابة تجميعات .NET بلغات متنوعة (C#، F#، إلخ) والتي يتم تجميعها إلى لغة وسيطة (IL). يكون CLR مسؤولاً عن أخذ التجميع الذي يحتوي على IL وتنفيذه.
تقليديًا، اعتمدت تقنية execute-assembly على استخدام الواجهة . ومن خلال التحميل من مصفوفة بايت، نتجنب الحاجة إلى كتابة أي تعليمات برمجية على نظام الملفات، الأمر الذي قد تلتقطه حلول الحماية عند الفحص.
وقد تم استبدال ومنذ ذلك الحين بالواجهة.
الشكل 1: صفحة MSDN لواجهة ICLRuntimeHost
تذكر وثائق MSDN للواجهة
ملاحظة: رغم أننا لا نستطيع استخدام
تتيح لنا الدالة تقديم تنفيذنا الخاص لواجهة COM ، وبهذه الطريقة يمكننا إخبار CLR باستخدام ميزات التخصيص المختلفة. تخصيصات CLR ميزة قلما يتم تناولها، وتتيح للمطورين التحكم في جوانب من CLR. تعمل التخصيصات عبر استخدام واجهات "Manager" متعددة يمكننا نحن المطورين تنفيذها، ويحدد تنفيذنا المديرين الذين نريد من CLR استخدامهم. وأي شيء لا ننفذه سيتولى CLR التعامل معه بالطريقة المعتادة. ترد أدناه قائمة بالمديرين المدعومين.
الشكل 2: بعض الواجهات المدعومة لتخصيصات CLR
استخدمت مربعات حمراء لتمييز المديرين اللذين سيتناولهما هذا المنشور:
كتبت إثبات المفهوم الأولي لتخصيصات CLR بلغة C++، لكنني اخترت في النهاية إعادة تنفيذ كل شيء بلغة C الخالصة، وهذا ما سنتناوله في هذا المنشور. أفضل المكوّنات البرمجية المكتوبة بلغة C لتجنب تضخم C++، لذلك أردت أن تكون أداة تنفيذ التجميع مكتوبة بلغة C أيضًا. تنفيذ واجهات COM في C مهمة شاقة للغاية، لكنني آمل أن تجعل المعلومات التالية الأمر أسهل لاحقًا. فيما يلي كيفية تحديد واجهة، والتي سميتها "MyHostControl".
الشكل 3: ملف الرأس الذي يقوم بتنفيذ واجهة IHostControl
لتنفيذ واجهة COM الخاصة بنا، يجب أن يكون لدينا العناصر التالية (الموضحة أعلاه بهذا الترتيب):
يعد تنفيذ الطرق الفعلية أكثر وضوحًا، كما هو موضح أدناه:
الشكل 4: تنفيذ طرق QueryInterface وAddRef وRelease
وكما ذكرت سابقاً، فإن دوالّ
الشكل 5: تنفيذ طريقة GETHostManager
هنا لا نحتاج فعليًا إلى تنفيذ الدالة
الآن بعد أن قمنا بتنفيذ واجهة الخاصة بنا ، يمكننا استدعاء وبدء تشغيل CLR. فيما يلي مقتطف من التعليمات البرمجية لتنفيذ مهام استضافة CLR المعتادة (, , )، ثم استدعاء باستخدام واجهة تحكم المضيف المخصصة لدينا. ثم نبدأ عملية CLR.
الشكل 6: استدعاء SetHostControl وبدء تشغيل CLR
بعد أن عرفنا كيفية تنفيذ واجهات COM في C، يصبح تنفيذ مديرين محددين أكثر سهولة. تتيح لنا الواجهة التحكم في إدارة ذاكرة CLR. فيما يلي قائمة بجميع الدوال التي نحتاج إلى تنفيذها من أجل .
الشكل 7: قائمة طرق واجهة IHostMemoryManager
ربما تلاحظ بعض الطرق التي تتيح بعض السلوكيات المثيرة للاهتمام، وهي VirtualAlloc و VirtualProtect و VirtualQuery و VirtualFire) التي تُستخدم لإدارة الجزء الأكبر من الذاكرة على نظام التشغيل Windows. فيما يلي تطبيق بسيط جداً لهذه الطرق.
الشكل 8: تنفيذ VirtualAlloc و Virtual Free و VirtualQuery و VirtualProtect
إن التحكم في تخصيصات الذاكرة يمكّن المشغل توسيع أساليب التنفيذ كما يشاء. فعلى سبيل المثال، يمكنك تنفيذ استدعاءات واجهات برمجة التطبيقات (API) الخاصة بالتخصيص عبر استدعاءات نظام غير مباشرة (indirect syscalls). ويمكنك أيضًا تتبّع جميع عمليات التخصيص التي يُجريها CLR وتشفيرها عندما ينتقل المكوّن البرمجي (implant) إلى وضع السكون. لاحظ أن تشفير عمليات تخصيص وقت التشغيل العام للغات (CLR) ليس مستقرًا للغاية. بالإضافة إلى الدوال الافتراضية*، توجد أيضًا دالّة
لقد ذكرت أعلاه أن محاولة تشفير عمليات تخصيص ذاكرة CLR تنتقل من "نوع من عدم الاستقرار" إلى "غير مستقر للغاية"، اعتمادًا على كيفية قيامك بذلك بالضبط. وذلك لأنه إذا قمت بتشفير أو تحرير جزء من الذاكرة الذي يحاول CLR الرجوع إليه لاحقًا، فسوف تتسبب CLR في حدوث خطأ وتعطل عمليتك. ومع ذلك، هناك استثناء واحد لهذا الذي وجدته: المخصصات التي تمت أثناء أحمال التجميع الأولية.
عند تحميل تجميع (assembly)، سواء من الذاكرة أو من القرص، يقوم CLR بحجز مساحة ثم يعمد إلى ربط التجميع بالذاكرة. وحسب علمي، لم تكن هناك طريقة عامة موثوقة لتحديد منطقة الذاكرة هذه ومسحها، باستثناء البحث في ذاكرة العملية عن أنماط بايت أو عن تخصيصات بحجم متوقع. وتوفر تخصيصات CLR آلية سهلة لتتبّع هذه التخصيصات عبر الدالة . وهذه الدالة عبارة عن دالة استدعاء لاحق (callback) للإشعار يتم تشغيله كلما قام CLR بتحميل تجميع (assembly) داخل العملية، ويتضمن callback عنوان التخصيص وحجمه كوسيطين. ووفقًا للاختبار الذي أجريته، لا يتم تشغيل دالة الاستدعاء اللاحق (callback) هذه إلا عند تحميل تجميع داخل العملية، ما يوفر لنا طريقة جيدة لتتبع تخصيصات أحمال التجميع. ولتعزيز الاعتمادية، يمكنك التحقق من الحجم أو تحليل الذاكرة للتأكد من أن هذا هو التجميع (assembly) المقصود. فيما يلي مثال على كيفية تنفيذ هذه الدالة. وبما أنها مجرد دالة استدعاء لاحق (callback) للإشعار، يمكنك تنفيذ ما تشاء داخلها ثم إرجاع S_OK ببساطة..
الشكل 9: تنفيذ طريقة AcquedVirtualAddressSpace
على عكس التخصيصات الأخرى التي قام بها CLR، لم أواجه أي مشكلة في تشفير منطقة الذاكرة أو مسحها بعد انتهاء التجميع. قد تواجهك مشاكل إذا حاولت تنفيذ نفس التجميع في نفس مجال التطبيق مرة أخرى لأن CLR قد يحاول استخدام التجميع المخزن مؤقتًا والذي أصبح الآن غير صالح. ستقوم معظم تنفيذات execute-assembly بإنشاء نطاق تطبيق جديد ثم تدميره بعد التنفيذ، لذا تأكد من اختبار التنفيذ الخاص بك.
تحتوي وظيفة الإعلام هذه أيضًا على تطبيق دفاعي بسيط. عادةً ما تستخدم المنتجات الدفاعية تتبع الأحداث لـ Windows (ETW) لتتبع أحمال التجميع في CLR، ولكن هذا يوفر طريقة أخرى للإشعار إذا تم تحميل تجميع في العملية. نظرًا لتضمين عنوان الذاكرة وحجمها، سيكون من السهل على المنتج الدفاعي إجراء مسح للذاكرة على تلك المنطقة.
أما المديرون الآخرون الذين سننظر فيهم فهم و . يتولى مهمتين: تزويد CLR بقائمة ملفات التجميع (assemblies) التي ينبغي أن يتولى تحميلها بنفسه (بدلًا منا بصفتنا مضيف CLR)، وإرجاع واجهة إلى CLR. تتضمن دالتَين هما: و .
تُستدعى كلما طُلب من CLR تحميل تجميع (assembly) غير موجود ضمن قائمة ملفات التجميع (assemblies) التي يكون CLR مسؤولًا عن تحميلها (والتي يتم إرجاعها بواسطة ). يستدعي CLR الدالّة ويمرر سلسلة الهوية الخاصة بالتجميع، وتكون مسؤولة عن إرجاع البايتات الخاصة بالتجميع. من المرجح أنك صادفت سلسلة الهوية من قبل؛ وتكون على نحو يشبه: "".
وبعد أن تقوم بحل التجميع، يتم إرجاع محتوى التجميع عبر ضبط مؤشر يُمرَّر كمتغيّر. وقد أبرزتُ المتغيّر ذي الصلة، ، في لقطة الشاشة أدناه.
الشكل 10: الوسيطات الخاصة بطريقة توفير التجميع (ProvideAssembly) لواجهة IHostAssemblyStore
يتم إرجاع التجميع عن طريق تعيين المؤشر على عنوان IStream في الذاكرة. عادةً ما تحاول CLR تحديد موقع التجميع عن طريق اتباع ترتيب البحث في الدليل على القرص، على غرار تحميل مكتبة الارتباط الديناميكي في عملية Windows العادية. ولكن نظرًا لأننا نستطيع توفير التنفيذ الخاص بنا، فيمكننا تلقي طلب تجميع يمكن تحميله عادةً من القرص وتقديم بدلاً من ذلك تجميعًا لدينا في الذاكرة. يجب أن تكون وحدات البايت المجمّعة في IStream، وهو ما يمكن تحقيقه باستخدام الدالة SHCreateMemStream التي تأخذ مصفوفة بايت وتُرجع IStream.
قد تتساءل عن سبب أهمية هذا الأمر إذا كانت هذه طريقة أخرى لتحميل التجميعات في الذاكرة. ماذا عن واجهة فحص مضاد البرامج الضارة (AMSI)؟
AMSI مسؤولة عن فحص أي تجميعات يتم تحميلها بشكل عكسي بحثًا عن المحتوى الضار. يستخدم Windows Defender AMSI، ويتيح AMSI أيضًا لحلول اكتشاف نقاط النهاية والاستجابة لها الأخرى ربط وفحص محتويات التجميعات التي تم تحميلها في الذاكرة. يميل البعض إلى السخرية من AMSI نظرًا لإمكانية تجاوزها، لكنني أشعر أنه بالنسبة لشيء مثبت في Windows افتراضيًا، فإن AMSI ميزة أمان فعالة إلى حد ما. كحد أدنى، سيكشف الكثير من أدوات .NET الخبيثة (مثل Seatbelt) التي يتم تنفيذها في الذاكرة. هناك تاريخ طويل من القطة والفأر لتجاوز AMSI بين أعضاء الفريق الأحمر، ولكن الكثير من تجاوزات AMSI تعتمد على تصحيح البايتات الخاصة بالوظائف الرئيسية (مثل AmsiScanBuffer) بحيث تفشل في التنفيذ أو ترجع قيمة “good”. تعد تجاوزات AMSI التقليدية فوضوية لأنها تترك وحدات بايت Copy-on-Write في قسم .text من ذاكرة AMSI.dll، وهي هدية ميتة لأي مدافعين ينظرون إلى عملية مشبوهة. كما أن تجاوزات AMSI الأكثر تطورًا مثل خطافات نقاط التوقف للأجهزة لها أيضًا بطاقات IOC أخرى مرتبطة بها، مثل فحص سياق سلسلة المحادثات للبحث عن استخدام سجل تصحيح الأخطاء.
من خلال تنفيذ نسختنا الخاصة من الدالة يمكننا التحايل على AMSI بالكامل. تقليديًا، تُستخدم الدالة لتحميل ملفات التجميع (assemblies) من مصفوفة بايت، وتخضع الدالة لآلية instrumentation من AMSI. لكن هل تعلم أن هناك دوال Load_* أخرى نادرة الاستخدام؟
الشكل 11: عائلة طرق Load
الآن الجزء الأهم: لأننا نستدعي
يوضح المثال التالي تنفيذ Seatbelt عبر استدعاء
الشكل 12: تنفيذ Seatbelt وتجاوز AMSI
الشكل 13: وحدة نمطية للعمليات مدرجة في القائمة ولم يتم تحميل AMSI.dx بها
وفقا لمقال CLR Inside Out نشر في مجلة MSDN بتاريخ أغسطس 2006، فقد استخدمت Microsoft نفسها تقنية مشابهة لجعل SQL Server 2005 يقوم بتحميل تجميعات .NET من قاعدة البيانات بدلاً من القرص. القدرة على تخزين وتنفيذ تجميعات .NET من قاعدة بيانات SQL هي تقنية الحركة الجانبية المفضلة لدي ويمكن تنفيذها بسهولة باستخدام SQLRecon، وهي أداة أخرى من ®X-Force كتبها Sanjiv Kawa.
أنشر إثبات مفهوم لهذه التقنية يمكنك العثور عليه على GitHub هنا. يوضح إثبات صحة المفهوم هذا كيفية تنفيذ الواجهات , و, و, و و. يقوم باستدعاء ويشغّل CLR باستخدام واجهة .
تنفيذ مدير الذاكرة يستدعي ببساطة واجهات برمجة تطبيقات Windows الصحيحة (مثل VirtualAlloc)، لكنه يتضمن مثالًا على تتبع جميع تخصيصات الذاكرة التي يجريها CLR. كما يتضمن مثالًا على مسح آثار تحميل التجميع التي توفرها دالة الاستدعاء اللاحق
التي نوقشت سابقًا.
يوجد أيضًا إثبات مفهوم كامل لتجاوز AMSI. هناك تنبيه يتعلق بهذا التجاوز إذا حاولت إدماجه في أدواتك الخاصة: يجب أن تتطابق هوية التجميع التي تحاول تحميلها باستخدام دالة مع معرّف التجميع الذي تُعيده في النهاية إلى CLR. على سبيل المثال، إذا استدعيت بالمتغيّر "Seatbelt, Version=0.0.0.0, PublicKeyToken=null, Culture=neutral"، فيجب أن يحمل التجميع الذي تحاول تشغيله في النهاية المعرّف نفسه أيضًا. لا يمكنك محاولة تحميل mscorlib ثم إعادة Seatbelt بدلًا من ذلك، لأن CLR سيتحقق من الأمر وسيُطلق استثناء. انتبه لأسماء التجميعات التي تحاول تحميلها. لكن إذا كنت ما زلت تحاول تحميل تجميع باسم Seatbelt على نحو انعكاسي في أي عام تقرأ فيه هذا، فأقترح أن تغلق هذا المنشور وتبحث عن نشاط أكثر إرضاءً.
في إثبات المفهوم، أستخدم دالة ضمن واجهة للحصول على سلسلة الهوية للتجميع الذي سيجري تنفيذه. يمكنك أيضًا نقل هذه الخطوة بعيدًا عن المكوّن البرمجي (implant) والحصول على هوية التجميع على العميل أو خادم الفريق، ثم تمرير سلسلة المعرّف في صورة متغيّر إلى المكوّن البرمجي (implant).
على الرغم من أن هذا يعد تجاوزًا جديدًا لـ AMSI، إلا أنه في النهاية يكون مجرد: تجاوز AMSI. يجب أن تستخدم المنتجات الدفاعية الاستراتيجية دفاعًا عميقًا وليس الاكتفاء بالاعتماد على AMSI لكشف التجمعات الخبيثة. ستؤدي أي تجميعات يتم تحميلها باستخدام هذه التقنية أيضًا إلى إنشاء أحداث Event Tracing for Windows (ETW) مثل أي تجميع آخر داخل الذاكرة. يمكن اكتشاف التجميعات الخبيثة عبر مسح الذاكرة، كما رأينا عدة منصات اكتشاف نقاط النهاية والاستجابة لها متقدمة تفعل ذلك. تتمتع العديد من أدوات ما بعد الاستغلال أيضًا بمؤشرات تشغيل فريدة خاصة بها.
كما ذكرنا أعلاه، هناك أيضًا بعض التطبيقات الدفاعية لهذا البحث. توفّر دالة الاستدعاء اللاحق أسلوبًا إضافيًا للإخطار عند تحميل ملفات التجميع (assemblies) داخل العملية. وإذا قام مدافع بتنفيذ واجهة ، فسيُدرج ضمن سلسلة تحميل ملفات التجميع، وسيكون لديه القدرة على منع تحميل التجميعات بالكامل، أو تعديل بايتات التجميع قبل تحميله داخل العملية. وسأقولها صراحة: أرى أنه من المرجح جدًا أن يشهد هذا المجال تطورات لاحقة.
أود أن أتطرق إلى جدولنا الزمني مع هذا البحث، ولماذا ننشره الآن. أجريت كل هذا البحث في يونيو 2023 ومنذ ذلك الحين أبقينا الأمر داخلياً، رغم أنه تم إرساله إلى عدة مؤتمرات في شكل طلب عروض تقديمية. في الوقت الذي أجريت فيه هذا البحث، بحثت في محركات البحث عن أي شيء مشابه، بحثًا عن مادة مرجعية في البداية وسعيًا لاحقًا للتأكد مما إذا كنت أول من حدد هذا السلوك. ومنذ ذلك الحين، أجريت عمليات بحث دورية لمعرفة ما إذا كان أي شخص قد نشر أي عمل مماثل. في بداية يناير 2025، وجدت هذا المقال: استخدام استضافة CLR للتهرب من AMSI بقلم Marcos González Hermida في مجلة "NTT Data".
كشف هذا المقال التجاوز نفسه، ووفقًا للمجلة نُشر أصلًا في يونيو 2024 (أما الملحق المشار إليه أعلاه فهو من يوليو 2024). وهو مقال مكتوب بإتقان، وأوصي بقراءته. والملاحظة الوحيدة لدي أن الكاتب يخلص إلى أن ملفات التجميع (assemblies) يجب أن تكون موقعة لكي تُنفّذ، وفي إثبات المفهوم الذي قدّمه يستخدم الدالة لاستخراج الدالة Main يدويًا من التجميع الذي يقوم بتحميله (Rubeus ، على وجه التحديد). ولم أواجه أي مشكلة في تحميل ملفات تجميع غير موقعة بهذه التقنية. ويمكنك استخدام الدالة نفسها للحصول على نقطة الدخول (entry point) للتجميع المحمّل، وهي الطريقة المستخدمة في كثير من تطبيقات execute-assembly، دون الحاجة إلى معرفة أسماء namespace أو class.
مع نشر مقال Marcos وإثبات المفهوم، يمكن الآن اعتبار تجاوز AMSI هذا علنيًا، لذلك قررنا أن الوقت قد حان لنشر البحث مع إثبات المفهوم لما اكتشفناه.
في هذا المنشور، نظرنا في كيفية استخدام تخصيصات CLR لتحسين OPSEC أثناء تشغيل أدوات .NET في الذاكرة. بالإضافة إلى ذلك، عرضنا تجاوز كامل لنظام AMSI باستخدام هذه الميزات الأقل شهرة CLR. سيظل استخدام أدوات .NET فعالاً بالنسبة للممارسين الهجوميين وعناصر التهديد. لهذا السبب، من الضروري أن يفهم المدافعون كيفية عمل CLR ويبنوا استراتيجية حساسة لاكتشاف أدوات ما بعد الاستغلال.
شكرًا لكما Brett و Valentina على مراجعة الأقران لهذا البحث!
التعامل مع الفشل: سياسة تصعيد الفشل في مضيفات CLR - هذا هو المثال الحقيقي الوحيد الذي استطعت العثور عليه من التخصيصات الهجومية باستخدام تخصيصات CLR عندما كنت أقوم بهذا البحث في البداية.
Hosted Pumpkin – مستودع GitHub يحتوي على دليل مفهوم لتنفيذ العديد من تخصيصات CLR.
Shellcode: تحميل تجميعات .NET من الذاكرة – كان Donut بمثابة مساعدة كبيرة في التعامل مع جميع هياكل البيانات والتعريفات ذات الصلة في C.
كتاب Customizing the Microsoft .NET Framework Common Language Runtime للمؤلف Steven Pratschner – يُعد هذا العمل هو المرجع الحاسم لموضوع تخصيصات وقت تشغيل اللغة المشتركة (CLR). يجب عليك قراءة هذا الكتاب إذا كنت مهتمًا بهذا المجال.