
Хабр, привет! Меня зовут Дмитрий, я занимаюсь расследованием и реагированием на инциденты информационной безопасности. В ходе недавнего расследования инцидента, связанного с деятельностью APT-группировки Space Pirates, были выявлены два любопытных образца вредоносного ПО. В процессе более тщательного анализа оказалось, что первый образец — это бэкдор LuckyStrike Agent, описанный коллегами из Solar 4RAYS, но который, в отличие от их случая, в качестве канала C2 использовал публичный облачный сервис Yandex Cloud, второй — ранее не описанный бэкдор, также использующий публичные облачные сервисы в качестве канала удалённого управления (на момент анализа OneDrive и Dropbox), обладающий широким функционалом и хорошо спроектированной архитектурой (по моему субъективному мнению); ему было решено дать название PhantomCloud. О них и пойдет речь в этой статье.
LuckyStrike Agent. Вариант с Yandex Cloud
Хоть данный образец и был интересен изначально, но, еще раз повторюсь, он был достаточно хорошо описан коллегами из Solar 4RAYS, поэтому я приведу лишь отличия, которые встретились в процессе анализа, а также краткую информацию о том, как выглядит конфигурация в случае с каналом удаленного управления в качестве Yandex Cloud.
Начнем с того, что загрузчик никаким образом не был связан с покерной тематикой, но имел аналогичную функциональность расшифрования полезной нагрузки и ее запуска.

Основная полезная нагрузка имела практически аналогичный функционал: была собрана с помощью Fody Costura, в ресурсах были те же отладочные символы, в которых упоминались такие строки, как «F:\Code\Thomas\Server\Library\obj\Paid Version.NETFramework» и прочие.
Формирование agent_id немного изменено, и представляет из себя MD5 от конкатенации строк «paidNew0», имени модуля, agent_type, username (добавляет символ * после имени пользователя, если он из группы администраторов) и физического адреса хоста:

В остальном каких-то существенных изменений обнаружено не было.
Расшифрованная конфигурация в случае с Yandex Cloud выглядит следующим образом:
{
"version": "",
"listener_id": "<REDACTED>",
"folder_name": "Sociosqu class ligula, ultrices, elementum mauris rhoncus.",
"app_id": "<REDACTED>",
"app_secret": "<REDACTED>",
"refresh_token": "<REDACTED>",
"base_url": "https://cloud-api.yandex.net/v1/disk/resources",
"token_url": "https://oauth.yandex.com/token",
"code_type": ".NET4.5.1",
"agent_type": "Yandex",
"scope": "cloud_api:disk.read cloud_api:disk.write",
"remote_config_uri": "",
"sleep": 360,
"proxy": "",
"begin_date": "00:00:00",
"end_date": "23:59:59",
"payload": {
"key": "<REDACTED>",
"meta_data_name": null,
"task_folder_name": "Primis magna augue",
"receive_folder_name": "Consectetur lectus dui",
"prepend": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n<html xmlns=\"http : //www.w3.org/1999/xhtml\">\r\n <head>\r\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\r\n <title>IIS Windows Server</title>\r\n <style type=\"text/css\">\r\n <!--\r\n body {\r\n\t color:#000000;\r\n\t background-color:#0072C6;\r\n\t margin:0;\r\n }\r\n\r\n# container {\r\n\t margin-left:auto;\r\n\t margin-right:auto;\r\n\t text-align:center;\r\n\t }\r\n\r\n a img {\r\n\t border:",
"append": ";\r\n }\r\n\r\n -->\r\n </style>\r\n </head>\r\n <body>\r\n <div id=\"container\">\r\n <a href=\"http://go.microsoft.com/fwlink/?linkid=66138&clcid=0x409\"><img src=\"iisstart.png\" alt=\"IIS\" width=\"960\" height=\"600\" /></a>\r\n </div>\r\n </body>\r\n</html>",
"payload_prepend": "Arcu",
"payload_append": "Conubia"
}
}
После чего эта конфигурация десериализуется в объект profile, который, в свою очередь, встроен в объект config:
public class config
{
public static object m_lock = new object();
public static string external = string.Empty;
public static string agent_id = string.Empty;
public static string version = string.Empty;
public static Mutex mutex = null;
public static profile profile = null;
public static string folder_flag = "-403";
public static string profile_name = "log.cached";
public static string logs = "C:\\Users\\Public\\Libraries\\thomas.log";
public static string current_date = string.Empty;
}
public class profile
{
public string version { get; set; }
public string listener_id { get; set; }
public string folder_name { get; set; }
public string app_id { get; set; }
public string app_secret { get; set; }
public string refresh_token { get; set; }
public string base_url { get; set; }
public string token_url { get; set; }
public string code_type { get; set; }
public string agent_type { get; set; }
public string scope { get; set; }
public string remote_config_uri { get; set; }
public string sleep { get; set; }
public string proxy { get; set; }
public string begin_date { get; set; }
public string end_date { get; set; }
public payload payload { get; set; }
}
Yandex Cloud в качестве канала C2 подтверждает предположение коллег из Solar 4RAYS о гибкости конфигурации LuckyStrike Agent. Как было отмечено в их анализе, наличие поля AgentType указывает на потенциальную возможность использования других облачных сервисов, что мы и увидели.
Думаю, что на этом можно закончить анализ первого образца.
PhantomCloud
Компоненты и запуск основной полезной нагрузки
На зараженную систему PhantomCloud попадал со следующими файлами:
Veeam.Guest.Interactions.exe — легитимный исполняемый файл, являющийся компонентом прокси-сервера взаимодействия с гостевой системой в продуктах Veeam;
Veeam.Guest.Interactions.exe.config — конфиг, используемый для загрузки вредоносной .NET сборки;
AppVStreamingUX.dll — вредоносная .NET сборка, представляющая из себя загрузчик, которая расшифровывает и запускает основную полезную нагрузку;
AppVStreamingUX.nlp — зашифрованная основная полезная нагрузка (собственно, сам PhantomCloud).
Загрузчик AppVStreamingUX.dll так же, как и LuckyStrike Agent, использует технику AppDomain Manager Injection (T1574.014), позволяя загружать вредоносные .NET сборки в легитимные .NET приложения. В данном случае в качестве легитимного приложения было выбрано Veeam.Guest.Interactions.exe. Похоже, что данная техника становится излюбленной для этой группировки.
Конфигурационный файл Veeam.Guest.Interactions.exe.config для загрузки вредоносной .NET сборки выглядит следующим образом:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
</assemblyBinding>
<etwEnable enabled="false" />
<appDomainManagerAssembly value="AppVStreamingUX, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
<appDomainManagerType value="HelperInfo" />
</runtime>
</configuration>
По функционалу загрузчик практически аналогичен тому, что используется в LuckyStrike Agent:
Полезная нагрузка расшифровывается алгоритмом AES в режиме CBC. С помощью PBKDF2 выполняется формирование ключа, а вектором инициализации выступают первые 16 байт основной полезной нагрузки;
Запуск расшифрованной вредоносной .NET сборки в контексте текущего процесса.
Единственное отличие состоит в том, что полезная нагрузка не была дополнительно закодирована в base64.
Технические детали
Структура классов

Видно, что имя сборки имеет название CloudPhantom.Client, а классы находятся в пространстве имен CloudCore, вследствие чего ему и было решено дать название PhantomCloud.
Несмотря на схожесть наименования исследуемого вредоносного ПО с названиями инструментов, используемых одной небезызвестной APT-группировкой, на текущий момент отсутствуют какие-либо данные, позволяющие утверждать наличие заимствования или прямой связи с ней. Дальнейший анализ в части атрибуции отдадим на откуп TI-аналитикам.
Инициализация приложения
Метод Program.Main является точкой входа WinForms-приложения и выполняет базовую настройку среды выполнения:
Задаётся используемая версия TLS для всех сетевых соединений в рамках процесса;
Регистрируется обработчик необработанных исключений, позволяя централизованно обрабатывать сбои и предотвращать отображение стандартных диалогов об ошибках;
Запускается цикл обработки сообщений Windows с формой
CloudAppв качестве основной.

Объект CloudApp наследуется от класса Form и служит контейнером для выполнения основной логики приложения. Фактически, данная форма не будет показана пользователю, так как во время загрузки она скрывается и не отображается в панели задач, о чем свидетельствует переопределенный метод CloudApp.OnLoad:

Далее вызывается метод CloudApp.Run, в котором находится основная логика запуска PhantomCloud:

Разберем каждый этап запуска основной логики поочередно.
Этапы запуска основной логики PhantomCloud
Этап 1. Инициализация конфигурации
Метод Settings.Initialize выполняют инициализацию или загрузку конфигурации приложения:

Далее метод Settings.LoadConfig ищет файл Transport.dsa в директории с исполняемым файлом, если находит его, то считывает все байты из файла, передает их в метод расшифрования Crypto.Decrypt, а после выполняет десериализацию в объект EnpointInfo. Если же данный файл не был обнаружен, то сначала считываются байты из строки configData, передаются методу Crypto.Decrypt, а уже после выполняется десериализация. В свою очередь, метод Crypto.Decrypt выполняет расшифрование данных, последовательно прибавляя к каждому байту константу и применяя побитовую операцию XOR с вычисленным значением.
Примечательно, что для сериализации данных приложение использует Protocol Buffers (Protobuf), что позволяет эффективно упаковывать структурированные данные в компактный бинарный формат.

Таким образом, configData содержит в себе следующую зашифрованную и сериализованную информацию:
Поле структуры EndpointInfo | Значение поля | Описание |
|---|---|---|
ConfigCommon.ResourceFolder | pdfResource | Директория с ресурсами в облачном хранилище, в котором PhantomCloud хранит вспомогательные и эксфильтрованные данные |
ConfigCommon.RootFolder | pdfClient | Корневая директория в облачном хранилище |
ConfigCommon.TaskFolder | pdfTask | Директория с задачами в облачном хранилище, используемая PhantomCloud для получения новых задач и передачи результатов |
ConfigType | 1 | Является перечисляемым типом (enum), указывающий какой облачный сервис использовать в качестве C2, в данной конфигурации указывает на OneDrive |
Configuration.RedurectURI | hxxps://localhost:65001 | Используется в конфигурации OneDrive при обновлении Access Token |
Configuration.ClientID | REDACTED | Идентификатор клиента PhantomCloud |
Configuration.AccessToken | EwA4B... | Access Token |
Configuration.RefreshToken | M.C542... | Refresh Token |
DISABLE | 01.01.0001 0:00:00 (значение по умолчанию) | Дата, до наступления которой PhantomCloud переходит в режим сна (sleep) |
Interval | 60 | Интервал выполнения запросов к C2 (beacon) |
После чего эта конфигурация десериализуется в объект EndpointInfo:
[ProtoContract]
public class EndpointInfo
{
[ProtoMember(1)]
public ClientInfo ClientInfo { get; set; }
[ProtoMember(2)]
public DateTime Disable { get; set; }
[ProtoMember(3)]
public int Interval { get; set; }
[ProtoMember(10)]
public ConfigCommon ConfigCommon { get; set; }
[ProtoMember(11)]
public ConfigType ConfigType { get; set; }
[ProtoMember(12)]
public IConfig Configuration { get; set; }
[ProtoMember(19)]
public string Msg { get; set; }
}
[ProtoContract]
public class ClientInfo
{
[ProtoMember(1)]
public string Id { get; set; }
[ProtoMember(2)]
public int ListenerId { get; set; }
[ProtoMember(4)]
public string OS { get; set; }
[ProtoMember(5)]
public string Username { get; set; }
[ProtoMember(6)]
public string Hostname { get; set; }
[ProtoMember(7)]
public AccountType AccountType { get; set; }
[ProtoMember(8)]
public string Region { get; set; }
[ProtoMember(9)]
public string IPInternal { get; set; }
[ProtoMember(10)]
public string IPExternal { get; set; }
[ProtoMember(11)]
public string ProcessName { get; set; }
[ProtoMember(12)]
public int ProcessId { get; set; }
[ProtoMember(13)]
public DateTime OnlineTime { get; set; }
[ProtoMember(14)]
public TimeSpan TimeZone { get; set; }
[ProtoMember(15)]
public string Domain { get; set; }
[ProtoMember(16)]
public string Owner { get; set; }
}
[ProtoContract]
public class ConfigCommon
{
[ProtoMember(4)]
public string RootFolder { get; set; }
[ProtoMember(5)]
public string TaskFolder { get; set; }
[ProtoMember(6)]
public string ResourceFolder { get; set; }
}
public enum ConfigType
{
Unknown, // 0
OneDrive, // 1
Dropbox, // 2
Mega, // 3
CloudflareR2, // 4
MailRU, // 5
ProtonDrive // 6
}
[ProtoContract]
public interface IConfig // данный интерфейс реализуют ConfigDropbox и ConfigOneDrive, описанные ниже
{
}
[ProtoContract]
internal class ConfigDropbox : IConfig
{
[ProtoMember(1)]
public string ClientId { get; set; }
[ProtoMember(2)]
public string ClientSecret { get; set; }
[ProtoMember(3)]
public string RefreshToken { get; set; }
[ProtoMember(4)]
public string AccessToken { get; set; }
}
[ProtoContract]
internal class ConfigOneDrive : IConfig
{
[ProtoMember(1)]
public string ClientID { get; set; }
[ProtoMember(2)]
public string RedirectURI { get; set; }
[ProtoMember(3)]
public string RefreshToken { get; set; }
[ProtoMember(4)]
public string AccessToken { get; set; }
}
После метода Settings.LoadConfig вызывается следующий метод EndpointInfo.GetInfo, который выполняет сбор информации о системе, и обогащает объект EnpointInfo.ClientInfo (приведен выше) следующей информацией:
Имя компьютера;
Имя пользователя;
Идентификатор и имя процесса, в котором запущен кли��нт PhantomCloud;
Информация о редакции Windows;
Домен/рабочая группа компьютера;
Имя владельца ОС;
Внутренний IP-адрес хоста;
Вычисляется MD5 от строки EndpointInfo.ConfigType\domain\username@hostname и присваивается полю ClientInfo.Id;
Внешний IP-адрес и страна хоста (выполняется запрос к сервису
ipinfo.io);Права пользователя и принадлежность к группам.


Далее вызывается метод Settings.SaveConfig, в котором объект EndpointInfo сериализуется , зашифровывается методом Crypto.Encrypt (обратный к Crypto.Decrypt) и сохраняется в конфигурационный файл Transport.dsa. Несмотря на тот факт, что вся информация о хосте (EndpointInfo.ClientInfo) сохраняется в файл Transport.dsa, при каждом запуске выполняются повторный сбор информации и запрос к удаленному сервису ipinfo.io.
Этап 2. Эвристический анализ на запуск в песочнице
Метод Sensibility.Calculate выполняет эвристический анализ для того, чтобы определить, запущен ли PhantomCloud в песочнице или нет:

Выполняются проверки количества запущенных браузеров на хосте, аномалий логических дисков, и автоматически сгенерированного имени пользователя:



Если по всем проверкам в сумме набирается 5 и выше баллов, приложение закрывается с ошибкой.
Этап 3. Создание мьютекса
На данном этапе происходит инициализация мьютекса с именем EndpointInfo.ClientInfo.Id (вычисленный MD5 от конкатенации строк) с целью предотвращения повторного запуска вредоносного ПО в текущей сессии пользователя.
Этап 4. Регистрация обработчиков событий
Метод MessageHandler.Register принимает в качестве аргумента интерфейс IMessageProcessor. Это означает, что все регистрируемые обработчики — FileManagerHandler, CodeTaskHandler, ConfigHandler и ClientHandler — реализуют данный интерфейс, а сам метод отвечает за их регистрацию.

internal interface IMessageProcessor
{
bool CanExecute(MessageBase message); // проверка на соответствие аргумента одному из указанных типов
bool IsSend(MessageBase message); // заглушка, во всех случаях возвращает false
bool CanExecuteFrom(ICloudSender sender); // заглушка, во всех случаях возвращает true
void Execute(ICloudSender sender, MessageBase message); // выполнить команду
}
Метод CanExecute проверяет соответствие типа объекта (передан в качестве аргумента в функцию) одному из указанных типов:

И, в зависимости от типа поступившего объекта, будет выполнено то или иное действие методом Execute:

После регистрации всех обработчиков далее, в основном цикле программы, мы увидим, как вызывается метод MessageHandler.Process, который выполняет фильтрацию зарегистрированных обработчиков по условиям CanExecute и CanExecuteFrom, после чего вызывает Execute для каждого подходящего обработчика.

Ниже приведена функциональность каждого обработчика:
FileManagerHandler — обработчик для работы с файлами
Тип объекта | Описание |
|---|---|
PsFileDeleteRequest | Удаление файла |
PsFileDownloadRequest | Загрузка файла в облачное хранилище с зараженной системы |
PsFileListRequest | Вывод содержимого директории |
PsFileDrivesRequest | Перечисление дисков системы и их параметров |
PsFileRenameRequest | Переименование файла или директории |
PsFileUploadRequest | Загрузка файла c облачного хранилища на зараженную систему |
CodeTaskHandler — обработчик выполнения команд
Тип объекта | Описание |
|---|---|
PsCodeTaskRequest | В зависимости от PsCodeTaskRequest.Code будет выполняться определенная команда |
PsCodeTaskResponse | Заглушка, не используется |
Возможные PsCodeTaskRequest.Code и их описание:
Код команды | Описание |
|---|---|
0x10C6D | Вывод содержимого директории |
0x22D8CE | Информация о системе |
0x4AC5B9C | Сбор информации о пользовательских сессиях |
0x4C26E10 | Выполнение CMD |
0x732F3B49 | ipconfig |
0x7BD59CFF | Время работы (bootTime) |
0x8B7CC1BD | Выполнение WMI запроса |
0xD1183151 | netstat |
0xEB72FFC3 | tasklist |

ConfigHandler — обработчик изменения конфигурационного файла PhantomCloud
Тип объекта | Описание |
|---|---|
PsConfigEditRequest | Изменение типа канала взаимодействия (на момент исследования: OneDrive или Dropbox) |
PsConfigGetRequest | Получение конфигурационного файла |
PsConfigIntervalRequest | Установка интервала выполнения запросов к C2 (beacon) |
PsConfigDisableRequest | Установка значение даты, до наступления которой PhantomCloud переходит в режим сна (sleep) |
ClientHandler — обработчик управления PhantomCloud
Тип объекта | Описание |
|---|---|
PsClientCloseRequest | Закрытие соединения |
PsClientUninstallRequest | Создание и запуск .bat скрипта, который удаляет с системы PhantomCloud |
PsClientUpdateRequest | Загрузка новой версии PhantomCloud с облачного хранилища, после чего создаётся и запускается .bat‑скрипт обновления, предназначенный для замены ранее установленной версии PhantomCloud |
Скрипт удаления PhantomCloud:

Скрипт обновления PhantomCloud:

Файлы, задействованные скриптом в процессе удаления и обновления:
Settings.ExecutableFile = "PhantomCloud.bin";
Settings.ExtraFile = { "AppVStreamingUX.dll", "AppVStreamingUX.exe.config", "AppVStreamingUX.nlp" };
Settings.Cfg = "Transport.dsa";
Интересно, что скрипт при выполнении удаления и обновления обращается к файлу AppVStreamingUX.exe.config, в то время как в ходе расследования был обнаружен файл Veeam.Guest.Interactions.exe.config. Трудно однозначно сказать, почему клиент не был перекомпилирован под данный конфигурационный файл.
Все рассмотренные типы (PsFileDeleteRequest, PsCodeTaskRequest и тд.) каждого обработчика наследуются от базового класса MessageBase и расширяют его, добавляя собственный набор специфичных полей.
Этап 5. Запуск основного цикла приложения
На финальном этапе создается экземпляр CloudClient:

Первым делом происходит важный момент: выполняется динамическая регистрация всех типов, унаследованных от MessageBase, которая позволяет сериализатору Protobuf в дальнейшем корректно восстанавливать реальные типы сообщений при десериализации, что является необходимым условием для их корректной обработки. В противном случае, десериализация в объект MessageBase приводила бы к потери полей, определённых в дочерних классах, что делало бы невозможной корректную проверку типа сообщения.

Затем в зависимости от enum поля EndpointInfo.ConfigType создается клиент OneDrive или Dropbox, который является частью объекта CloudClient.
Также определяются переменные, которые будут использоваться для коммуникации с директориями облачного хранилища:
Переменная | Значение | Описание |
|---|---|---|
ROOT | /pdfClient/EndpointInfo.ClientInfo.Id | Путь до корневой директории |
TASKS | ROOT/pdfTask | Путь до директории с задачами |
RESOURCES | ROOT/pdfResource | Путь до директории с ресурсами |
Клиенты OneDrive и Dropbox реализуют интерфейс ICloud:
public interface ICloud
{
string Info(string path); // обращение к ресурсам объекта drive/получение метаданных для всех файлов
bool CreateFolder(string parent, string pathname); // создание директории в облачном хранилище
int Upload(string path, string filename, byte[] data); // загрузка файлов в облачное хранилище
byte[] Download(string fullpath, long size = 0L); // загрузка файлов с облачного хранилища
FileItem[] List(string path); // получение списка файлов определенной директории облачного хранилища
void Delete(string input); // удаление файла с облачного хранилища
void UpdateConfig(IConfig config); // обновление конфига (refresh, access tokens)
}
Примечательно, что на момент исследования каналы C2 реализованы только через сервисы OneDrive и Dropbox, в то время как в коде можно обнаружить, что EndpointInfo.ConfigType содержит куда больше значений:


Данный факт может свидетельствовать о том, что PhantomCloud находится в активной разработке, и в скором будущем мы увидим реализацию и других каналов взаимодействия, указанных в EndpointInfo.ConfigType. Также любопытно наличие значения MailRU, очевидно указывающего на российский облачный сервис VK Cloud, что говорит о дополнительном фокусе на RU-сегмент.
Конфигурация для взаимодействия с Dropbox API, используемая клиентом Dropbox:
private object EventArg;
private static readonly int CHUNKSIZE = 655360;
private readonly string api_url = "https://api.dropboxapi.com";
private readonly string content_url = "https://content.dropboxapi.com";
private readonly string token_path = "/oauth2/token";
private readonly string info_path = "/2/files/get_metadata";
private readonly string list_path = "/2/files/list_folder";
private readonly string create_v2_path = "/2/files/create_folder_v2";
private readonly string delete_v2_path = "/2/files/delete_v2";
private readonly string upload_path = "/2/files/upload";
private readonly string upload_session_start_path = "/2/files/upload_session/start";
private readonly string upload_session_append_v2_path = "/2/files/upload_session/append_v2";
private readonly string upload_session_finish_path = "/2/files/upload_session/finish";
private readonly string download_path = "/2/files/download";
private readonly string DROPBOX_API_ARG = "Dropbox-API-Arg";
private readonly HttpClient basic_client = new HttpClient(new HttpClientHandler
{
AllowAutoRedirect = false
});
private string upload_session_id = string.Empty;
Конфигурация для взаимодействия с OneDrive API, используемая клиентом OneDrive:
private object EventArg;
private static readonly int ChunkSize = 655360;
private readonly string token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private readonly string basic_url = "https://graph.microsoft.com/v1.0";
private readonly string info_path = "/drive/root{0}";
private readonly string children_path = "/drive/root{0}/children";
private readonly string upload_session_path = "/drive/root{0}/createUploadSession";
private readonly string content_path = "/drive/root{0}/content";
private readonly HttpClient basic_client = new HttpClient(new HttpClientHandler
{
AllowAutoRedirect = false
});
Также стоит упомянуть, что CloudClient реализует интерфейс ICloudSender и обладает следующим функционалом:
public interface ICloudSender
{
void SendTask(MessageBase message); // сериализация сообщения, архивирование его, генерация имени файла из 16 случайных символов, добавление расширения .resp и отправка в директорию TASKS облачного хранилища
void SendResource(string hash, Stream stream); // архивирование данных, отправка в директорию RESOURCES облачного хранилища с именем хеша файла
MessageBase[] ReceiveTasks(); // вызов метода List интерфейса ICloud на директорию TASKS облачного хранилища, поиск файлов с расширением .req, скачивание их из директории TASKS и десериализация в объект MessageBase
byte[] ReceiveResource(string hash); // загрузка файла с облачного хранилища из директории RESOURCES по хешу файла и разархивирование
void Close(); // отмена текущего токена (отмена контекста)
}
После завершения работы конструктора выполняется метод CloudClient.Start, запускающий клиент-серверное взаимодействие с центром удаленного управления:

Запускается бесконечный цикл, пока для данного токена не будет выполнен запрос на его отмену(по сути, являющийся контекстом, который можно будет отменить для корректного завершения потока выполнения). Далее клиент отправляет серверу heartbeat с информацией о текущем времени и клиенте, тем самым подтверждая свою активность:

Далее вызывается метод ReceiveTask, который в свою очередь вызывает ReceiveTasks, после чего происходит вызов метода List интерфейса ICloud (напоминаю, клиенты OneDrive и Dropbox реализуют интерфейс ICloud) для директории TASKS (директория с задачами) облачного хранилища, поиск файлов с расширением .req, скачивание их из директории TASKS, корректная десериализация в объект MessageBase (для этого как раз и нужна была регистрация всех дочерних типов), и уже полученные задачи добавляются в очередь:


Затем вызывается метод CloudClient.GetTaskRequest, который забирает задачи из очереди и отправляет их в метод MessageHandler.Process (описывал его тут).

После выполнения каждой задачи вызывается метод sendTask, который сериализует сообщение, архивирует его, генерирует имя файла из 16 случайных символов, добавляет расширение .resp и отправляет в облачное хранилище в директорию TASKS:


После того, как все задачи будут обработаны, программа уходит в таймаут на EndpointInfo.Interval*1000 (для данного образца Interval == 60, что в конечном итоге составляет 60 секунд).
Обход средств защиты
PhantomCloud в качестве обхода средств защиты использует метод Bypass.PatchAll, который патчит функции AmsiScanBuffer и AmsiOpenSession из amsi.dll и EtwEventWrite из ntdll.dll:



По сути, здесь используется классический патчинг и заключается в следующем:
Получаем адрес целевой библиотеки
Получаем адрес функции этой библиотеки
Устанавливаем защиту памяти региона страниц в значение 0x4 (PAGE_READWRITE)
Собственно, сам патч, которым перезаписываем первые байты функции своими инструкциями (заполнение регистра eax/rax нужными значениями, которые будут корректно обработаны при возврате из функции, а также непосредственный возврат из функции)
Возвращаем защиту памяти для регионов страниц в исходное значение
Патч для функции EtwEventWrite из ntdll.dll выглядит так:
x64_etw_patch = [72,51,192,195]
Опкоды:
48 33 C0 XOR RAX, RAX
C3 RET
x86_etw_patch = [51,192,194,20,0]
Опкоды:
33 C0 XOR EAX, EAX
C2 14 00 RET 0x14
Патч для функций AmsiScanBuffer и AmsiOpenSession из amsi.dll выглядит следующим образом:
x64_a1_patch = [184,87,0,7,128,195]
Опкоды:
B8 57 00 07 80 MOV EAX, 0x80070057
C3 RET
x86_a1_patch = [184,87,0,7,128,194,24,0]
Опкоды:
B8 57 00 07 80 MOV EAX, 0x80070057
C2 18 00 RET 0x18
где 0x80070057 - ошибка неверного параметра
На момент анализа эта функция в коде не используется, но вероятнее всего в будущем она будет задействована в одной из команд.
Заключение
PhantomCloud — технически зрелый .NET-бэкдор с широким набором функциональных возможностей и хорошо продуманной архитектурой. Анализ кода указывает на то, что проект находится в активной стадии развития, а выбранные архитектурные решения позволяют достаточно быстро наращивать функциональность за счёт высокой расширяемости. В частности, отчетливо прослеживаются планы по реализации взаимодействия через дополнительные облачные сервисы, при этом наличие канала с типом MailRU, очевидно указывающего на облачный сервис VK Cloud, свидетельствует о наличии дополнительного интереса к российскому сегменту.
Использование легитимных облачных сервисов в качестве каналов C2 всё чаще встречается в современных образцах вредоносного ПО. Подобные каналы, как правило, характеризуются меньшей заметностью в сетевом трафике и могут способствовать более продолжительному сохранению активности в скомпрометированной системе, усложняя процессы обнаружения и анализа.
IOCs
Files
LuckyStrike Agent
Имя файла | SHA-1 | Комментарий |
|---|---|---|
TrustFabric.dll | 8718cb8e617c61144387b44c3fa28624e57a472a | Загрузчик |
netfxsbs9.hkf | 3681140dba6b8ffdc78ad5fb73b5ca5288faa6f2 | Зашифрованная полезная нагрузка |
log.cached | b28dfd7bb495acb84f058e871cb52f07d7b87b82 | Зашифрованный файл конфигурации |
Yandex.exe | 532eb5886223e204230cc54986a4be52dafd7146 | Расшифрованная полезная нагрузка |
PhantomCloud
Имя файла | SHA-1 | Комментарий |
|---|---|---|
AppVStreamingUX.dll | 8f07818d96fe4c950610b5ab93baecd64a59ce1c | Загрузчик |
AppVStreamingUX.nlp | a8940e23be9a40246d7c0d1e2e34f1175550c9ec | Зашифрованная полезная нагрузка |
CloudPhantom.Client | e5658cba7e1591cbf029d94fc2a0b06953ec4c1e | Расшифрованная полезная нагрузка |
YARA
PhantomCloud
rule PhantomCloud {
meta:
description = "PhantomCloud .NET Client"
author = "John Doe"
strings:
$asm = "Phantom"
$ns = "CloudCore"
$pb = "ProtoBuf"
$enum = "ConfigType"
$e1 = "OneDrive"
$e2 = "Dropbox"
$e3 = "Mega"
$e4 = "CloudflareR2"
$e5 = "MailRU"
$e6 = "ProtonDrive"
condition:
uint16(0) == 0x5A4D and
$asm and
$ns and
$pb and
$enum and
all of ($e*)
}
