Исследователи информационной безопасности обнаружили сложную многоступенчатую атаку, в рамках которой злоумышленники одновременно развертывают два различных вредоносных компонента: инфостилер Vidar 2.1 и полнофункциональный RAT (троян удаленного доступа) SnappyClient. Атака использует технику DLL- sideloading через легитимное программное обеспечение, включая Radmin VPN и браузер Opera, что позволяет обойти многие традиционные средства защиты.
Описание
В основе кампании лежит загрузчик HijackLoader, который работает в две независимые цепочки. Первая цепочка, как описывалось ранее, загружает, расшифровывает и внедряет Vidar в легитимный подписанный процесс NeuroManag.exe для кражи сессионных данных и паролей. Вторая цепочка, запускаемая тем же родительским процессом WizardDa42.exe (легитимный Radmin VPN), осуществляет доставку SnappyClient. Между собой цепочки не имеют общих компонентов, за исключением общего родителя - у каждой свой хост для sideloading, свой экземпляр HijackLoader, свой набор ресурсов и конечная полезная нагрузка.
Схема второй цепочки выглядит следующим образом: WizardDa42.exe запускает AlphVector.exe (легитимный бинарник Opera). Opera, в свою очередь, загружает подмененную библиотеку opera_elf.dll, в которую злоумышленники внедрили патч размером около 150 байт. Патч заменяет один вызов внутри статического инициализатора libc++, который срабатывает автоматически при загрузке DLL до вызова DllMain. В результате opera_elf.dll загружает библиотеку WebView2Loader.dll (на самом деле - модифицированную сборку объемом 165 КБ вместо 141 КБ у оригинальной Microsoft), а та, в свою очередь, извлекает из ресурсного файла audio.lib зашифрованный PE-файл SnappyClient.
Предоставленный анализ показывает, что для извлечения конечной полезной нагрузки требуется преодоление нескольких слоев защиты. Сама audio.lib представляет собой ресурсный пакет HijackLoader. Внутри него SnappyClient хранится в сжатом LZMA-формате в секции .xyz. Распаковка дает исполняемый файл размером 3,7 МБ.
SnappyClient - это коммерческого уровня RAT с широким набором функций. В отличие от Vidar, который крадет данные однократно и завершает работу, SnappyClient предназначен для закрепления в системе и предоставления оператору интерактивного управления. Его возможности включают: удаленный рабочий стол (VNC и скрытый рабочий стол HVNC), FTP-сервер, обратный прокси SOCKS5, удаленную командную оболочку, кейлоггер, кражу паролей браузеров, cookies и криптовалютных кошельков. Управление и контроль (C2) осуществляется по собственному зашифрованному протоколу поверх TCP, а не через HTTPS.
Динамический анализ подтвердил, что после запуска цепочка операций занимает чуть более двух минут до установления активного C2-соединения. Процесс разворачивается так: AlphVector.exe копирует всю цепочку загрузки в C:\ProgramData\service и перезапускается оттуда. Затем opera_elf.dll загружает WebView2Loader.dll, который извлекает SnappyClient и записывает его во временный файл со случайным именем в %Temp%. После этого создается процесс VirtualAr.exe (подписанный бинарник Qihoo 360) в приостановленном состоянии, в его адресное пространство методом process hollowing отображается SnappyClient, после чего основной поток возобновляется. Параллельно Crisp.exe устанавливает постоянство: создает ярлык автозапуска в папке Startup и задачу в планировщике Windows (формат .job, Task Scheduler 1.0), избегая стандартных ключей реестра Run и XML-задач.
Обнаружение дополнительных компонентов показало, что SnappyClient использует при старте тайминг-петлю на 1,7 миллиарда итераций для обхода песочниц, а затем сканирует среду на наличие VMware Tools. Полезная нагрузка выполняет сбор данных: перечисление профилей Chrome и Edge, извлечение зашифрованных ключей App-Bound через вызов COM-интерфейса IElevator, что приводит к запуску elevation_service.exe от имени services.exe. Атака также проверяет наличие криптовалютных кошельков (Bitcoin, Coinomi, Electrum, Exodus) и собирает файлы Telegram.
Конфигурация C2 восстанавливается через сложную цепочку: внутри SnappyClient обнаружен JSON-объект с тегом "TIGRAN" и директорией хранения "Mayanex". Далее зашифрованный сетевой конфигурационный блок проходил через Snappy-сжатие, Base58-кодирование, шифрование ChaCha20-Poly1305, модифицированный RIPEMD-160 и защищенный паролем 7z-архив. В итоге были получены шесть адресов C2-серверов: основной IP 66.163.113.238 и пять доменов вида yoda-*.sbs/.site/.online, порты управления 3333 и передачи данных 3334.
Сам протокол C2 уникален: сервер при подключении отправляет 48 байт (32-байтный ключ ChaCha20, 12-байтный nonce и 4-байтный ID сессии). Клиент отвечает подтверждением, зашифровав полученный ключ тем же алгоритмом. После этого все сообщения имеют формат: зашифрованный заголовок (2 байта команды, 4 байта ID сообщения, длина тела, флаги) плюс Snappy-сжатый и зашифрованный JSON. Каждая сессия данных получает свой ключ и nonce, при этом управляющее соединение (порт 3333) используется только для команд, а три параллельных канала данных (порт 3334) - для массовой передачи похищенной информации.
В ходе динамического анализа наблюдалась команда на сбор браузерных данных, охватывающая более десятка браузеров на Chromium и Mozilla, а также команда exSeeds для поиска файлов с сид-фразами криптокошельков (BIP-39) и команда на кражу программных кошельков и Telegram. При этом другие возможности - HVNC, SOCKS-прокси, обратная оболочка - в данном запуске не использовались, хотя статически присутствуют в коде.
Данная кампания показательна тем, что объединяет в одной атаке два этапа, которые обычно разделены во времени: кражу данных (через Vidar) и обеспечение долговременного доступа (через SnappyClient). В модели access economy инфостилеры часто служат начальным вектором, после чего доступ продается брокерам, которые затем развертывают вымогательское ПО. Здесь оба этапа выполняются одновременно, что резко сокращает время между компрометацией и возможным нападением. Для защиты это означает, что обнаружение инфостилера не должно рассматриваться как изолированный инцидент - та же загрузочная цепочка могла доставить и постоянную RAT-нагрузку, и хост следует считать полностью скомпрометированным, пока не доказано обратное.
Индикаторы компрометации
IPv4 Port Combination
- 66.163.113.238:3333
- 193.24.123.38:3333
- 193.24.123.38:3334
SHA256
- 725344564a8c9b0a033e9a5d41369fae5b8503d36e87002e7c064eface927e7b
- 8e04821fa87512991cb562a6aa4c3df695a63cb1261c5b5c7e9b9af37a3a50a9
- debbb3b4fdd9107deec64c46b453b988c5b51a99e006f2c6d1c804b5e2a33991
YARA
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | import "pe" rule Vidar_x64_v2_family { meta: description = "Vidar x64 2.x unpacked payload" author = "Gianluca Tiepolo" date = "2026-06-22" family = "Vidar" version = "2.x" arch = "x86-64" scope = "unpacked payload" reference_1 = "725344564a8c9b0a033e9a5d41369fae5b8503d36e87002e7c064eface927e7b" reference_2 = "debbb3b4fdd9107deec64c46b453b988c5b51a99e006f2c6d1c804b5e2a33991" reference_3 = "8e04821fa87512991cb562a6aa4c3df695a63cb1261c5b5c7e9b9af37a3a50a9" strings: /* * Shared 4-argument bytecode-VM decoder setup */ $vm_setup = { 44 89 4C 24 20 4C 89 44 24 18 89 54 24 10 48 89 4C 24 08 48 83 EC 68 E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 25 FF 00 00 00 88 44 24 30 C6 44 24 31 00 48 8B 44 24 70 48 89 44 24 38 8B 44 24 78 89 44 24 40 C7 44 24 44 00 00 00 00 } /* * Stable VM dispatch loop */ $vm_dispatch = { 8B 44 24 40 39 44 24 44 73 4F 8B 44 24 50 39 44 24 54 73 45 8B 44 24 44 48 8B 4C 24 38 8A 04 01 88 44 24 20 8B 44 24 44 FF C0 89 44 24 44 0F B6 44 24 20 48 8D 0D ?? ?? ?? ?? 48 8B 04 C1 48 89 44 24 28 48 83 7C 24 28 00 75 02 EB 0C 48 8D 4C 24 30 FF 54 24 28 90 EB A7 48 83 C4 68 C3 } /* * Embedded-config repeating-XOR decoder. The low register bits in * the ModR/M and SIB bytes are wildcarded because v2.2 preserves the * loop but allocates its source/index registers differently */ $config_xor_loop = { 4C 8B C9 4C 2B CA 4C 8B C2 49 F7 D8 [0-96] 4B 8D 04 ?? 48 F7 F7 8A 0C 32 41 8A 0? 32 C8 43 88 0C 1? 49 FF C? 4B 8D 04 ?? 49 3B C? 72 ?? } condition: uint16(0) == 0x5A4D and pe.machine == pe.MACHINE_AMD64 and pe.number_of_imported_functions == 0 and pe.sections.len() >= 4 and pe.sections.len() <= 6 and filesize > 800KB and filesize < 1500KB and for any i in (0..pe.sections.len() - 1): ( pe.sections[i].name == ".data" and pe.sections[i].virtual_size > 0x100000 and pe.sections[i].raw_data_size < 0x4000 ) and all of them } |