Хабр, привет! Меня зовут Дмитрий, я занимаюсь расследованием и реагированием на инциденты информационной безопасности. В ходе недавнего расследования инцидента, связанного с деятельностью 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 (добавляет символ * после имени пользователя, если он из группы администраторов) и физического адреса хоста:

Формирование agent_id
Формирование agent_id

В остальном каких-то существенных изменений обнаружено не было.

Расшифрованная конфигурация в случае с 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&amp;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:

  1. Полезная нагрузка расшифровывается алгоритмом AES в режиме CBC. С помощью PBKDF2 выполняется формирование ключа, а вектором инициализации выступают первые 16 байт основной полезной нагрузки;

  2. Запуск расшифрованной вредоносной .NET сборки в контексте текущего процесса.

Единственное отличие состоит в том, что полезная нагрузка не была дополнительно закодирована в base64.

Технические детали

Структура классов

Структура классов PhantomCloud
Структура классов PhantomCloud

Видно, что имя сборки имеет название CloudPhantom.Client, а классы находятся в пространстве имен CloudCore, вследствие чего ему и было решено дать название PhantomCloud.

Несмотря на схожесть наименования исследуемого вредоносного ПО с названиями инструментов, используемых одной небезызвестной APT-группировкой, на текущий момент отсутствуют какие-либо данные, позволяющие утверждать наличие заимствования или прямой связи с ней. Дальнейший анализ в части атрибуции отдадим на откуп TI-аналитикам.

Инициализация приложения

Метод Program.Main является точкой входа WinForms-приложения и выполняет базовую настройку среды выполнения:

  1. Задаётся используемая версия TLS для всех сетевых соединений в рамках процесса;

  2. Регистрируется обработчик необработанных исключений, позволяя централизованно обрабатывать сбои и предотвращать отображение стандартных диалогов об ошибках;

  3. Запускается цикл обработки сообщений Windows с формой CloudApp в качестве основной.

Точка входа
Точка входа

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

Переопределение параметров загрузки
Переопределение параметров загрузки

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

Основная логика запуска PhantomCloud
Основная логика запуска PhantomCloud

Разберем каждый этап запуска основной логики поочередно.

Этапы запуска основной логики PhantomCloud

Этап 1. Инициализация конфигурации

Метод Settings.Initialize выполняют инициализацию или загрузку конфигурации приложения:

Инициализация или загрузка конфигурации приложения
Инициализация или загрузка конфигурации приложения

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

Примечательно, что для сериализации данных приложение использует Protocol Buffers (Protobuf), что позволяет эффективно упаковывать структурированные данные в компактный бинарный формат.

Структура EndpointInfo после десериализации данных из configData
Структура EndpointInfo после десериализации данных из configData

Таким образом, 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);

  • Права пользователя и принадлежность к группам.

Обогащенная структура EnpointInfo.ClientInfo
Обогащенная структура EnpointInfo.ClientInfo
Фрагмент метода EndpointInfo.GetInfo
Фрагмент метода EndpointInfo.GetInfo

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

Этап 2. Эвристический анализ на запуск в песочнице

Метод Sensibility.Calculate выполняет эвристический анализ для того, чтобы определить, запущен ли PhantomCloud в песочнице или нет:

Вызов метода Sensibility.Calculate
Вызов метода Sensibility.Calculate

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

Проверка количества запущенных браузеров на хосте
Проверка количества запущенных браузеров на хосте
Проверка дисков на признаки, характерные для песочниц или виртуальных окружений.
Проверка дисков на признаки, характерные для песочниц или виртуальных окружений.
Обнаружение автоматически сгенерированных имен пользователей
Обнаружение автоматически сгенерированных имен пользователей

Если по всем проверкам в сумме набирается 5 и выше баллов, приложение закрывается с ошибкой.

Этап 3. Создание мьютекса

На данном этапе происходит инициализация мьютекса с именем EndpointInfo.ClientInfo.Id (вычисленный MD5 от конкатенации строк) с целью предотвращения повторного запуска вредоносного ПО в текущей сессии пользователя.

Этап 4. Регистрация обработчиков событий

Метод MessageHandler.Register принимает в качестве аргумента интерфейс IMessageProcessor. Это означает, что все регистрируемые обработчики — FileManagerHandler, CodeTaskHandler, ConfigHandler и ClientHandler — реализуют данный интерфейс, а сам метод отвечает за их регистрацию.

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

Метод CanExecute проверяет соответствие типа объекта (передан в качестве аргумента в функцию) одному из указанных типов:

Метод CanExecute для обработчика FileManagerHandler
Метод CanExecute для обработчика FileManagerHandler

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

Метод Execute обработчика FileManagerHandler и типа PsFileDeleteRequest
Метод Execute обработчика FileManagerHandler и типа PsFileDeleteRequest

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

MessageHandler.Process
MessageHandler.Process

Ниже приведена функциональность каждого обработчика:

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

Выполнение команд в зависимости от PsCodeTaskRequest.Code
Выполнение команд в зависимости от PsCodeTaskRequest.Code

ConfigHandler — обработчик изменения конфигурационного файла PhantomCloud

Тип объекта

Описание

PsConfigEditRequest

Изменение типа канала взаимодействия (на момент исследования: OneDrive или Dropbox)

PsConfigGetRequest

Получение конфигурационного файла

PsConfigIntervalRequest

Установка интервала выполнения запросов к C2 (beacon)

PsConfigDisableRequest

Установка значение даты, до наступления которой PhantomCloud переходит в режим сна (sleep)

ClientHandler — обработчик управления PhantomCloud

Тип объекта

Описание

PsClientCloseRequest

Закрытие соединения

PsClientUninstallRequest

Создание и запуск .bat скрипта, который удаляет с системы PhantomCloud

PsClientUpdateRequest

Загрузка новой версии PhantomCloud с облачного хранилища, после чего создаётся и запускается .bat‑скрипт обновления, предназначенный для замены ранее установленной версии PhantomCloud

Скрипт удаления PhantomCloud:

Создание .bat скрипта, удаляющего с клиент PhantomCloud
Создание .bat скрипта, удаляющего с клиент PhantomCloud

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

Создание .bat скрипта, обновляющего клиент PhantomCloud
Создание .bat скрипта, обновляющего клиент 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:

Конструктор CloudClient
Конструктор CloudClient

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

Регистрация всех типов, унаследованных от MessageBase
Регистрация всех типов, унаследованных от 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 содержит куда больше значений:

Enum EndpointInfo.ConfigType
Enum 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, запускающий клиент-серверное взаимодействие с центром удаленного управления:

Клиент-серверное взаимодействие с C2
Клиент-серверное взаимодействие с C2

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

Отправка heartbeat
Отправка heartbeat

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

Вызов ReceiveTasks и добавление новых задач в очередь
Вызов ReceiveTasks и добавление новых задач в очередь
Получение задач
Получение задач

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

CloudClient.GetTaskRequest
CloudClient.GetTaskRequest

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

Выполнение задачи PsFileUploadRequest
Выполнение задачи PsFileUploadRequest
Отправка результата выполнение задачи PsFileUploadRequest в облачное хранилище
Отправка результата выполнение задачи PsFileUploadRequest в облачное хранилище

После того, как все задачи будут обработаны, программа уходит в таймаут на EndpointInfo.Interval*1000 (для данного образца Interval == 60, что в конечном итоге составляет 60 секунд).

Обход средств защиты

PhantomCloud в качестве обхода средств защиты использует метод Bypass.PatchAll, который патчит функции AmsiScanBuffer и AmsiOpenSession из amsi.dll и EtwEventWrite из ntdll.dll:

метод PatchAll
метод PatchAll
метод Patch
метод Patch
Список патчей
Список патчей

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

  • Получаем адрес целевой библиотеки

  • Получаем адрес функции этой библиотеки

  • Устанавливаем защиту памяти региона страниц в значение 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*)
}