
أداة إثبات مفهوم (PoC) مبنية بلغة Rust تستخدم ألياف ويندوز (Windows fibers) لتنفيذ كود في الذاكرة بشكل خفي، وإخفاء مداخيل الحمولة من نظام EDR عن طريق التبديل بين الألياف التحكمية وألياف الحمولة بدون استدعاءات نواة (kernel callbacks).
الـ fiber هي وحدة تنفيذ يجب جدولتها يدويًا بواسطة التطبيق بدلاً من الاعتماد على آلية الجدولة القائمة على الأولوية المضمنة في Windows. غالبًا ما تُسمى الـ fibers بالخيوط الخفيفة. لمزيد من المعلومات التفصيلية حول ماهية الـ fibers وكيفية عملها، راجع التوثيق الرسمي.
تسمح الـ fibers بوجود تدفقات تنفيذية متعددة في خيط واحد، لكل منها حالة سجلاته ومكدسه الخاص. من ناحية أخرى، فإن الـ fibers غير مرئية للنواة (kernel)، مما يجعلها طريقة أكثر تمويهًا (وأقل تكلفة) لتنفيذ كود في الذاكرة مقارنة بإنشاء خيوط جديدة.
يمكن لخيط واحد إنشاء fibers متعددة، والتبديل بينها حسب الرغبة عن طريق استدعاء الدالة SwitchToFiber. قبل ذلك، يجب أن يصبح الخيط الحالي نفسه fiberًا عن طريق استدعاء ConvertThreadToFiber لأنه لا يمكن إلا لـ fiber إنشاء fibers أخرى. أخيرًا، لإنشاء fiber ينفذ، عند جدولته، كودًا في الذاكرة (على سبيل المثال، بعد تحميل PE بشكل انعكاسي أو بعض الشيل كود)، كل ما هو مطلوب هو استدعاء CreateFiber.
الدالة SwitchToFiber هي أهم جزء في هذه العملية وحيث يحدث كل السحر. تسمح هذه الدالة بجدولة fiber أو آخر، وكل ذلك يحدث في مساحة المستخدم. وفقًا للتوثيق الرسمي، "تقوم دالة SwitchToFiber بحفظ معلومات حالة الـ fiber الحالي واستعادة حالة الـ fiber المحدد". هذا يعني أنه عند استدعاء هذه الدالة، يتم تبديل قيم السجلات والمكدس من حالة الـ fiber الحالي إلى حالة الـ fiber الهدف، مما يسمح "بإخفاء" مكدس الـ fiber الحالي بمجرد اكتمال العملية. يسمح هذا أيضًا بمواصلة تنفيذ الـ fiber الهدف من نفس النقطة التي توقف فيها التنفيذ (بنفس الطريقة التي تحدث عندما يقوم المجدول بالتبديل بين الخيوط وفقًا لمنطق الأولوية الخاص به).
وهذا بالضبط ما يفعله هذا الإثبات البسيط للمفهوم (PoC):
run() المُصدرة من ملف dll المعين يدويًا. سيُعرف هذا fiber من الآن فصاعدًا باسم fiber الحمولة.تتكرر هذه العملية إلى ما لا نهاية.
قد يكون استخدام الـ fibers مفيدًا لبعض أنواع الحمولات (مثل منارة C2) لبعض هذه الأسباب:
JMP أو CALL من المحمل التي تشير إلى مناطق ذاكرة غير مدعومة (unbacked memory).نظرًا لأننا نستخدم إضافة LITCRYPT لتعتيم الحروف النصية (string literals)، فمن الضروري تعيين متغير البيئة LITCRYPT_ENCRYPT_KEY قبل تجميع الكود:
C:\Users\User\Desktop\Fiber> set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"
بعد ذلك، قم بتجميع كل من الحمولة والمحمل وتشغيل الأخير:
C:\Users\User\Desktop\Fiber\payload> cargo build --release
C:\Users\User\Desktop\Fiber\loader> cargo build --release
C:\Users\User\Desktop\Fiber\loader\target\release> loader.exe
لا يوجد الكثير من الغموض في تنفيذ هذا الإثبات للمفهوم. كل ما يجب فعله هو تشغيل المحمل واستخدام أي أداة مثل ProcessHacker لفحص مكدس الخيط. نظرًا لأن الحمولة تعود إلى fiber التحكم قبل النوم، فإن مكدس fiber الحمولة يظل مخفيًا معظم الوقت. سترى في الإخراج كيف يتم جدولة الـ fiberين بالتتابع وفقًا للمنطق المذكور سابقًا.
الكود مُعلق لإظهار كيفية استخدام وإنشاء وجدولة الـ fibers. ستلاحظ أن كلاً من المحمل والحمولة المقدمين كمثال عالقان في حلقة لا نهائية، مما يسمح بالتبديل إلى ما لا نهاية بين الـ fibers ومواصلة التنفيذ.
إذا كنت تريد اختبار حمولة مختلفة، فقط قم بتعديل المسار الموجود في السطر 32 من ملف src::main.rs للمحمل. في هذه الحالة، يجب على ملف dll الجديد تصدير دالة run(PVOID) ستستقبل كمعامل إدخال عنوان fiber التحكم. يجب على هذه الدالة التبديل مرة أخرى إلى fiber التحكم من أجل استدعاء دالة Sleep، على الرغم من أنه يمكنك تعديل هذا السلوك حسب رغبتك ليتناسب مع متطلباتك.
هناك طريقة أخرى لاختبار هذه الأداة مع حمولة عشوائية وهي إجراء ربط (hooking) لجدول IAT لتوجيه أي استدعاء لدالة Sleep (أو أي دالة مستوردة أخرى) تقوم به الحمولة إلى دالة موجودة في المحمل، مما يسمح بالتبديل مرة أخرى إلى fiber التحكم عند حدوث هذا الاستدعاء. الأمر لك.
في لقطات الشاشة التالية، يمكننا رؤية كيف ينتقل مكدس الخيط الحالي من منطقة ذاكرة خاصة إلى أخرى أثناء تبديل الـ fibers:
