Введение

Ранее мы публиковали статью, посвященную APT-группировке Erudite Mogwai, в которой среди используемых группировкой утилит встречается бэкдор LuckyStrike Agent, замеченный в атаках на ИТ-инфраструктуру российской госорганизации и на ИТ-компании. В этой публикации мы разберем его подробнее.

Ключевые тезисы:

  • Lucky Strike Agent был замечен в атаках группировки Erudite Mogwai.
  • Бэкдор использует технику AppDomain Manager Injection, способен выполнять широкий спектр команд, а также использует публичный облачный сервис OneDrive в качестве канала C2. Не исключено также, что он может использовать и другие облачные сервисы.
  • Анализ LuckyStrike Agent позволяет с высокой долей уверенности отнести его к категории коммерческих решений. На это указывают такие факторы, как четкая модульная структура, аккуратная реализация, а также явные признаки "платной версии", обнаруженные в отладочных строках PDB-файла (E:\Csharp\HA_Thomas\Server\Library\obj\Paid Version\Library.pdb) и префикс "paid", добавляемый при генерации agent_id.

Бэкдор встречался нами в нескольких инцидентах и поставлялся со следующим набором файлов:

  • LuckyStrike.dll — загрузчик, является .NET-библиотекой. Расшифровывает и запускает основную нагрузку;
  • log.cached — зашифрованный конфиг основной нагрузки;
  • netfxsbs9.hkf — зашифрованная основная нагрузка;
  • UevAppMonitor.exe — легитимный файл, является частью технологии UE-V;
  • UevAppMonitor.exe.config — конфиг, используемый для инъекции вредоносной библиотеки.

Данное ВПО использует технику AppDomain Manager Injection (T1574.014). Она позволяет загружать вредоносные .NET-сборки в легитимные .NET-приложения. В качестве такого приложения злоумышленниками выбран UevAppMonitor.exe. Подробнее с техникой можно ознакомиться в публикации Rapid7.

Файл UevAppMonitor.exe.config содержит конфигурацию загрузки вредоносной сборки и выглядит следующим образом:

	
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<runtime>
	<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
	</assemblyBinding>
	<etwEnable enabled= "false" />
	<appDomainManagerAssembly value="LuckyStrike, Version=0.0.0.0, Culture=neutral,
	PublicKeyToken=null" />
	<appDomainManagerType value="Strike" />
	</runtime>
</configuration>
	

Техническое описание

Загрузчик LuckyStrike.dll

File name

LuckyStrike.dll

SHA256

0a61f81d4c24fdc2f7360c6b19b569055aa1f3adb6fc15d500a81fc9862c7a49

Compiler timestamp

Sun Jul 15 23:43:43 2091 | UTC

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

Структура классов LuckyStrike.dll
Структура классов LuckyStrike.dll

Точкой входа является класс Strike, который наследуется от класса AppDomainManager. В исходном коде встречается обфускация строк и значений.

При запуске библиотека выводит случайный текст для создания впечатления о начале игрового процесса (StrikeAI.Example).

Вывод LuckyStrike.dll
Вывод LuckyStrike.dll

Затем вызывается метод Dynamic.SetObject, в котором и происходит расшифровка полезной нагрузки.

Расшифровка полезной нагрузки
Расшифровка полезной нагрузки

Переменные key и path инициализируются с помощью метода ExtractSecretKeys, в котором возвращается захардкоженный b64 ключа и файла с нагрузкой. В нашем случае эти значения были равны netfxsbs9.hkf для имени файла с зашифрованной нагрузкой и UevAppMonitor — для ключа. Расшифровка осуществляется с помощью алгоритма AES256-CBC, размер ключа хранится в статической переменной CryptoHelper.KeySize. Зашифрованная нагрузка хранится в файле в виде b64, после декодирования считываются первые 16 байт, которые являются вектором инициализации и salt для вывода ключа. Ключ выводится с помощью PBKDF2, затем с помощью него осуществляется расшифровка нагрузки.

Полученная полезная нагрузка также представляет собой .NET-сборку, которая исполняется далее.

Основная нагрузка

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

Name

OneDrive.exe

SHA256

6f3b79f32625e568d48bd59887d3bd0b0648256fe24a3677ef6bef154d120118

Compiler timestamp

Thu Nov 24 04:26:15 2039 | UTC

Сборка представляет собой собранный с помощью Fody Costura бинарный файл. Fody Costura предназначен для сборки единого исполняемого файла, в котором все зависимости хранятся в ресурсах. В данном случае хранится библиотека library.dll (а также library.pdb), которая содержит основные классы вредоноса. Она используется основным исполняемым файлом.

Name

Library.dll

SHA256

94a16337fefbed7c77826731bf32f922f4ad8fa3a6ac19febcd4343f5a41a623

Compiler timestamp

Tue Nov 12 20:36:20 2047 | UTC

После запуска OneDrive.exe вызывается функция StealthOperations.ApplyHook, которая перезаписывает код функции Exit на инструкцию немедленного возврата. Вероятно, это сделано для предотвращения выхода из приложения. 

Установка хука на функцию Exit
Установка хука на функцию Exit

После этого осуществляется расшифровка файла конфигурации. Имя файла cached.log захардкожено. Алгоритм расшифровки следующий:

  • XOR с ключом "SecretKey".
  • Декодирование base64.
  • Расшифровка с помощью AES в режиме CBC. Ключ — "Thomas", IV — "". Ключ и вектор инициализации дополняется нулями до нужной длины.

На выходе получается JSON следующего вида:

	
{
   "ListenerID": 1,
  "FolderName": "Placerat est lacus libero nostra, neque, sodales.",
  "AppID": "ec839d60-ed1b-45e4-ab69-94e676532033",
  "RefreshToken": "M.C520_BAY.0.U.-Cr2nYDYyrNze!!A4*!a4*GQmpCGOIYewMoTTi4Wnml*myPBCp5jXVlkG5sfp1R5shXtq9*DXJ56yiRzzrt0L4d1lvzIWab78cFA*fRuPJURGOP4Nj*pgOQ1kjI!bceoUu!RO6Izgyuxqxj17*VHW7tllyPtDcsAejfGDZfVEN*388YVXwY5*RblcLaOCptklIA9zh262bfLFWSDnnfltAnM6XWMRwh5oIIrsR2*euPdZ7bfyPokPAD96U7eQF!CIJFvjtN7RNkvw3CHlwCEObd67HYDBgqRq2u06fJIwZAOIVYAJMlQNjGPMAC*F8uyeDyP0Cvd5z!Amih40J1hD5dU*IeNdUdfXsDA6CcJwcj8SnzLajDaY8GLzKbvn!ggLEw$$",
  "BaseUrl": "https://graph.microsoft.com/v1.0/drive",
  "TokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
  "CodeType": ".NET4.5.1",
  "AgentType": "OneDrive",
  "Scope": "offline_access files.readwrite",
  "Sleep": 86,
  "Proxy": "",
  "BeginDate": "00:00:01",
  "EndDate": "23:59:00",
  "Payload": {
    "Key": "pu043v2m746opqb1",
    "MetaDataName": null,
    "TaskFolderName": "Eros tempor himenaeos",
    "ReceiveFolderName": "Eu efficitur eros",
    "Prepend": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\&quot;&gt;\r\n&lt;html xmlns=\"http : //www.w3.org/1999/xhtml\&quot;&gt;\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;amp;clcid=0x409\&quot;&gt;&lt;img src=\"iisstart.png\" alt=\"IIS\" width=\"960\" height=\"600\" /></a>\r\n        </div>\r\n    </body>\r\n</html>",
    "PayloadPrepend": "Sagittis",
    "PayloadAppend": "Non"
  }
}

Эта конфигурация затем десериализуется в структуру 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 Mutex mutex = null;
	public static Profile profile = null;
	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 int ListenerID { get; set; }
	public string FolderName { get; set; }
	public string AppID { get; set; }
	public string RefreshToken { get; set; }
	public string BaseUrl { get; set; }
	public string TokenUrl { get; set; }
	public string CodeType { get; set; }
	public string AgentType { get; set; }
	public string Scope { get; set; }
	public int Sleep { get; set; }
	public string Proxy { get; set; }
	public string BeginDate { get; set; }
	public string EndDate { get; set; }
	public Payload Payload { get; set; }
}
	

Можно заметить, что переменные profile_name и logs заданы по умолчанию. Logs содержит путь, по которому будут записываться логи приложения. Пример лога (на момент тестирования срок действия токена уже закончился):

	
2024-09-03 00:52:13 [InitAppDomain] ERROR: Another instance of the agent is already running
2024-09-03 00:53:18 [InitAppDomain] ERROR: Another instance of the agent is already running
2024-09-03 00:53:26 [async_session_post] An error occurred while sending the request.
2024-09-03 00:53:26 [While1-] One or more errors occurred.
2024-09-03 00:54:52 [async_session_post] An error occurred while sending the request.
2024-09-03 00:54:52 [While1-] One or more errors occurred.
2024-09-03 01:00:18 [async_session_post] An error occurred while sending the request.
2024-09-03 01:00:25 [While1-] One or more errors occurred.
	

Также присутствует поле AgentType, которое во всех семплах имело значение OneDrive, однако само его наличие говорит о возможности использования и других облачных сервисов в качестве канала C2. В объекте Payload задаются имена папок для коммуникации, а также формат данных.

Не менее интересными являются поля RefreshToken и Scope. Первое используется для получения токена доступа, а второе сообщает о том, какие права необходимо предоставить токену доступа. Изучить инфраструктуру атакующих не удастся — при попытке получить токен доступа, система любезно сообщает нам о том, что время действия данного refresh token уже исчерпано.

После инициализации структуры Profile происходит генерация agent_id, который представляет собой MD5 от конкатенации строк "paid", AgentType, username (добавляет символ * после имени пользователя, если он из группы администраторов), значения processorID из win32_processor, физического адреса хоста.

Промежуточный результат формирования agent_id
Промежуточный результат формирования agent_id

Затем происходит инициализация мьютекса Global\\{AGENTID}, где {AGENTID} — рассчитанный для текущего пользователя agentid. Такой алгоритм вычисления agent_id позволяет запускать бэкдор на одном и том же компьютере в нескольких пользовательских сессиях.

Главный цикл и функциональность

После основной инициализации запускается главный цикл. Он начинается с функции Sleep со значением из конфига. Для данного семпла это значение равно 89 секундам. Основной задачей данного цикла является получение токена доступа OneDrive с использованием Refresh Token. После получения токена запускается отдельный поток, который обрабатывает очередь сообщений. Всего инициализируется две очереди: Input и Output для сообщений на вход и на выход.

Аутентификация и запуск потока обработки сообщений
Аутентификация и запуск потока обработки сообщений

В главном потоке, если текущее время укладывается в интервал [BeginDate; EndDate], происходит:

  1. Sleep.
  2. Сбор информации о системе (IP, имя пользователя, имя хоста, PWD, информация о текущем процессе, время запуска, последнее время активности, полное имя операционной системы, также записывается информация о типе текущего агента и используемой версии .NET). Эта информация записывается в структуру MainData, затем сериализуется и зашифровывается с помощью RSA, в котором значение public key представляет собой хардкод-строку md752US9s6TEQZjARaT+ZUZgOK7gJseLeFtJQGJCy2C1F0txZZAJXdY0WpPuiTNfxRmtxXFKf5AwdbH7SnsraLPn+PGkJrI35gXMDTJmezInbvgEWKZ84l9JYi9gm8mCUSad+N+3GAus/j+woCUhOgRrW496NLmX05KnIhgJc/E=.
  3. Отправка данных. Происходит вызов функции SendMetaData, в которой с помощью GraphQL в папке root/:/{FolderName}-{ListenerID}-{agent_id} создается файл с именем "Read_{agent_id}.max" с содержимым из пункта 1. Пример полного пути — "root/:/Placerat est lacus libero nostra, neque, sodales.-1-1dc2a35510e7d9cdb3a5ababfab781f7/Read_1dc2a35510e7d9cdb3a5ababfab781f7.max".
  4. Чтение новых задач из директории TaskFolderName. Вредонос получает список файлов внутри этой директории, а затем сравнивает их название по регулярному выражению (файлы должны иметь расширение .max). Затем содержимое каждого файла записывается в очередь задач (Input). После этого файл удаляется из хранилища OneDrive.
Главный цикл
Главный цикл

В потоке обработки очередей сначала вызывается функция обработки задачи из очереди Input. Из очереди достается сообщение, создается отдельный поток для обработки команды, а затем в нем вызывается функция UnGeneratePayload, которая достает из строки задачи полезную нагрузку, так как сама нагрузка заключена между значениями PayloadPrepend и PayloadAppend, указанными в конфигурации малвари:

	
public string UnGeneratePayload(string command)
{
	string payloadPrepend = Config.profile.Payload.PayloadPrepend;
	string PayloadAppend = Config.profile.Payload.PayloadAppend;
	string pattern = Regex.Escape(payloadPrepend) + "(.*?)" + Regex.Escape(PayloadAppend);
	Match match = Regex.Match(command, pattern);
	if (match.Success)
	{
		command = match.Groups[1].Value.Trim();
	}
	return command;
}
	

Затем содержимое сообщения декодируется с помощью base64 и расшифровывается с помощью AES в режиме CBC с ключом Payload.Key из конфигурации. Вектор IV при этом пустой. Сообщение представляет собой JSON со структурой MsgData:

	
{
	public string ID { get; set; }
	public MsgType MsgType { get; set; }
	public string Data { get; set; }
}
	

В значении MsgType находится тип команды, а в значении Data — необходимые для нее аргументы. Всего существует 11 типов команд:

Тип команды

Описание

CMD_TYPE_SHELL

Исполнение консольной команды. В очередь Output записывается результат этой команды

CMD_TYPE_EXEC_ASM

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

Сборка в поле Data закодирована в следующем виде:
"BASE64_ASSEMBLY|ARGS" (сперва следует base64 с самой сборкой, а после разделителя — аргументы запуска). Функция записывает в очередь Output статус запуска (успех/ошибка)

CMD_TYPE_EXIT

С функции Environment.Exit убирается хук и происходит ее вызов. Бэкдор прекращает свою работу. Не возвращает никаких значений

CMD_TYPE_REMOVE

Удалить файл. В очередь Output записывается информация о директории, из которой был удален файл

CMD_TYPE_DOWNLOAD

Скачать файл с хоста жертвы. Вывод в очередь Output происходит чанками по 7340032 байт. Формат вывода:

"FILEPATH|CHUNK_COUNT|CHUNK_INDEX|DATA_BASE64". Последним выводится сообщение об успешной передаче файла

CMD_TYPE_UPLOAD

Загрузка файла с C2. Содержимое также загружается чанками (содержится внутри сообщения в виде base64). Файл создается, только когда все чанки собраны. В Output записывается статус

CMD_TYPE_DRIVES

Получить информацию о дисках в системе (имя диска и объем)

CMD_TYPE_FILE_BROWSE

Обход директории. Возвращает информацию о файлах в директории или о файле (имя файла, полный путь, base64 от иконки, является ли путь директорией, время последней записи в файл, размер). Список файлов возвращается в формате JSON

CMD_TYPE_SLEEP

Изменить значение Sleep в конфигурации. В Output записывается сообщение об изменении значения со старого на новое

CMD_TYPE_TASKSCHEDULER

В коде программы не используется

CMD_TYPE_Plugin

Подгрузка и исполнение сборки с заданными аргументами. В команде также содержится BASE64-сборки и аргументы. От команды EXEC_ASM отличается форматом сборки. Сборка должна содержать в себе тип Plugin со статическим методом Run

Затем происходит обработка сообщения из очереди Output. Содержимое сообщения сериализуется в JSON и зашифровывается, кодируется в base64. Эта строка далее обрамляется значениями из конфигурации:

	
public string GeneratePayload(string command)
{
	string Prepend = Config.profile.Payload.Prepend;
	string Append = Config.profile.Payload.Append;
	string PayloadPrepend = Config.profile.Payload.PayloadPrepend;
	string PayloadAppend = Config.profile.Payload.PayloadAppend;
	command = string.Concat(new string[]
	{
		Prepend,
		"\r\n\t",
		PayloadPrepend,
		command,
		PayloadAppend,
		"\r\n",
		Append
	});
	return command;
}
	

После этого в директории ReceiveFolderName создается файл с расширением .max с содержимым вывода. Имя файла формируется на основе текущих даты и времени, смещенных на дату 1962-07-07 08:08:08.008. Затем эта разница преобразуется в секунды и умножается на 100000.

Заключение

LuckyStrike Agent — это многофункциональный .NET-бэкдор, использующий технику AppDomain Manager Injection для загрузки вредоносной нагрузки в адресное пространство легитимного .NET-приложения. Мы сталкивались с этим инструментом в ходе расследований, связанных с активностью APT-группировки Erudite Mogwai.

Известно, что азиатские APT-группировки нередко адаптируют под себя общедоступные инструменты или обмениваются наработками внутри условно «закрытого круга» — как это, например, происходит в случае с ShadowPad, к которому, по всей видимости, имеют доступ сразу несколько китайских групп. На этом фоне бэкдор LuckyStrike Agent выглядит менее типично. Его архитектура и методы внедрения позволяют предположить, что это — продукт коммерческого происхождения, ориентированный на ограниченный круг клиентов. На это указывают такие признаки, как четкая модульная структура, а также наличие элементов, свидетельствующих о возможном существовании «платной версии».

В частности, в отладочных строках обнаружено имя PDB-файла:

E:\Csharp\HA_Thomas\Server\Library\obj\Paid Version\Library.pdb.

Это может свидетельствовать о внутреннем названии проекта ("HA_Thomas") и существовании платной редакции бэкдора. Дополнительно при генерации agent_id к значению добавляется префикс paid, что также намекает на коммерческую природу продукта.

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

Если вы заинтересованы в непрерывном получении разведданных о деятельности группировки Erudite Mogwai и других актуальных киберугрозах, свяжитесь с нами, чтобы подключить пилот сервиса Solar TI Feeds.

IOCs

Files

0a61f81d4c24fdc2f7360c6b19b569055aa1f3adb6fc15d500a81fc9862c7a49
d676a8bbaf95612305efa9500356294ec14d332baa16ed4980e4c47a8d4b831b
6f3b79f32625e568d48bd59887d3bd0b0648256fe24a3677ef6bef154d120118
fd647324461f4114d258102ccafaddf4a50041f7d5425f62009b89395166d588
94a16337fefbed7c77826731bf32f922f4ad8fa3a6ac19febcd4343f5a41a623
0d02ebe3b7c366b8458e5518d7424befb1ecd85117c769b880b59fa979e8408b
26ac58cd3addf226ebcc1173f0b237942176ed7cf311e0ff8bfa6bf5c6ea80e8
89fcea271d9a647aab772bf13407407b4f93ab3926f7feef68e13a1bbbf7fc4d
efa406b93ccb39704c2cbdbe5e49611777b17786834b416d1fb7256d3d914452
89fcea271d9a647aab772bf13407407b4f93ab3926f7feef68e13a1bbbf7fc4d
e4cb17978d132ab8518cfbd6362cbfa7f281cacf1b79eead24286ad7b5b7b94d
c97d88ea4b6592b42bc6b1b9d890c5f7c498b82741a57d4a634bae02b9bc3048

Yara

	
	
rule LuckyStrike {
    meta:
        description = "LuckyStrike loader"
    strings:
        $class_name1 = "Card"
        $class_name2 = "ArtificialPlayer"
        $class_name3 = "OnlinePlayer"
        $class_name4 = "NonEmptySeat"
        $class_name5 = "EmptySeat"
        $class_name6 = "Game"
        $class_name7 = "Hand"
        $str1 = "LuckyStrike"

    condition:
        all of them

}
	

Приложение 1. Расширенная информация по файловым IOCs

Имя файла

SHA256

Комментарий

LuckyStrike.dll

0a61f81d4c24fdc2f7360c6b19b569055aa1f3adb6fc15d500a81fc9862c7a49

Загрузчик

log.cached

 

d676a8bbaf95612305efa9500356294ec14d332baa16ed4980e4c47a8d4b831b

Зашифрованный файл конфигурации

OneDrive.exe

6f3b79f32625e568d48bd59887d3bd0b0648256fe24a3677ef6bef154d120118

Расшифрованная основная нагрузка

netfxsbs9.hkf

fd647324461f4114d258102ccafaddf4a50041f7d5425f62009b89395166d588

Зашифрованная основная нагрузка

Library.dll

94a16337fefbed7c77826731bf32f922f4ad8fa3a6ac19febcd4343f5a41a623

 

LuckyStrike.dll

0d02ebe3b7c366b8458e5518d7424befb1ecd85117c769b880b59fa979e8408b

Загрузчик

log.cached

26ac58cd3addf226ebcc1173f0b237942176ed7cf311e0ff8bfa6bf5c6ea80e8

Зашифрованный файл конфигурации

OneDrive.exe

89fcea271d9a647aab772bf13407407b4f93ab3926f7feef68e13a1bbbf7fc4d

Расшифрованная основная нагрузка

netfxsbs9.hkf

efa406b93ccb39704c2cbdbe5e49611777b17786834b416d1fb7256d3d914452

Зашифрованная основная нагрузка

LuckyStrike.dll

89fcea271d9a647aab772bf13407407b4f93ab3926f7feef68e13a1bbbf7fc4d

Загрузчик

LuckyStrike.dll

e4cb17978d132ab8518cfbd6362cbfa7f281cacf1b79eead24286ad7b5b7b94d

Загрузчик

LuckyStrike.dll

c97d88ea4b6592b42bc6b1b9d890c5f7c498b82741a57d4a634bae02b9bc3048

Загрузчик