قد يُعجب بعضكم بهذا الأسلوب وقد يكرهه البعض الآخر، لكن في هذه المرحلة لا ينبغي أن يكون مفاجئًا أن أساليب .NET باقية معنا لفترة أطول مما كان متوقعًا. يُعد إطار عمل .NET جزءًا لا يتجزأ من نظام التشغيل الخاص بشركة Microsoft مع أحدث إصدار من .NET هو .NET core. يُعد Core هو الخليفة متعدد المنصات لـ .NET Framework الذي يجلب .NET إلى Linux وmacOS أيضًا. وهذا يجعل الآن .NET أكثر شيوعًا من أي وقت مضى في أساليب الاستغلال اللاحق بين الخصوم والفرق الحمراء. وستتعمق هذه المدونة في Beacon Object File (BOF) الجديد الذي يسمح للمشغلين بتنفيذ تجميعات .NET أثناء العملية عبر Cobalt Strike بدلاً من وحدة المجموعة المدمجة التقليدية التي تستخدم تقنية fork and run.
لاحظ برنامج Cobalt Strike، المستخدم في محاكاة سلوك الخصوم، انتقال الفرق الحمراء من الاعتماد على أدوات PowerShell إلى C#، وذلك بسبب ازدياد قدرات الكشف المرتبطة بـ PowerShell، وفي عام 2018 قدم الإصدار 3.11 من Cobalt Strike وحدة التجميع التنفيذي. وسمح ذلك للمشغلين بالاستفادة من قوة مجموعات .NET بعد الاستغلال من خلال تنفيذها في الذاكرة دون التعرض لخطر إسقاط تلك الأدوات على القرص. وبينما لم تكن قدرة تحميل تجميعات.NET في الذاكرة عبر رمز غير مُدار جديد أو غير معروف وقت الإصدار، أود أن أقول إن Cobalt Strike أحضر هذه القدرة إلى التيار الرئيسي وساعد في تعزيز شعبية .NET في الأساليب بعد الاستغلال.
تستخدم الوحدة النمطية لتنفيذ التجميع في Cobalt Strike تقنية "fork and runل"، وهي إنشاء عملية جديدة للتضحية، وحقن الرمز الضار بعد الاستغلال في تلك العملية الجديدة، وتنفيذ الرمز البرمجي الضار، وعند الانتهاء، قم بإيقاف العملية الجديدة. وهذا له فوائده وعيوبه. إن الفائدة من طريقة fork and run هي أن التنفيذ يحدث خارج عملية زرع الإشارة لدينا. هذا يعني أنه إذا حدث خطأ ما في إجراءات ما بعد الاستغلال أو تم اكتشافه، فهناك فرصة أكبر بكثير لبقاء الزرع. وللتبسيط، فإنه يساعد حقًا في استقرار الزرع بشكل عام. ومع ذلك، نظرًا لأن بائعي الخدمات الأمنية يتعاملون مع هذا الانقسام وسلوك التشغيل، فقد أضاف الآن ما يعترف به Cobalt Strike، وهو نمط مكلف من OPSEC.
وبدءًا من الإصدار 4.1 الذي صدر في يونيو 2020، قدم Cobalt Strike ميزة جديدة لمحاولة معالجة هذه المشكلة من خلال إدخال ملفات Beacon Object Files (BOF). وتسمح ملفات BOF للمشغلين بتجنب أنماط التنفيذ المعروفة كما هو موضح أعلاه أو غيرها من حالات فشل OPSEC مثل استخدام cmd.exe/powershell.exe عن طريق تنفيذ ملفات الكائنات في الذاكرة داخل نفس العملية التي يتم فيها زرع الإشارة. وعلى الرغم من أنني لن أخوض في الأعمال الداخلية لملفات BOF، فإليك بعض المنشورات المفيدة:
إذا كنت قد قرأت المدونات أعلاه، فيجب أن ندرك الآن أن ملفات BOF لم تكن بالضبط نعمة التوفير التي كنا نأملها، وإذا كنت تحلم بإعادة كتابة كل أدوات .NET الرائعة تلك وتحويلها إلى ملفات BOF، فقد تم سحق تلك الأحلام الآن. آسف. ومع ذلك، لا يضيع الأمل لأن هناك، في رأيي، بعض الأشياء الرائعة التي يمكن أن تقدمها ملفات BOF، وقد استمتعت مؤخرًا كثيرًا (وكان هناك بعض بعض الإحباط أيضًا) بتجاوز حدود ما يمكن القيام به باستخدامها. أولاً، كان عن طريق إنشاء CredBandit الذي يقوم بإجراء تفريغ كامل في الذاكرة لعملية مثل LSASS وإرسالها مرة أخرى من خلال قناة اتصال Beacon موجودة لديك. واليوم أقوم بإصدار InlineExecute-Assembly، والذي يمكن استخدامه لتنفيذ تجميعات .NET داخل عملية الإشارة الخاصة بك دون أي تعديل على أدوات .NET المفضلة لديك. لننتقل الآن إلى أسباب كتابة ملف BOF، وأبرز ميزاته، والتنبيهات المرتبطة به، وكيف يمكن أن يكون مفيدًا عند تنفيذ محاكاة الخصوم وعمليات الفرق الحمراء.
النشرة الإخبارية الخاصة بالمجال
ابقَ على اطلاع دائم على أبرز الاتجاهات في مجالات الذكاء الاصطناعي، والأتمتة، والبيانات، وغيرها الكثير من خلال رسالة Think الإخبارية. راجع بيان الخصوصية لشركة IBM.
سيصلك محتوى الاشتراك باللغة الإنجليزية. ستجد رابط إلغاء الاشتراك في كل رسالة إخبارية. يمكنك إدارة اشتراكاتك أو إلغاء اشتراكك من هنا. لمزيد من المعلومات، راجع بيان خصوصية IBM.
السبب وراء بناء InlineExecute-Assembly بسيط جدًا. كنت أبحث عن وسيلة تُمكّن فريق محاكاة الخصوم لدينا من تنفيذ تجميعات .NET داخل نفس العملية، وذلك لتجنّب بعض مشكلات أمن العمليات (OPSEC) التي تم التطرّق إليها سابقًا عند استخدام Cobalt Strike في البيئات المتقدمة. وكنت بحاجة أيضًا إلى أن تكون الأداة غير مُرهِقة لفريقنا من حيث وقت التطوير، بحيث لا نضطر إلى إجراء تعديلات على معظم أدوات .NET الحالية لدينا. كما يجب أن يكون مستقرًا. حسنًا، بقدر ما يمكن أن يكون مستقرًا بقدر ما يمكن أن يكون ملف BOF معقدًا، لأن آخر شيء نريده هو فقدان أحد إشاراتنا القليلة في البيئة. وبشكل أساسي، يجب أن تعمل بسلاسة للمشغل مثل وحدة التجميع التنفيذي في Cobalt Strike قدر الإمكان.
أنا أعلم، هذا واضح نوعًا ما. ما كنا لنذهب بعيدًا بدونه، هل أنا على حق! وبغض النظر عن المزاح، فإن تفاصيل كيفية عمل CLR وما يحدث بالتفصيل يمكن أن تكون منشور مدونة مستقل بحد ذاته، لذلك سنتناول ما يستخدمه ملف BOF على مستوى عالٍ جدًا عند تحميل رمز CLR VIA غير المُدار.
تحميل CLR
كما هو موضح في لقطة الشاشة المبسطة أعلاه، فإن الخطوات الرئيسية التي سيتخذها ملف BOF لتحميل CLR هي كما يلي:
لذلك تتم الآن تهيئة CLR ولكن لا يزال هناك الكثير مما يجب أن يحدث قبل أن نبدأ بالفعل في تنفيذ تجميعات .NET المفضلة لدينا. ونحن بحاجة إلى إنشاء مثيل AppDomain الخاص بنا وهو ما تفسره Microsoft على أنه "بيئة معزولة يتم فيها تنفيذ التطبيقات". بعبارة أخرى، سيتم استخدام هذا لتحميل وتنفيذ تجميعات .NET بعد الاستغلال.
يتم إنشاء AppDomain وتحميل/تنفيذ التجميع
كما هو موضح في لقطة الشاشة المبسطة أعلاه، فإن الخطوات الرئيسية التي سيتخذها ملف BOF لتحميل واستدعاء تجميع .NET الخاص بنا هي كما يلي:
نأمل أن يكون لديك الآن فهم عالي المستوى لتنفيذ .NET عبر الرمز غير المُدار، ولكن هذا لا يجعلنا نحصل على أداة فعالة من الناحية التشغيلية، لذا سننظر في بعض الميزات التي تم تنفيذها في ملف BOF لنقلها من العادي إلى السليم تمامًا.
ربما تتساءل عن سبب أهمية ذلك. حسنًا، إذا كنت تشبهني وتقدر وقتك، فأنت لا تريد أن تقضيه في تعديل كل تجميع.NET تقريبًا بحيث تقوم نقطة الدخول الخاصة به بإرجاع سلسلة مرة أخرى مع جميع بياناتك التي عادةً ما يتم نقلها إلى وحدة التحكم في النتائج القياسية، أليس كذلك؟ كنت متوقعًا ذلك. ولتجنب ذلك، ما نحتاج إلى القيام به هو إعادة توجيه نتائجنا القياسية إما إلى قناة اتصال محددة أو فتحة بريد، وقراءة النتيجة بعد كتابتها، ثم إعادتها إلى حالتها الأصلية. وبهذه الطريقة، يمكننا تشغيل التجميعات غير المعدلة الخاصة بنا تمامًا كما لو كما نفعل من cmd.exe أو powershell.exe. والآن، قبل أن ننتقل إلى أي رمز، يجب أن أشكر @N4k3dTurtl3 ومنشور مدونتهم حول عملية تنفيذ التجميع وفتحات البريد. وهذا هو ما دفعني في الأصل إلى تطبيق هذه التقنية في زرع C الخاص بي عند ظهورها لأول مرة، وبعد عدة أشهر قمت بنقل نفس الوظيفة إلى ملف BOF. حسنًا، بعد تقديم الشكر المستحق، دعونا ننتقل إلى مثال مبسّط يوضّح كيف يمكن تحقيق ذلك من خلال إعادة توجيه stdout إلى قناة اتصال محددة كما هو موضح أدناه:
إعادة توجيه النتيجة القياسية لوحدة التحكم إلى قناة اتصال محددة والعودة مرة أخرى
تذكر عند تحميل CLR عبر ICLRMetaHost ->GetRuntime، كان علينا تحديد الإصدار الذي نحتاجه من إطار العمل .NET؟ هل تذكر كيف أن ذلك يعتمد على الإصدار الذي تم تجميع .NET الخاص بنا به؟ لن يكون من الممتع جدًا تحديد الإصدار المطلوب يدويًا في كل مرة، أليس كذلك؟ لحسن حظنا، قام @b4rtik بتنفيذ دالة رائعة للتعامل مع هذا الأمر في وحدة تنفيذ التجميع لإطار عمل Metasploit والذي يمكننا تنفيذه بسهولة في أدواتنا الخاصة الموضحة أدناه:
الدالة التي تقرأ تجميع .NET وتساعد في تحديد إصدار .NET الذي نحتاجه عند تحميل CLR
ما تفعله هذه الدالة بشكل أساسي هو عند تمرير وحدات بايت التجميع الخاصة بنا، أنها ستقرأ تلك البايتات وتبحث عن القيم السداسية العشرية لـ 76 34 2E 30 2E 33 30 33 31 39، والتي عند تحويلها إلى ASCII تكون v4.0.30319. وأتمنى أن يبدو ذلك مألوفًا. وإذا تم العثور على هذه القيمة عند قراءة التجميع، فستعرض الدالة 1 أو true، وإذا لم يتم العثور عليها تُرجع 0 أو false. ويمكننا استخدام هذا لتحديد الإصدار المراد تحميله بسهولة سواء كان 1/true أو 0/false كما هو موضح في مثال الرمز أدناه:
عبارة if/else لتعيين متغير إصدار .NET
لا يمكننا بالتأكيد التحدث عن الأسلوب الهجومي لـ .NET دون الحديث عن AMSI. وعلى الرغم من أننا لن نتعمق في الحديث عن ماهية AMSI وجميع الطرق الممكنة لتجاوزه، نظرًا لأن ذلك تم تناوله مرارًا، فإننا سنتحدث قليلًا عن السبب الذي قد يجعل تصحيح AMSI ضروريًا اعتمادًا على ما تقرر تنفيذه عبر BOF. وعلى سبيل المثال، إذا قررت تشغيل Seatbelt دون أي تشويش، فستلاحظ بسرعة أنك لم تحصل على أي نتائج وأن الإشارة الخاصة بك معطلة. نعم، معطلة. هذا لأن AMSI أمسكت تجميعتك، وقررت أنها ضارة، وأغلقتك مثل حفلة منزلية تُحدث الكثير من الضوضاء. ليس هذا هو الوضع المثالي، أليس كذلك؟ لدينا الآن خياران جيدان هنا عندما يتعلق الأمر بـ AMSI، إما أن نقوم بإخفاء أدوات .NET الخاصة بنا عبر شيء ما مثل ConfuserX أو Invisibility Cloak أو يمكننا تعطيل AMSI باستخدام مجموعة متنوعة من التقنيات. وفي حالتنا، سنستخدم أسلوب من RastaMouse، والمتمثل في تصحيح amsi.dll في الذاكرة بحيث تعيد E_INVALIDARG وتصبح نتيجة المسح 0. وكما تمت الإشارة إليها في المنشور، ويتم تفسير هذه النتيجة عادةً على أنها AMSI_RESULT_CLEAN. لنلقِ نظرة على نسخة مبسطة من الرمز لعملية x64 أدناه:
في تصحيحات الذاكرة لـ AmsiScanBuffer
كما ترون في لقطة الشاشة أعلاه، نقوم ببساطة بما يلي:
ومن خلال تطبيق هذا في أداتنا، يجب أن نكون قادرين الآن على تشغيل الإصدار الافتراضي من Seatbelt.exe باستخدام علامة amsi لتجاوز اكتشاف AMSI كما هو موضح أدناه:
مثال على تجاوز InlineExecute-Assemby AMSI
لحسن الحظ بالنسبة للمدافعين، هناك أكثر من مجرد AMSI للمساعدة عندما يتعلق الأمر باستخدام أسلوب .NET الضار باستخدام ETW. ولسوء الحظ، ومثل AMSI، يمكن أن يكون هذا أيضًا سهلًا جدًا على الخصوم لتجاوزه، وقد قام @xpn ببعض الأبحاث الرائعة حقًا حول كيفية القيام بذلك. فدعونا نلقي نظرة على مثال مبسط لكيفية تصحيح ETW لتعطيله تمامًا أدناه:
في تصحيح الذاكرة لـ EtwEventWrite
كما ترى من لقطة الشاشة أعلاه، فإن الخطوات مطابقة إلى حد كبير لكيفية تصحيح AMSI، لذا لن أتطرق إلى الخطوات الخاصة بهذا الأمر. ويمكنك رؤية لقطة شاشة قبل وبعد تشغيل لقطة شاشة –etw أدناه:
استخدام Process Hacker لعرض خصائص PowerShell.EXE قبل تشغيل inlineExecute-Assembly باستخدام العلامة –etw
تشغيل inline-Execute-Assembly باستخدام العلامة –etw
استخدام Process Hacker لعرض خصائص PowerShell.EXE نفسها بعد تشغيل inlineExecute-Assembly
بشكل افتراضي، يستخدم AppDomain أو قناة اتصال محددة أو فتحة البريد القيمة الافتراضية "totesLegit". يمكن تغيير هذه القيم لتندمج بشكل أفضل مع البيئة التي تختبرها إما من خلال تعديلها في برنامج نصي للمهاجم أو عبر إشارات سطر الأوامر سريعًا. يمكن عرض مثال على تغييرها عبر سطر الأوامر أدناه:
مثال على InlineExecute-Assembly باستخدام اسم AppDomain الفريد واسم قناة اتصال فريد
مثال على اسم AppDomain الفريد ChangedMe
مثال على قناة اتصال محدد فريد LookAtMe
مثال على AppDomain الذي تمت إزالته بعد الانتهاء من التنفيذ الناجح
مثال على قناة الاتصال المحددة الذي تتم إزالته بعد الانتهاء من التنفيذ الناجح
سيكون هذا القسم تكرارًا لما أشرت إليه في مستودع GitHub، لكنني شعرت أنه من المهم تكرار بعض الأشياء التي يجب أن تضعها في اعتبارك عند استخدام هذه الأداة:
فيما يلي بعض الاعتبارات الدفاعية: