أن تكون مضيفًا جيدًا لوقت تشغيل اللغة المشتركة (CLR) – تحديث الأساليب الهجومية لـ .NET

امرأة تكتب على الكمبيوتر داخل غرفة مظلمة

يتم تعريف الفريق الأحمر الحديث من خلال قدرته على اختراق نقاط النهاية واتخاذ الإجراءات لإكمال الأهداف. ولتحقيق الهدف الأول، تقوم العديد من الفرق بتنفيذ خيار الأوامر والتحكم (C2) المخصص لها أو استخدام خيار مصدر مفتوح. بالنسبة للأخير، هناك تدفق مستمر من أدوات ما بعد الاستغلال التي تستفيد من ميزة مختلفة في Windows وActive Directory وتطبيقات الجهات الخارجية. وقد اعتمدت آلية تنفيذ هذه الأدوات، على مدار السنوات العديدة الماضية، اعتمادًا كبيرًا على تنفيذ تجميعات .NET في الذاكرة.

على الرغم من كونها جزءًا كبيرًا من ترسانة الفريق الأحمر الحديثة، فإن الأساليب المستخدمة في تنفيذ تجميعات .NET على نقطة نهاية معرضة للخطر ظلت راكدة إلى حد كبير. في منشور المدونة هذا، سنناقش كيف يمكن للفرق الحمراء إدخال أدوات تنفيذ .NET الخاصة بها إلى هذا العقد.

النقاط البارزة الرئيسية

  • يمكن للمشغلين التحكم في العديد من جوانب CLR باستخدام "تخصيصات CLR" عند تنفيذ تجميعات .NET في الذاكرة
  • يتيح تولي إدارة الذاكرة لـ CLR للمشغلين التحكم في جميع التخصيصات التي أجرتها CLR وتتبعها، كما يوفر طريقة سهلة لتتبع التجميعات التي يتم تحميلها في العملية
  • يتيح تنفيذ مدير تحميل تجميع مخصص إمكانية تجاوز AMSI جديدة باستخدام الوظيفة "المقصودة" فقط، دون الحاجة إلى تصحيحات البايت أو اختراق العمليات

تاريخ موجز لتنفيذ التجميعات

منذ فترة ليست ببعيدة، اعتمدت العديد من الفرق الحمراء على PowerShell لأدوات ما بعد الاستغلال. اتخذت Cobalt Strike خطوة لتغيير ذلك في عام 2018 من خلال تنفيذ وحدة تنفيذ التجميع (execute-assembly). كان تنفيذ التجميع يؤدي إلى إنشاء عملية تضحية وحقن ملف DLL عاكس فيها، والذي كان يحمِّل وقت تشغيل اللغة المشتركة (CLR) وينفذ تجميع .NET المقدم بواسطة المشغل.

أدى ذلك إلى نقل الكثير من أدوات ما بعد الاستغلال إلى تجميعات .NET. بعد فترة من الزمن، بدأ المدافعون في إنشاء عمليات كشف لسلوك "الشوكة والتشغيل" الخاص بتجميع التنفيذ، أي حقن DLL العاكس. لتجاوز ذلك، طور شون جونز، أيضا من فريق IBM Adversary Simulation، ملف كائن منارة التجميع (BOF) لعملية التنفيذ الداخلي . وقد سمح ذلك للمشغلين بالانتقال بعيدًا عن سلوك "التفرّع والتشغيل" الخاص بتجميع التنفيذ والبقاء ضمن عملية الزرع. منذ ذلك الحين، تبنت العديد من إطارات عمل C2 هذا السلوك بشكل أصلي.

إذا لم تكن على دراية مسبقا بكيفية استضافة CLR وتنفيذ تجميعات .NET، أنصحك بقراءة منشور المدونة المتوفر رابط له أعلاه.

Mixture of Experts | 12 ديسمبر، الحلقة 85

فك تشفير الذكاء الاصطناعي: تقرير إخباري أسبوعي

انضمّ إلى نخبة من المهندسين والباحثين وقادة المنتجات وغيرهم من الخبراء وهم يقدّمون أحدث الأخبار والرؤى حول الذكاء الاصطناعي، بعيدًا عن الضجيج الإعلامي.

كيفية استضافة وقت تشغيل اللغة المشتركة

تستفيد تقنية التجميع المنفذ من ميزة Windows تعرف باسم “استضافة CLR غير المدارة“. وقت التنفيذ المشترك للغات، أو CLR، هو وقت التشغيل لـ .NET. يمكن للمستخدمين كتابة تجميعات .NET بلغات متنوعة (C#، F#، إلخ) والتي يتم تجميعها إلى لغة وسيطة (IL). يكون CLR مسؤولاً عن أخذ التجميع الذي يحتوي على IL وتنفيذه.

تقليديًا، اعتمدت تقنية execute-assembly على استخدام الواجهة ICorRuntimeHost التي تم إيقافها (deprecated). ويستخدم المختصون في الجانب الهجومي هذه الواجهة لأنها تتيح تحميل assemblies من مصفوفة بايت موجودة في الذاكرة، عبر إنشاء App Domain واستخدام الدالة Load_3 . ومن خلال التحميل من مصفوفة بايت، نتجنب الحاجة إلى كتابة أي تعليمات برمجية على نظام الملفات، الأمر الذي قد تلتقطه حلول الحماية عند الفحص.

وقد تم استبدال ICorRuntimeHost ومنذ ذلك الحين بالواجهةICLRRuntimeHost .

صفحة MSDN لواجهة ICLRuntimeHost

الشكل 1: صفحة MSDN لواجهة ICLRuntimeHost

تذكر وثائق MSDN للواجهة ICLRRuntimeHost أنها تضيف دالّة SetHostControl ، ولكنها تستبعد بعض الدوالّ التي توفرها ICorRuntimeHost . وعندما تقول Microsoft "استبعاد بعض الدوال"، فهي تقصد عمليًا الدوال المهمة التي تتيح تحميل تجميعات (assemblies) بأسلوب شانعكاسي (reflective). وفي المقابل، نحصل على إمكانية الوصول إلى تخصيصات CLR عبر دالّة SetHostControl.

ملاحظة: رغم أننا لا نستطيع استخدام ICLRRuntimeHost مباشرةً لتحميل التجميعات (assemblies) انعكاسيًا، يمكننا بدء تشغيل CLR باستخدام ICLRRuntimeHost ثم استدعاء GetInterface للحصول على واجهة ICorRuntimeHost . ثم يمكننا استخدام واجهة ICorRuntimeHost لتحميل التجميعات انعكاسيًا مع إبقاء تخصيصات CLR مفعّلة في الوقت نفسه.

ما هي تخصيصات CLR؟

تتيح لنا الدالة SetHostControl تقديم تنفيذنا الخاص لواجهة COM IHostControl، وبهذه الطريقة يمكننا إخبار CLR باستخدام ميزات التخصيص المختلفة. تخصيصات CLR ميزة قلما يتم تناولها، وتتيح للمطورين التحكم في جوانب من CLR. تعمل التخصيصات عبر استخدام واجهات "Manager" متعددة يمكننا نحن المطورين تنفيذها، ويحدد تنفيذنا IHostControl المديرين الذين نريد من CLR استخدامهم. وأي شيء لا ننفذه سيتولى CLR التعامل معه بالطريقة المعتادة. ترد أدناه قائمة بالمديرين المدعومين.

بعض الواجهات المدعومة لتخصيصات CLR

الشكل 2: بعض الواجهات المدعومة لتخصيصات CLR

استخدمت مربعات حمراء لتمييز المديرين اللذين سيتناولهما هذا المنشور: IHostMemoryManager و IHostAssemblyManager . لكن أولاً ، سننفذ واجهة IHostControl الخاصة بنا.

تنفيذ IHostControl

كتبت إثبات المفهوم الأولي لتخصيصات CLR بلغة C++، لكنني اخترت في النهاية إعادة تنفيذ كل شيء بلغة C الخالصة، وهذا ما سنتناوله في هذا المنشور. أفضل المكوّنات البرمجية المكتوبة بلغة C لتجنب تضخم C++، لذلك أردت أن تكون أداة تنفيذ التجميع مكتوبة بلغة C أيضًا. تنفيذ واجهات COM في C مهمة شاقة للغاية، لكنني آمل أن تجعل المعلومات التالية الأمر أسهل لاحقًا. فيما يلي كيفية تحديد واجهة، والتي سميتها IHostControl "MyHostControl".

ملف الرأس الذي يقوم بتنفيذ واجهة IHostControl

الشكل 3: ملف الرأس الذي يقوم بتنفيذ واجهة IHostControl

لتنفيذ واجهة COM الخاصة بنا، يجب أن يكون لدينا العناصر التالية (الموضحة أعلاه بهذا الترتيب):

  1. تعريف نوع (typedef) لبنية (struct) تتضمن دوال واجهتنا. الدوال QueryInterface, و AddRef، و Release الوظائف دوال قالبية جاهزة (boilerplate) وتوجد في كل واجهة COM. الدالتان أدناه، GetHostManager و SetAppDomainManager، خاصتان بهذه الواجهة.
  2. نوع تعريفي للهيكل الذي يحدد واجهتنا الفعلية، والتي تحتوي على جدول افتراضي (VTBL) وعدد.
  3. تعريفات الدوال المخصصة التي سننفذها على نحو منفصل. وقد سبقتُ أسماءها بالبادئة "MyHostControl_" لأنك ستحتاج إلى تعريف QueryInterface /AddRef /Release لكل واجهة COM.
  4. ثابت من VTBL الذي قمنا بتعريفه سابقًا ولكن تم ملؤه بالوظائف التي حددناها أعلاه.

يعد تنفيذ الطرق الفعلية أكثر وضوحًا، كما هو موضح أدناه:

تنفيذ طرق QueryInterface وAddRef وRelease

الشكل 4: تنفيذ طرق QueryInterface وAddRef وRelease

وكما ذكرت سابقاً، فإن دوالّQueryInterface /AddRef /Release هي دوال قالبية (boilerplate). والشيء الوحيد الذي يلزم تغييره لتطبيق واجهة مختلفة هو قيمة "xIID_IHostControl" في دالّة QueryInterface.

تنفيذ طريقة GETHostManager

الشكل 5: تنفيذ طريقة GETHostManager

هنا لا نحتاج فعليًا إلى تنفيذ الدالة SetAppDomainManager، ويمكننا ببساطة إرجاع E_NOTIMPL، ما دمنا لن نحاول استدعاءها لاحقًا. الدالة GetHostManager، وهي جوهر هذه الواجهة، ليست سوى سلسلة من عبارات "if" نتحقق من خلالها مما إذا كان CLR يطلب منا مديرًا يهمنا. في الكود أعلاه، أتحقق مما إذا كان IID الممرَّر يخص واجهة IHostMemoryManager، ثم أنشئ مدير ذاكرة جديدًا وأجعل المتغير ppObject يشير إليه.

بدء تشغيل CLR

الآن بعد أن قمنا بتنفيذ واجهة IHostControl الخاصة بنا ، يمكننا استدعاء SetHostControl وبدء تشغيل CLR. فيما يلي مقتطف من التعليمات البرمجية لتنفيذ مهام استضافة CLR المعتادة (CLRCreateInstance , GetRuntime , GetInterface )، ثم استدعاء SetHostControl باستخدام واجهة تحكم المضيف المخصصة لدينا. ثم نبدأ عملية CLR.

استدعاء SetHostControl وبدء تشغيل CLR

الشكل 6: استدعاء SetHostControl وبدء تشغيل CLR

الاستحواذ على ذاكرة CLR

بعد أن عرفنا كيفية تنفيذ واجهات COM في C، يصبح تنفيذ مديرين محددين أكثر سهولة. تتيح لنا الواجهة IHostMemoryManager التحكم في إدارة ذاكرة CLR. فيما يلي قائمة بجميع الدوال التي نحتاج إلى تنفيذها من أجل IHostMemoryManager .

قائمة طرق واجهة IHostMemoryManager

الشكل 7: قائمة طرق واجهة IHostMemoryManager

ربما تلاحظ بعض الطرق التي تتيح بعض السلوكيات المثيرة للاهتمام، وهي VirtualAlloc و VirtualProtect و VirtualQuery و VirtualFire) التي تُستخدم لإدارة الجزء الأكبر من الذاكرة على نظام التشغيل Windows. فيما يلي تطبيق بسيط جداً لهذه الطرق.

تنفيذ VirtualAlloc و VirtualQuery و VirtualQuery و VirtualProtect

الشكل 8: تنفيذ VirtualAlloc و Virtual Free و VirtualQuery و VirtualProtect

إن التحكم في تخصيصات الذاكرة يمكّن المشغل توسيع أساليب التنفيذ كما يشاء. فعلى سبيل المثال، يمكنك تنفيذ استدعاءات واجهات برمجة التطبيقات (API) الخاصة بالتخصيص عبر استدعاءات نظام غير مباشرة (indirect syscalls). ويمكنك أيضًا تتبّع جميع عمليات التخصيص التي يُجريها CLR وتشفيرها عندما ينتقل المكوّن البرمجي (implant) إلى وضع السكون. لاحظ أن تشفير عمليات تخصيص وقت التشغيل العام للغات (CLR) ليس مستقرًا للغاية. بالإضافة إلى الدوال الافتراضية*، توجد أيضًا دالّة CreateMAlloc التي تُعيد تنفيذًا للواجهة IHostMalloc. وتتيح لنا هذه الواجهة التحكم في جميع عمليات تخصيص Heap التي يُجريها CLR.

تتبع عيوب التجميع وإزالتها

لقد ذكرت أعلاه أن محاولة تشفير عمليات تخصيص ذاكرة CLR تنتقل من "نوع من عدم الاستقرار" إلى "غير مستقر للغاية"، اعتمادًا على كيفية قيامك بذلك بالضبط. وذلك لأنه إذا قمت بتشفير أو تحرير جزء من الذاكرة الذي يحاول CLR الرجوع إليه لاحقًا، فسوف تتسبب CLR في حدوث خطأ وتعطل عمليتك. ومع ذلك، هناك استثناء واحد لهذا الذي وجدته: المخصصات التي تمت أثناء أحمال التجميع الأولية.

عند تحميل تجميع (assembly)، سواء من الذاكرة أو من القرص، يقوم CLR بحجز مساحة ثم يعمد إلى ربط التجميع بالذاكرة. وحسب علمي، لم تكن هناك طريقة عامة موثوقة لتحديد منطقة الذاكرة هذه ومسحها، باستثناء البحث في ذاكرة العملية عن أنماط بايت أو عن تخصيصات بحجم متوقع. وتوفر تخصيصات CLR آلية سهلة لتتبّع هذه التخصيصات عبر الدالة AcquiredVirtualAddressSpace . وهذه الدالة عبارة عن دالة استدعاء لاحق (callback) للإشعار يتم تشغيله كلما قام CLR بتحميل تجميع (assembly) داخل العملية، ويتضمن callback عنوان التخصيص وحجمه كوسيطين. ووفقًا للاختبار الذي أجريته، لا يتم تشغيل دالة الاستدعاء اللاحق (callback) هذه إلا عند تحميل تجميع داخل العملية، ما يوفر لنا طريقة جيدة لتتبع تخصيصات أحمال التجميع. ولتعزيز الاعتمادية، يمكنك التحقق من الحجم أو تحليل الذاكرة للتأكد من أن هذا هو التجميع (assembly) المقصود. فيما يلي مثال على كيفية تنفيذ هذه الدالة. وبما أنها مجرد دالة استدعاء لاحق (callback) للإشعار، يمكنك تنفيذ ما تشاء داخلها ثم إرجاع S_OK ببساطة..

تنفيذ طريقة AcquireVirtualAddressSpace

الشكل 9: تنفيذ طريقة AcquedVirtualAddressSpace

على عكس التخصيصات الأخرى التي قام بها CLR، لم أواجه أي مشكلة في تشفير منطقة الذاكرة أو مسحها بعد انتهاء التجميع. قد تواجهك مشاكل إذا حاولت تنفيذ نفس التجميع في نفس مجال التطبيق مرة أخرى لأن CLR قد يحاول استخدام التجميع المخزن مؤقتًا والذي أصبح الآن غير صالح. ستقوم معظم تنفيذات execute-assembly بإنشاء نطاق تطبيق جديد ثم تدميره بعد التنفيذ، لذا تأكد من اختبار التنفيذ الخاص بك.

تحتوي وظيفة الإعلام هذه أيضًا على تطبيق دفاعي بسيط. عادةً ما تستخدم المنتجات الدفاعية تتبع الأحداث لـ Windows (ETW) لتتبع أحمال التجميع في CLR، ولكن هذا يوفر طريقة أخرى للإشعار إذا تم تحميل تجميع في العملية. نظرًا لتضمين عنوان الذاكرة وحجمها، سيكون من السهل على المنتج الدفاعي إجراء مسح للذاكرة على تلك المنطقة.

إدارة أحمال التجميع

أما المديرون الآخرون الذين سننظر فيهم فهم IHostAssemblyManager و IHostAssemblyStore . يتولى IHostAssemblyManager مهمتين: تزويد CLR بقائمة ملفات التجميع (assemblies) التي ينبغي أن يتولى تحميلها بنفسه (بدلًا منا بصفتنا مضيف CLR)، وإرجاع واجهة IHostAssemblyStore إلى CLR. تتضمن IHostAssemblyStore دالتَين هما: ProvideAssembly و ProvideModule .

تُستدعى ProvideAssembly كلما طُلب من CLR تحميل تجميع (assembly) غير موجود ضمن قائمة ملفات التجميع (assemblies) التي يكون CLR مسؤولًا عن تحميلها (والتي يتم إرجاعها بواسطة IHostAssemblyManager ). يستدعي CLR الدالّة ProvideAssembly ويمرر سلسلة الهوية الخاصة بالتجميع، وتكون ProvideAssembly مسؤولة عن إرجاع البايتات الخاصة بالتجميع. من المرجح أنك صادفت سلسلة الهوية من قبل؛ وتكون على نحو يشبه: "Seatbelt, Version=0.0.0.0, PublicKeyToken=null, Culture=neutral ".

وبعد أن تقوم ProvideAssembly بحل التجميع، يتم إرجاع محتوى التجميع عبر ضبط مؤشر يُمرَّر كمتغيّر. وقد أبرزتُ المتغيّر ذي الصلة، ppStmAssemblyImage ، في لقطة الشاشة أدناه.

الوسيطات الخاصة بطريقة توفير التجميع (ProvideAssembly) لواجهة IHostAssemblyStore

الشكل 10: الوسيطات الخاصة بطريقة توفير التجميع (ProvideAssembly) لواجهة IHostAssemblyStore

يتم إرجاع التجميع عن طريق تعيين المؤشر على عنوان IStream في الذاكرة. عادةً ما تحاول CLR تحديد موقع التجميع عن طريق اتباع ترتيب البحث في الدليل على القرص، على غرار تحميل مكتبة الارتباط الديناميكي في عملية Windows العادية. ولكن نظرًا لأننا نستطيع توفير التنفيذ الخاص بنا، فيمكننا تلقي طلب تجميع يمكن تحميله عادةً من القرص وتقديم بدلاً من ذلك تجميعًا لدينا في الذاكرة. يجب أن تكون وحدات البايت المجمّعة في IStream، وهو ما يمكن تحقيقه باستخدام الدالة SHCreateMemStream التي تأخذ مصفوفة بايت وتُرجع IStream.

قد تتساءل عن سبب أهمية هذا الأمر إذا كانت هذه طريقة أخرى لتحميل التجميعات في الذاكرة. ماذا عن واجهة فحص مضاد البرامج الضارة (AMSI)؟

تجاوز واجهة فحص مضاد البرامج الضارة (AMSI)

AMSI مسؤولة عن فحص أي تجميعات يتم تحميلها بشكل عكسي بحثًا عن المحتوى الضار. يستخدم Windows Defender AMSI، ويتيح AMSI أيضًا لحلول اكتشاف نقاط النهاية والاستجابة لها الأخرى ربط وفحص محتويات التجميعات التي تم تحميلها في الذاكرة. يميل البعض إلى السخرية من AMSI نظرًا لإمكانية تجاوزها، لكنني أشعر أنه بالنسبة لشيء مثبت في Windows افتراضيًا، فإن AMSI ميزة أمان فعالة إلى حد ما. كحد أدنى، سيكشف الكثير من أدوات .NET الخبيثة (مثل Seatbelt) التي يتم تنفيذها في الذاكرة. هناك تاريخ طويل من القطة والفأر لتجاوز AMSI بين أعضاء الفريق الأحمر، ولكن الكثير من تجاوزات AMSI تعتمد على تصحيح البايتات الخاصة بالوظائف الرئيسية (مثل AmsiScanBuffer) بحيث تفشل في التنفيذ أو ترجع قيمة “good”. تعد تجاوزات AMSI التقليدية فوضوية لأنها تترك وحدات بايت Copy-on-Write في قسم .text من ذاكرة AMSI.dll، وهي هدية ميتة لأي مدافعين ينظرون إلى عملية مشبوهة. كما أن تجاوزات AMSI الأكثر تطورًا مثل خطافات نقاط التوقف للأجهزة لها أيضًا بطاقات IOC أخرى مرتبطة بها، مثل فحص سياق سلسلة المحادثات للبحث عن استخدام سجل تصحيح الأخطاء.

من خلال تنفيذ نسختنا الخاصة من الدالة ProvideAssembly يمكننا التحايل على AMSI بالكامل. تقليديًا، تُستخدم الدالة Load_3 لتحميل ملفات التجميع (assemblies) من مصفوفة بايت، وتخضع الدالة Load_3 لآلية instrumentation من AMSI. لكن هل تعلم أن هناك دوال Load_* أخرى نادرة الاستخدام؟

عائلة طرق Load

الشكل 11: عائلة طرق Load

Load_2 يأخذ سلسلة معرّف التجميع كمتغير بدلًا من مصفوفة بايت مثل Load_3 . عادةً ما يعني هذا أن التجميع يجب أن يكون موجودًا على القرص في موقع تستطيع CLR العثور عليه. لكننا نعلم أنه عندما يُطلب من CLR تحميل تجميع وفق الهوية، فسيطلب من تنفيذنا ProvideAssembly توفير ذلك التجميع. ونعلم أيضًا أنه يمكننا إعادة مصفوفة بايت موجودة في الذاكرة (ضمن IStream) من ProvideAssembly . وهذا يعني أنه يمكننا استدعاء Load_2 وتزويد CLR بتجميع موجود في الذاكرة ليقوم بتحميله.

الآن الجزء الأهم: لأننا نستدعي Load_2، يعتقد CLR أننا نحمّل التجميع من القرص، ولذلك لا يقوم AMSI بفحص بايتات التجميع. بل إن AMSI لا يتم تحميله داخل العملية أصلًا.

يوضح المثال التالي تنفيذ Seatbelt عبر استدعاء Load_2 من دون تحميل AMSI داخل العملية.

تنفيذ Seatbelt وتجاوز AMSI

الشكل 12: تنفيذ Seatbelt وتجاوز AMSI

وحدة نمطية للعمليات مدرجة في القائمة ولم يتم تحميل AMSI.dx بها

الشكل 13: وحدة نمطية للعمليات مدرجة في القائمة ولم يتم تحميل AMSI.dx بها

وفقا لمقال CLR Inside Out نشر في مجلة MSDN بتاريخ أغسطس 2006، فقد استخدمت Microsoft نفسها تقنية مشابهة لجعل SQL Server 2005 يقوم بتحميل تجميعات .NET من قاعدة البيانات بدلاً من القرص. القدرة على تخزين وتنفيذ تجميعات .NET من قاعدة بيانات SQL هي تقنية الحركة الجانبية المفضلة لدي ويمكن تنفيذها بسهولة باستخدام SQLRecon، وهي أداة أخرى من ®X-Force  كتبها Sanjiv Kawa.

إثبات المفهوم والتشغيل الإضافي

أنشر إثبات مفهوم لهذه التقنية يمكنك العثور عليه على GitHub هنا. يوضح إثبات صحة المفهوم هذا كيفية تنفيذ الواجهات IHostControl , وIHostMemoryManager , وIHostMalloc , وIHostAssemblyStore وIHostAssemblyManager . يقوم باستدعاء SetHostControl ويشغّل CLR باستخدام واجهة ICLRRuntimeHost .

تنفيذ مدير الذاكرة يستدعي ببساطة واجهات برمجة تطبيقات Windows الصحيحة (مثل VirtualAlloc)، لكنه يتضمن مثالًا على تتبع جميع تخصيصات الذاكرة التي يجريها CLR. كما يتضمن مثالًا على مسح آثار تحميل التجميع التي توفرها دالة الاستدعاء اللاحق AcquiredVirtualAddressSpace التي نوقشت سابقًا.

يوجد أيضًا إثبات مفهوم كامل لتجاوز AMSI. هناك تنبيه يتعلق بهذا التجاوز إذا حاولت إدماجه في أدواتك الخاصة: يجب أن تتطابق هوية التجميع التي تحاول تحميلها باستخدام دالة Load_2 مع معرّف التجميع الذي تُعيده في النهاية إلى CLR. على سبيل المثال، إذا استدعيت Load_2 بالمتغيّر "Seatbelt, Version=0.0.0.0, PublicKeyToken=null, Culture=neutral"، فيجب أن يحمل التجميع الذي تحاول تشغيله في النهاية المعرّف نفسه أيضًا. لا يمكنك محاولة تحميل mscorlib ثم إعادة Seatbelt بدلًا من ذلك، لأن CLR سيتحقق من الأمر وسيُطلق استثناء. انتبه لأسماء التجميعات التي تحاول تحميلها. لكن إذا كنت ما زلت تحاول تحميل تجميع باسم Seatbelt على نحو انعكاسي في أي عام تقرأ فيه هذا، فأقترح أن تغلق هذا المنشور وتبحث عن نشاط أكثر إرضاءً.

في إثبات المفهوم، أستخدم دالة GetBindingIdentityFromStream ضمن واجهة ICLRAssemblyIdentityManager للحصول على سلسلة الهوية للتجميع الذي سيجري تنفيذه. يمكنك أيضًا نقل هذه الخطوة بعيدًا عن المكوّن البرمجي (implant) والحصول على هوية التجميع على العميل أو خادم الفريق، ثم تمرير سلسلة المعرّف في صورة متغيّر إلى المكوّن البرمجي (implant).

الاعتبارات الدفاعية

على الرغم من أن هذا يعد تجاوزًا جديدًا لـ AMSI، إلا أنه في النهاية يكون مجرد: تجاوز AMSI. يجب أن تستخدم المنتجات الدفاعية الاستراتيجية دفاعًا عميقًا وليس الاكتفاء بالاعتماد على AMSI لكشف التجمعات الخبيثة. ستؤدي أي تجميعات يتم تحميلها باستخدام هذه التقنية أيضًا إلى إنشاء أحداث Event Tracing for Windows (ETW) مثل أي تجميع آخر داخل الذاكرة. يمكن اكتشاف التجميعات الخبيثة عبر مسح الذاكرة، كما رأينا عدة منصات اكتشاف نقاط النهاية والاستجابة لها متقدمة تفعل ذلك. تتمتع العديد من أدوات ما بعد الاستغلال أيضًا بمؤشرات تشغيل فريدة خاصة بها.

كما ذكرنا أعلاه، هناك أيضًا بعض التطبيقات الدفاعية لهذا البحث. توفّر دالة الاستدعاء اللاحق AcquiredVirtualAddressSpace أسلوبًا إضافيًا للإخطار عند تحميل ملفات التجميع (assemblies) داخل العملية. وإذا قام مدافع بتنفيذ واجهة IHostAssemblyStore ، فسيُدرج ضمن سلسلة تحميل ملفات التجميع، وسيكون لديه القدرة على منع تحميل التجميعات بالكامل، أو تعديل بايتات التجميع قبل تحميله داخل العملية. وسأقولها صراحة: أرى أنه من المرجح جدًا أن يشهد هذا المجال تطورات لاحقة.

لماذا تطلق هذا الآن؟

أود أن أتطرق إلى جدولنا الزمني مع هذا البحث، ولماذا ننشره الآن. أجريت كل هذا البحث في يونيو 2023 ومنذ ذلك الحين أبقينا الأمر داخلياً، رغم أنه تم إرساله إلى عدة مؤتمرات في شكل طلب عروض تقديمية. في الوقت الذي أجريت فيه هذا البحث، بحثت في محركات البحث عن أي شيء مشابه، بحثًا عن مادة مرجعية في البداية وسعيًا لاحقًا للتأكد مما إذا كنت أول من حدد هذا السلوك. ومنذ ذلك الحين، أجريت عمليات بحث دورية لمعرفة ما إذا كان أي شخص قد نشر أي عمل مماثل. في بداية يناير 2025، وجدت هذا المقال: استخدام استضافة CLR للتهرب من AMSI بقلم Marcos González Hermida في مجلة "NTT Data".

كشف هذا المقال التجاوز نفسه، ووفقًا للمجلة نُشر أصلًا في يونيو 2024 (أما الملحق المشار إليه أعلاه فهو من يوليو 2024). وهو مقال مكتوب بإتقان، وأوصي بقراءته. والملاحظة الوحيدة لدي أن الكاتب يخلص إلى أن ملفات التجميع (assemblies) يجب أن تكون موقعة لكي تُنفّذ، وفي إثبات المفهوم الذي قدّمه يستخدم الدالة GetType_2 لاستخراج الدالة Main يدويًا من التجميع الذي يقوم بتحميله (Rubeus ، على وجه التحديد). ولم أواجه أي مشكلة في تحميل ملفات تجميع غير موقعة بهذه التقنية. ويمكنك استخدام الدالة Entrypoint نفسها للحصول على نقطة الدخول (entry point) للتجميع المحمّل، وهي الطريقة المستخدمة في كثير من تطبيقات execute-assembly، دون الحاجة إلى معرفة أسماء namespace أو class.

مع نشر مقال Marcos وإثبات المفهوم، يمكن الآن اعتبار تجاوز AMSI هذا علنيًا، لذلك قررنا أن الوقت قد حان لنشر البحث مع إثبات المفهوم لما اكتشفناه.

الخاتمة

في هذا المنشور، نظرنا في كيفية استخدام تخصيصات CLR لتحسين OPSEC أثناء تشغيل أدوات .NET في الذاكرة. بالإضافة إلى ذلك، عرضنا تجاوز كامل لنظام AMSI باستخدام هذه الميزات الأقل شهرة CLR. سيظل استخدام أدوات .NET فعالاً بالنسبة للممارسين الهجوميين وعناصر التهديد. لهذا السبب، من الضروري أن يفهم المدافعون كيفية عمل CLR ويبنوا استراتيجية حساسة لاكتشاف أدوات ما بعد الاستغلال.

شكر وتقدير

شكرًا لكما Brett و Valentina على مراجعة الأقران لهذا البحث!

  • Brett Hawkins (@h4wkst3r)
  • Valentina Palmiotti (@chompie1337)

الأعمال ذات الصلة

التعامل مع الفشل: سياسة تصعيد الفشل في مضيفات CLR - هذا هو المثال الحقيقي الوحيد الذي استطعت العثور عليه من التخصيصات الهجومية باستخدام تخصيصات CLR عندما كنت أقوم بهذا البحث في البداية.

Hosted Pumpkin – مستودع GitHub يحتوي على دليل مفهوم لتنفيذ العديد من تخصيصات CLR.

Shellcode: تحميل تجميعات .NET من الذاكرة – كان Donut بمثابة مساعدة كبيرة في التعامل مع جميع هياكل البيانات والتعريفات ذات الصلة في C.

كتاب Customizing the Microsoft .NET Framework Common Language Runtime للمؤلف Steven Pratschner – يُعد هذا العمل هو المرجع الحاسم لموضوع تخصيصات وقت تشغيل اللغة المشتركة (CLR). يجب عليك قراءة هذا الكتاب إذا كنت مهتمًا بهذا المجال.