Содержание
- Введение
- Начальный вектор
- MataDoor. Описание бэкдора
- 1. Запуск и закрепление
- 2. Общая архитектура
- 3. Плагины
- 4. Асинхронные операции
- 5. Механизм сериализации
- 6. Общая схема работы бэкдора
- 7. Отложенные команды
- 8. Описание плагинов
- 9. Архитектура сетевой подсистемы
- 9.1. Обзор компонентов транспортной подсистемы
- 9.2. Создание транспортных интерфейсов и соединений. Конфигурация соединений
- 9.3. Конкретные транспорты
- 9.4. Транспорты прикладного уровня
- Заключение
- Приложения
Введение
В октябре 2022 года в ходе расследования инцидента командой PT CSIRT на одном из российских промышленных предприятий были обнаружены образцы ранее не встречавшегося вредоносного программного обеспечения, функционирующего на скомпрометированных элементах инфраструктуры организации. Имена исполняемых файлов этого вредоноса были похожи на имена легального ПО, установленного на зараженных устройствах; ряд образцов имел действительную цифровую подпись. Также выявленные исполняемые файлы и библиотеки были обработаны протектором Themida для усложнения их анализа и обнаружения.
Последующий анализ указанных образцов показал, что выявленное ПО представляет собой достаточно сложный модульный бэкдор, названный нами MataDoor, ориентированный на долговременное скрытое функционирование в скомпрометированной системе.
Начальный вектор
Предполагаем, что начальным вектором внедрения бэкдора в скомпрометированную систему было фишинговое письмо с вложением — документом в формате DOCX, содержание которого относилось к сфере деятельности атакованного предприятия. Документ содержал в себе эксплойт для уязвимости CVE-2021-40444. Полную цепочку атаки восстановить не удалось, однако корреляция времени начала активности бэкдора и времени отправки письма позволяет сделать предположение, что источником внедрения бэкдора стал именно вредоносный документ.
Этот случай не является единичным. Похожие письма, содержащие документы с эксплойтами для уязвимости CVE-2021-40444, рассылались на российские предприятия оборонно-промышленного комплекса в августе-сентябре 2022 года. Часть из них по содержанию относилась к сфере деятельности атакованных предприятий, часть была составлена так, чтобы просто привлечь внимание адресата. При этом все письма побуждали пользователя активировать режим редактирования документа, что является необходимым условием для отработки эксплойта.
Ниже приведены примеры упомянутых документов, посвященных области деятельности предприятия:
Оформление, в котором выполнены приведенные документы, побуждает пользователя включить режим редактирования и сменить цвет шрифта на более контрастный. При включении режима редактирования происходит загрузка и выполнение вредоносной полезной нагрузки с контролируемого атакующими ресурса вследствие эксплуатации уязвимости CVE-2021-40444. Саму полезную нагрузку получить не удалось, так как на момент начала расcледования серверы с ней были недоступны.
Документы, нацеленные на привлечение внимания адресата, имели такой вид:
При анализе свойств вышеупомянутых документов выявлено, что URL, с которого загружается полезная нагрузка эксплойта, закодирован в формате HTML encoding:
Отметим, что в ходе анализа массива документов, эксплуатирующих уязвимость CVE-2021-40444, использование указанного способа кодирования URL полезной нагрузки встречалось только в приведенных документах, что позволяет предположить, что эти рассылки могут быть реализованы одним инициатором.
Имеются определенные сходства данной рассылки с фишинговыми письмами, зафиксированными в сентябре 2021 года, в адрес российских оборонных предприятий. Сведения о них опубликовала компания Malwarebytes (malwarebytes.com/blog/news/2021/09/mshtml-attack-targets-russian-state-rocket-centre-and-interior-ministry). В отчете компании упоминалась рассылка с приложением в виде документа в формате DOCX, якобы направленная отделом кадров предприятия с требованием заполнить приведенную в документе таблицу:
При активации режима редактирования документа происходила эксплуатация упомянутой уязвимости CVE-2021-40444 с последующим выполнением вредоносного содержимого.
В документах от сентября 2021 года также была использована кодировка HTML encoding для кодирования URL c полезной нагрузкой:
Один и тот же способ кодирования URL полезной нагрузки вредоносного документа позволяет сделать предположение, что описанные выше рассылки 2021 и 2022 года реализованы одним и тем же инициатором. В пользу этого также свидетельствуют следующие факты:
- полезная нагрузка в рассылках от сентября 2021 года и некоторые экземпляры бэкдора MataDoor, выявленные в 2022 году, были подписаны сертификатами Sectigo;
- доменные имена серверов с полезной нагрузкой всех упомянутых выше вредоносных документов зарегистрированы в регистраторе namecheap. Там же зарегистрированы доменные имена С2 MataDoor;
- объектами всех описанных атак стали российские предприятия оборонно-промышленного комплекса.
Элементы сетевой инфраструктуры, использованной при атаках на разные организации, не имеют пересечений между собой. Информация о реквизитах, на которые зарегистрированы домены, скрыта.
Некоторые сведения о характеристиках бэкдора MataDoor был опубликованы «Лабораторией Касперского» в разделе Southeast Asia and Korean Peninsula отчета APT trends report Q2 2023. В этом отчете данный бэкдор, названный MATAv5, связывался с активностью группировки Lazarus. Нам в ходе проведенного исследования использованной сетевой инфраструктуры не удалось однозначно атрибутировать автора этого инструмента; в связи с этим мы присвоили группировке, инициировавшей атаку, наименование Dark River — по имени River, указанному в поле Author некоторых упомянутых выше фишинговых документов.
MataDoor. Описание бэкдора
1. Запуск и закрепление
Бэкдор представляет собой DLL, которая, помимо стандартной функций DllMain, экспортирует функцию DllRegisterServer — она позволяет запускать полезную нагрузку при вызове через стандартную программу Windows Regsvr32, выполняющую регистрацию элементов управления OLE в системном реестре. Фактически обе функции — DllMain и DllRegisterServer — выполняют одни и те же действия, инициализируя бэкдор и запуская основную логику его выполнения в отдельном потоке.
Однако во всех известных случаях бэкдор выполняется с помощью дополнительного компонента loader service — службы Windows, которая выполняется автоматически при запуске системы, за счет чего обеспечивается в том числе и закрепление в системе. Loader service не выполняет никаких действий, кроме запуска DLL c бэкдором, путь к которой задается в его конфигурации. Конфигурация загрузчика, как и конфигурация самого бэкдора, зашифрована алгоритмом AES в режиме Cipher Feedback (CFB).
Во всех исследованных образцах в качестве ключа расшифровки конфигурации применяется последовательность 29 23 BE 84 E1 6C D6 AE 52 90 49 F1 F1 BB E9 EB, которая представляет собой последовательность из первых 16 байт, сгенерированных линейным конгруэнтным генератором X[n] = (0x343fd × X[n-1] + 0x269ec3) mod 0xffffffff, где в качестве seed (X[0]) использовано число 1. Данный генератор соответствует реализации функции rand() из библиотеки стандартных функций языка C, реализованной Microsoft (Microsoft C Runtime).
Стоит отметить что компоненты данного ВПО имеют характерные имена компиляции, загрузчики — loader_service_raw_win_intel_64_le_RELEASE.dll, бэкдоры — MATA_DLL_DLL_PACK_**********_win_intel_64_le_RELEASE.dll, где на месте ********** указывается некоторая дата. Исследованы варианты бэкдоров со следующими именами компиляции:
MATA_DLL_DLL_PACK_20221013_011_win_intel_64_le_RELEASE.dll,
MATA_DLL_DLL_PACK_20221006_011_win_intel_64_le_RELEASE.dll,
MATA_DLL_DLL_PACK_20221013_011_win_intel_64_le_RELEASE.dll.
Из-за наличия строки «MATA» в имени компиляции бэкдору было дано наименование MataDoor.
Как уже было отмечено ранее, имена исполняемых файлов загрузчика и бэкдора подбираются атакующим при установке данного вредоносного ПО отдельно для каждого скомпрометированного узла, чтобы мимикрировать под имена файлов легитимного программного обеспечения. Например, если в системе присутствует легитимный исполняемый файл с именем 1cv8.dll, имя файла бэкдора может иметь вид 1cv8u.dll. Схожесть имен загрузчика и бэкдора отмечается во многих случаях, например: dlpsvc.dll — имя файла загрузчика, dlpsvcHelper.dll — имя файла бэкдора.
Как было указано выше, часть экземпляров бэкдора и загрузчика были подписаны сертификатами Sectigo:
2. Общая архитектура
Исследуемый образец представляет собой модульный бэкдор, который состоит из ядра (оркестратора) и модулей (плагинов). Ядро включает в себя следующую функциональность:
- предоставление механизма сериализации данных, для чего используется свой, кастомный формат;
- работа с конфигурацией бэкдора;
- прием и обработка команд от управляющего сервера (С2);
- предоставление механизма выполнения асинхронных операций;
- работа с плагинами.
Рассмотрим каждую из этих компонент подробнее.
3. Плагины
Плагины (модули), используемые бэкдором, делятся на две категории:
- функциональные, реализующие команды с С2, которые может выполнять бэкдор;
- транспортные (сетевые), реализующие какой-либо сетевой протокол.
Плагины могут иметь вид отдельных PE-модулей или быть статически скомпонованными с бэкдором, «встроенными». В ходе исследования плагинов, имеющих вид отдельных модулей, не обнаружено; изучались только «встроенные». Каждый плагин имеет свой идентификатор, уникальный в рамках множества плагинов одной и той же категории. То есть идентификаторы транспортного и функционального плагина могут совпадать, однако, например, идентификаторы двух различных транспортных плагинов совпадать не должны. Идентификатор используется при регистрации плагина в ядре, а также при его выгрузке. Сведения о текущих зарегистрированных плагинах поддерживаются оркестратором в виде двух списков, один из которых используется для сетевых плагинов, другой для функциональных. Рассмотрим каждую из этих категорий отдельно.
Функциональные плагины
Каждый функциональный плагин независимо от своей формы должен регистрироваться в контексте оркестратора. Оркестратор поддерживает список всех подобных плагинов, каждый элемент которого представляет собой следующую структуру:
typedef struct { int unknown; // данное поле не использовалось в коде. int pluginId; // идентификатор плагина void* pluginModulePtr; // указатель на загруженный PE-модуль плагина. UNLOAD_PROC unloadProc; // указатель на процедуру выгрузки плагина PluginInfo *next; } PluginInfo;
Поле pluginModulePtr содержит указатель на загруженный PE-модуль плагина по аналогии с дескриптором модуля, возвращаемого вызовом LoadLibrary. Для «встроенных» плагинов данное поле равно 0. Помимо самого плагина, в оркестраторе должны регистрироваться также обработчики команд от С2, которые этот плагин реализуют. Каждая из команд, а также соответствующий обработчик имеет свой идентификатор, уникальный в рамках всего множества команд, реализуемых бэкдором. Плагин регистрирует реализуемые им обработчики команд самостоятельно, вызывая соответствующий API-метод, который предоставляет оркестратор. Эти действия осуществляются в процедуре инициализации плагина. Кроме того, процедура инициализации плагина сообщает оркестратору его идентификатор и адрес процедуры выгрузки. Пример такой процедуры инициализации представлен ниже:
int __fastcall Plugins::Bridge::Init(ORCHESTRATOR *ctx, int *moduleId, void *unloadProc) { if ( (ctx->id - 7) > 0x3E0 ) return ERR_PROC_NOT_FOUND; if ( moduleId ) { Plugins::Bridge::PluginId = *moduleId; *moduleId = 5; } if ( unloadProc ) *unloadProc = Plugins::Bridge::Unload; ctx->RegisterCommandHandler(ctx, 500, Plugins::Bridge::RunBridgedClients); ctx->RegisterCommandHandler(ctx, 502, Plugins::Bridge::RunAuthServer); ctx->RegisterCommandHandler(ctx, 505, Plugins::Bridge::RunRawServer); return 0; }
Здесь представлена процедура инициализации плагина с идентификатором 5, который предоставляет возможность экземпляру бэкдора работать в качестве промежуточного узла при передаче данных между другими экземплярами бэкдора. Эта функциональность реализуется с помощью команд с идентификаторами 500, 502, 505.
Обработчик команды получает на вход указатель на контекст оркестратора, параметры в сериализованном виде (подробнее о сериализации — в соответствующем разделе), а также указатель на переменную для записи статуса выполнения команды или кода ошибки:
SerializedData *__fastcall CommandHandler( ORCHESTRATOR *ctx, SerializedData *parameters, int *pStatus);
Результат выполнения команд обработчик возвращает в сериализованном виде. Состав сериализованных элементов специфичен для каждой команды.
Процедура выгрузки плагина освобождает ресурсы, использованные плагином, а также удаляет реализуемые им команды из списка актуальных, также путем вызова соответствующей функции API оркестратора. Пример такой функции для вышеупомянутого модуля представлен ниже:
int __fastcall Plugins::Bridge::Unload(ORCHESTRATOR *ctx) { ctx->UnregisterCommandHandler(ctx, 500); ctx->UnregisterCommandHandler(ctx, 502); ctx->UnregisterCommandHandler(ctx, 505); return 0; }
Транспортные (сетевые) плагины
Все сетевые протоколы, используемые бэкдором, реализованы в транспортных плагинах. Каждый из таких плагинов также имеет свой уникальный идентификатор, по которому он регистрируется в оркестраторе или выгружается. Сведения об используемых сетевых плагинах оркестратор хранит в отдельном списке, элементы которого имеют следующий вид:
typedef struct { int moduleId; // идентификатор плагина int unknown1; // всегда равен 0 int factoryId; // идентификатор фабрики сетевых соединений. int unknown2; // не используется void* moduleBase; // указатель на PE-модуль плагина void* unloadProc; // указатель на процедуру выгрузки плагина NetworkPluginInfo* next; } NetworkPluginInfo;
Помимо непосредственно идентификатора сетевого модуля (moduleId) для регистрации необходим также идентификатор существующей и зарегистрированной в оркестраторе фабрики сетевых соединений, в рамках которой данный плагин будет функционировать (параметр factoryId). Это дает возможность фабрике создавать новые соединения с использованием протокола, реализуемого данным транспортным плагином.
Если транспортный плагин является «встроенным», то поле moduleBase равно 0.
Транспортный плагин может реализовывать несколько вариантов сетевого протокола (например, HTTP и HTTPS). При этом каждый реализуемый вариант протокола (или транспорт в контексте данного отчета) должен иметь свой строковый идентификатор (например, tcp, tcp6), по которому он задается в строке конфигурации сетевого соединения (подробнее — в разделе «Обзор транспортной подсистемы»). Соответствие между строковым идентификатором и процедурой создания транспорта регистрируется в соответствующей фабрике процедурой инициализации транспортного плагина с помощью API, предоставленного бэкдором. Ниже представлен пример процедуры инициализации сетевого плагина, реализующего транспорты http/https:
int __fastcall HttpTransport::InitProc( NETWORK_TRANSPORT_FACTORY **factories, int factoryId, _DWORD *pStatus, _QWORD *unloadProc) { NETWORK_TRANSPORT_FACTORY *factory; // rbx factory = *factories; if ( !*factories ) return ERR_BAD_COMMAND; while ( factory->identifier != factoryId ) { factory = factory->next; if ( !factory ) return ERR_BAD_COMMAND; } if ( !HttpTransport::GetTransportDescriptorStatus ) CreatePipeNoBlockingMode(&HttpTransport::ControlPipeRead, &HttpTransport::ControlPipeTransportEndpoint, 0i64, 0); HttpTransport::GetTransportDescriptorStatus = factory->GetTransportDescriptorsStatus; factory->RegisterTransportCreationRoutines(factory, aHttp, HttpTransport::HttpTransport); factory->RegisterTransportCreationRoutines(factory, aHttps, HttpsTransport::HttpsTransport); HttpTransport::RegisterProxyTransportFactories(factory); if ( pStatus ) *pStatus = 1; if ( unloadProc ) *unloadProc = HttpTransport::Unload; return 0; }
Процедура выгрузки плагина удаляет из фабрики функции создания транспорта и освобождает использованные ресурсы. Пример такой процедуры для сетевого плагина http:
int __fastcall HttpTransport::UnregisterFactory(NETWORK_TRANSPORT_FACTORY **factoriesList, int factoryId) { NETWORK_TRANSPORT_FACTORY *factory; // rbx factory = *factoriesList; if ( !*factoriesList ) return ERR_BAD_COMMAND; while ( factory->identifier != factoryId ) { factory = factory->next; if ( !factory ) return ERR_BAD_COMMAND; } factory->DeleteTransportCreationInfo(factory, "http"); factory->DeleteTransportCreationInfo(factory, "https"); factory->DeleteTransportCreationInfo(factory, "proxy_http"); factory->DeleteTransportCreationInfo(factory, "proxy_https"); return HttpTransport::FinalizeProcessingConnections(); }
4. Асинхронные операции
Команды, выполнение которых занимает продолжительное время, реализуются асинхронно, в отдельном потоке. Данные обо всех текущих асинхронных операциях хранятся в бэкдоре в отдельном глобальном (в рамках экземпляра бэкдора) списке. Сведения об асинхронной операции добавляются в список перед ее запуском и удаляются по ее окончании. Сведения об операции имеют следующий вид:
typedef struct { int state; // состояние: 1 - выполняется, 2 - завершено int pluginID; // ID функционального плагина, выполняющего операцию int unknown1; int workerThreadId; // ID потока, выполняющего операцию HANDLE hWorkerThread; // handle потока, выполняющего операцию long startTime; // время начала операции в формате Unix timestamp int unknown2; int operationId; // идентификатор операции int unknown3; char* operationDescription; // при выполнении не используется, предположительно описание операции void* acyncOperationRun; // функция, непосредственно выполняющая операцию void* asyncOperationFinalize; // функция для освобождения ресурсов AsyncOperationBasicContext *next; } AsyncOperationBasicContext;
Каждая асинхронная операция имеет свой идентификатор — operationId, который никак не связан с идентификатором команды, выполняющей операцию. Одновременно может выполняться несколько операций с одним и тем же operationId. В контексте асинхронной операции обязательно указывается идентификатор плагина (pluginID), который ее выполняет, чтобы избежать выгрузки плагина в момент выполнения им асинхронной операции — в этом случае она завершится.
5. Механизм сериализации
В рассмотренном бэкдоре широко используется собственная система сериализации данных, которая применяется например для хранения конфигураций как самого бэкдора, так и сетевых соединений, а также для передачи команд и результатов их выполнения. Работа с ними осуществляется через API, предоставляемым ядром бэкдора.
Сериализованная единица данных имеет следующую структуру:
[размер данных: 4 байта, big endian] [ключ: 4 байта, big endian] [сериализованные данные]
Сериализованные единицы данных могут объединяться между собой в массивы, и в таком случае они рассматриваются с точки зрения бэкдора как единая сущность.
Ключ сериализации представляет собой двойное слово. Старшее слово содержит тип сериализованных данных, младшее — идентификатор единицы данных. В одном и том же массиве может содержаться несколько единиц с идентичными ключами. В этом случае доступ к ним осуществляется по дополнительному индексу.4
Всего используется 14 возможных типов данных, от 0x01 до 0x0E. На основании размера хранимых данных типы можно разделить на следующие группы:
- 0x01, 0x06 — BYTE;
- 0x02, 0x07 — WORD;
- 0x03,0x08, 0x0B — DWORD;
- 0x04, 0x09, 0x0A — QWORD;
- 0x05, 0xC, 0xD, 0xE — массив байтов.
Все значения с фиксированным размером (WORD, DWORD, QWORD) сериализованы в big endian.
Из контекста использования можно вывести назначение некоторых типов:
- 0x1 — не встречался в исследованном коде;
- 0x2 — номер порта сетевого соединения;
- 0x3 — единица хранения размером 4 байта, без специфических свойств;
- 0x4 — смещение относительно какой-либо величины, например относительно начала файла;
- 0x5 — криптографические данные (открытые ключи, nonce, значение хеша и т. п.);
- 0x6 — не встречался в исследованном коде;
- 0x7 — не встречался в исследованном коде;
- 0x8 — статус выполнения операции или команды;
- 0x9 — не встречался в исследованном коде;
- 0xA — временные атрибуты объектов операционной системы (процессы, файлы) в формате Unix timestamp;
- 0xB — битовая маска;
- 0xC — строка;
- 0xD — массив контейнеров;
- 0xE — контейнер; его область данных представляет собой массив других сериализованных единиц. Контейнеры позволяют выстраивать сериализованные данные в иерархическую структуру произвольной глубины.
API для работы с указанными форматом включает в себя следующие функции:
// присоединить одну единицу сериализованных данных к другой так, чтобы они находились в едином буфере и шли друг за другом подряд SerializedData *__fastcall ConcatItems(SerializedData *dstItem, SerializedData *srcItem); // добавить единицу хранения в контейнер SerializedData *__fastcall AddNewItemIntoContainer(SerializedData *container, u_long newItemId, char *newItemData, signed int newItemBufLen, DWORD *status); // скопировать единицу хранения SerializedData *__fastcall CopyItem(SerializedData *srcItem); // получить размер единицы хранения (включая заголовки) __int64 __fastcall GetItemSize(SerializedData *item); // получить количество находящихся в контейнере единиц хранения с заданным ключом __int64 __fastcall CountItemsInContainer(SerializedData *container, int itemsKey); // получить указатель на буфер с данными для единицы хранения с заданным ключом и индексом SerializedData *__fastcall GetItemDataWithDesiredId(SerializedData *items, int key, int itemIndex) // получить из контейнера копию хранимой единицы данных с заданным ключом и индексом int __fastcall GetItemDataCopyFromContainer(SerializedData *items, unsigned int itemKey, __int64 index, _BYTE *dst, unsigned int dstCapacity); // получить из контейнера указатель на буфер хранимой единицы данных с заданным ключом и индексом char *__fastcall FindItemPtrWithDesiredId(SerializedData *items, int key, int itemIndex); // создать новый контейнер с заданным ключом SerializedData *__fastcall MakeNewContainer(u_long key); // удалить заданную единицу хранения из контейнера __int64 __fastcall DeleteItem(SerializedData *items, int id, int itemsCount); // заменить заданную единицу хранения в контейнере SerializedData *__fastcall ReplaceItem(SerializedData *container, unsigned int key, int replacedItemIndex, char *replacementData, u_long size, DWORD *status);
6. Общая схема работы бэкдора
Общую схему работы бэкдора можно представить следующим образом.
6.1. Инициализация
В ходе этого этапа бэкдор инициализирует оркестратор, регистрирует встроенные сетевые и функциональные плагины, восстанавливает свои параметры из конфигурации. Конфигурация бэкдора представляет собой отдельный участок в секции инициализированных данных; она зашифрована алгоритмом AES в режиме CFB с ключом, упомянутым ранее в разделе «Запуск и закрепление». Указатель на расшифрованную конфигурацию хранится далее в контексте оркестратора.
После расшифровки конфигурационного файла бэкдор пытается загрузить сохраненную конфигурацию. Для этого он извлекает из своей текущей конфигурации сериализованное (ключ — 0xC0037) имя ключа реестра в разделе HKEY_LOCAL_MACHINE, в котором находится сохраненная конфигурация. При сохранении текущая конфигурация сжимается алгоритмом LZ4, после чего шифруется приведенным выше алгоритмом AES-CFB с тем же самым ключом. Если сохраненная конфигурация отсутствует, например при первом запуске, бэкдор использует конфигурацию, извлеченную из секции данных.
Далее из конфигурации извлекается команда (ключом 0xC002F), которая затем исполняется в интерпретаторе cmd.exe. Во всех рассмотренных экземплярах бэкдора значение данной команды в исходной конфигурации было пустым.
6.2. Установление соединения
Далее бэкдор создает соединение с управляющим сервером на основе сетевой конфигурации, которая хранится в сериализованном виде с ключом 0xD0033. Сетевая конфигурация может содержать в себе несколько различных вариантов подключения к С2. В таком случае используемый вариант выбирается случайно. В выбранной конфигурации соединения счетчик ее использования увеличивается на 1 каждый раз, когда данное соединение выбирается для использования (ключ сериализации — 0×30035). Далее из выбранной конфигурации извлекается строка, описывающая соединение до С2 (ключ сериализации — 0xC0034), на основе которой бэкдор устанавливает соединение с С2, после чего отправляет на него сериализованные списки идентификаторов всех используемых сетевых и функциональных плагинов. Ключ сериализации для списка функциональных плагинов — 0xD0006, для списка сетевых — 0xD0009. Далее осуществляется переход к приему и обработке команд. Соединение с С2 использует транспорт agent с проверкой подлинности подключаемых клиентов (см. описание транспортной подсистемы).
6.3. Цикл приема и обработки команд
Команда передается от С2 к бэкдору в сериализованном виде. При этом предварительно передается общий размер команды (4 байта, порядок байт — big endian), а затем и сама команда. Код команды имеет размер 4 байта и сериализован ключом 0×30005. Данные, полученные в ходе выполнения команды и подлежащие отправке на С2, также сериализуются в контейнере с ключом 0xE0002. В данный контейнер дополнительно добавляется код статуса выполнения операции (ключ 0×80003), после чего результат отправляется на С2 и цикл повторяется. При возникновении ошибок соединение с управляющим сервером сбрасывается и устанавливается заново.
Ниже представлен пример пустого обработчика команд из плагина Processes (см. соответствующий раздел).
SerializedData *__fastcall Processes::EmptyCommand( ORCHESTRATOR *ctx, SerializedData *parameters, int *pStatus) { int status; SerializedData *operationResultContainer; status = 0; operationResultContainer = ctx->MakeNewContainer(OPERATION_RESULT_CONTAINER); if ( !operationResultContainer ) status = ERR_OUT_OF_MEMORY; if ( pStatus ) *pStatus = status; return operationResultContainer; }
Минимально необходимый объем действий, которые должен выполнить обработчик команд:
- создать контейнер, в который будут сериализовываться результаты выполнения команд. В данном случае он не содержит каких-либо единиц хранения;
- записать в переменную pStatus статус выполнения операции или код ошибки;
- вернуть контейнер с результатами выполнения команды.
7. Отложенные команды
В бэкдоре дополнительно реализована возможность периодически выполнять набор дополнительных команд, вне основного цикла их приема и обработки. Подобные команды жестко задаются в конфигурации бэкдора, поменять их состав можно только вместе со сменой всей конфигурации.
Для выполнения отложенных команд отводится некоторый временной интервал, который задается оператором бэкдора (команды с ID = 109 и 110 плагина Management, см. описание ниже). Если этот интервал не задан (равен 0), отложенные команды не выполняются. Проверка наличия данного интервала и вызов обработчика отложенных команд происходит после выполнения очередной команды от С2 в основном цикле их приема и обработки.
Если временной интервал для отложенных команд ненулевой, осуществляется попытка их выполнения. В случае неудачи обработчик ожидает некоторое время (по умолчанию — 3 секунды), после чего заново предпринимает попытку их выполнения, до тех пор пока отведенный временной промежуток не будет исчерпан либо пока команды не будут выполнены успешно.
Кроме того, оператор может задать конкретное время начала выполнения отложенных команд (команда с ID = 110 плагина Management), которое можно назвать также «временем пробуждения». Если оно задано, обработчик отложенных команд ожидает, пока не наступит заданное время пробуждения, и только тогда переходит к выполнению. Соответственно, на время ожидания приостанавливается весь цикл приема и обработки команд от С2.
Упрощенный листинг обработчика отложенных команд представлен ниже:
__int64 __fastcall ExecuteScheduledTasks(ORCHESTRATOR *this, unsigned int scheduledCmdExecutionTimeInterval) { ... config = this->configuration; commandsExecutionTimeFrame = scheduledCmdExecutionTimeInterval; sleepIntervalInSeconds = 0; wakeUpTime = 0i64; timeWhenExecutionBegun = time64(0i64); if ( GetItemDataCopyFromContainer(config, SLEEP_TIME_IN_SECONDS, 0i64, &sleepIntervalInSeconds, 4u) < 0 || sleepIntervalInSeconds > 7200 ) { sleepIntervalInSeconds = 3; } if ( GetItemDataCopyFromContainer(config, WAKE_UP_TIME, 0i64, &wakeUpTime, 8u) > 0 && wakeUpTime ) { while ( time64(0i64) < wakeUpTime ) { Sleep(1000 * sleepIntervalInSeconds); } wakeUpTime = 0i64; ReplaceItem(config, WAKE_UP_TIME, 0, &wakeUpTime, 8u, 0i64); } while ( timeWhenExecutionBegun + commandsExecutionTimeFrame > time64(0i64) ) { ... if ( ExecuteScheduledCommands(this) >= 0 ) return 4i64; Sleep(1000 * sleepIntervalInSeconds); } return 1i64; }
Массив отложенных команды сериализован в конфигурации бэкдора ключом 0xD0024. Каждый элемент данного массива включает в себя следующие значения:
Ключ сериализации |
Описание единицы данных |
0x30025 |
идентификатор команды |
0xD0028 |
массив параметров команды |
0x30026 |
если данный параметр равен 0, данная команда не исполняется |
0x30027 |
если данный параметр равен 1, исполнение отложенных команд завершается |
Результаты выполнения отложенных команд сериализуется в массив с ключом 0xD002A. Каждый элемент указанного массива включает в себя следующие элементы:
Ключ сериализации |
Описание единицы данных |
0x30025 |
идентификатор выполненной команды |
0x4003B |
время завершения выполнения команды |
0xE0002 |
контейнер с результатом выполнения команды |
Результаты выполнения отложенных команд отправляются на С2 отдельной командой с ID = 105. После отправки контейнер с результатами освобождается. Если количество записей в данном контейнере превышает 1000, старые записи удаляются.
8. Описание плагинов
В данном разделе будет рассмотрена функциональность всех плагинов, использовавшихся в исследованных экземплярах бэкдора. Названия плагинов даны исходя из их функциональности и в самом коде не встречаются.
8.1. Management
Рассмотрим плагин Management, который имеет идентификатор pluginId = 1. Основная его функциональность — предоставление возможности управлять оркестратором бэкдора. Всего здесь имеется 18 команд с кодами 100–117. Особенность их состоит в том, что все обработчики данных команд «накрыты» виртуальной машиной Themida, чтобы затруднить анализ логики управления оркестратором.
- Команда с ID = 100. Команда загружает и регистрирует заданный плагин. Принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0x30007 |
идентификатор плагина |
0x50065 |
буфер с PE-модулем, реализующий функциональность плагина |
0xC0066 |
имя функции инициализации плагина (экспортируется PE-модулем плагина) |
Заданный PE-модуль рефлективно загружается в адресное пространство процесса бэкдора, после чего вызывается заданная функция инициализации плагина. Вновь загруженный плагин регистрируется в оркестраторе под своим идентификатором.
- Команда с ID = 101. Выгружает заданный плагин. В качестве параметра принимает идентификатор плагина (ключ сериализации — 0×30007). При выгрузке ожидает завершения всех асинхронных операций, производимых данным плагином.
- Команда с ID = 102. Загружает заданный транспортный плагин. Принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0x30007 |
идентификатор транспортного плагина |
0x30014 |
идентификатор фабрики сетевых соединений (см. раздел «Архитектура транспортной подсистемы») |
0x50065 |
буфер с PE-модулем, реализующий функциональность плагина |
0xC0066 |
имя функции инициализации плагина (экспортируется PE-модулем плагина) |
Как и при выполнении команды с ID=100, заданный PE-модуль рефлективно загружается в адресное пространство процесса бэкдора с последующим вызовом функции инициализации плагина. Далее плагин регистрируется в заданной фабрике сетевых соединений под своим идентификатором.
- Команда с ID = 103. Выгружает заданный сетевой плагин, удаляя его из заданной фабрики. В качестве параметров принимает идентификатор фабрики сетевых транспортов (0×30014), идентификатор плагина (0×30007).
- Команда с ID = 104. В качестве входных данных принимает идентификатор фабрики сетевых соединений (ключ сериализации — 0×30014). Создает новую (пустую) фабрику сетевых соединений с заданным идентификатором.
- Команда с ID = 105. Отправляет на С2 результаты выполнения команд по расписанию, а также подробную информацию о текущей машине. Контейнер с результатом выполнения команды содержит следующие записи:
Ключ сериализации |
Описание единицы данных |
0x4007B |
константа, различается в разных семплах (например, 0x846A9F5EA9D10C3C); возможно, часть идентификатора билда |
0x3007A |
константа, различается в разных семплах (например, 0x780C2714); возможно, часть идентификатора билда |
0x30079 |
константа, различается в разных семплах (например, 0x03); возможно, часть идентификатора билда |
0xC0081 |
имя компьютера |
0xC0082 |
имя пользователя |
0xC0083 |
IP-адрес локального сетевого адаптера (в виде строки) |
0x50085 |
MAC-адрес локального сетевого адаптера |
0x50088 |
сведения о версии ОС в виде структуры OSVERSIONINFOEXW |
0x30086 |
основной номер версии ОС |
0x30087 |
дополнительный номер версии ОС |
0x8003A |
разница в секундах между локальным временем и временем в UTC |
- Команда с ID = 106. Обработчик отправляет на С2 текущую используемую конфигурацию бэкдора.
- Команда с ID = 107. Обновляет конфигурацию бэкдора, которая задается в параметрах команды (ключ сериализации — 0xE001E). В ходе выполнения команда тестирует вновь задаваемые в новой конфигурации сетевые соединения к С2. Результаты тестирования для каждого варианта сетевого соединения отправляются на С2, сериализованные следующим образом:
Ключ сериализации |
Описание единицы данных |
0xC0034 |
строка конфигурации протестированного соединения |
0x80003 |
код результата попытки подключения по указанному транспорту |
- Команда с ID = 108. Пустой обработчик.
- Команда с ID = 109. Обработчик устанавливает величину временного промежутка, в течение которого выделенная функция будет предпринимать попытки выполнения отложенных команд или команд по расписанию (ключ параметра — 0×30064). В случае успеха функция прекращает указанные попытки. Временной интервал задается в минутах. Значение установленного параметра команда сериализует в контейнер с результатом выполнения команды (0×30064). Продолжительность указанного временного интервала не может составлять более 180 суток.
- Команда с ID = 110. Обработчик устанавливает величину временного промежутка для выполнения отложенных команд (аналогично функции с ID = 109, ключ параметра — 0×30064), а также устанавливает время пробуждения (в формате Unix timestamp, ключ параметра — 0×40022). Время пробуждения рассчитывается следующим образом: время начала выполнения указанной команды + заданная в параметрах величина временного промежутка для выполнения отложенных команд (в секундах). После завершения выполнения указанной команды цикл приема и обработки команд от С2 приостанавливается до времени пробуждения.
- Команда с ID = 111. Устанавливает в конфигурации бэкдора параметр с ключом 0×30021, назначение которого неизвестно.
- Команда с ID = 112. Устанавливает в конфигурации бэкдора параметр с ключом 0×30020, назначение которого неизвестно.
- Команда с ID = 113. Обработчик добавляет новые варианты конфигурации сетевых транспортов для связи с C2, которые задаются в параметрах команды (0xC0034). В процессе каждая из указанных конфигураций тестируется путем создания тестового транспорта (тип транспорта AUTH), который предпринимает попытку связи с С2. Результаты тестирования: строка с конфигурацией сетевого транспорта, а также (ключ — 0xC0034), а также код результата попытки подключения по указанному транспорту (ключ — 0×80003) отправляется на С2 с помощью текущего используемого соединения.
- Команда с ID = 114. Обработчик получает идентификаторы текущего и родительского процесса, которые сериализует в контейнер с результатом выполнения команды с ключами 0×3008C и 0×3008D соответственно.
- Команда с ID = 115. Прекращает прием и обработку команд от С2 до следующего запуска бэкдора. В контейнер с результатом выполнения команды сериализуется константа 0×1 c ключом 0×3006B.
- Команда с ID = 116. Обработчик собирает данные по всем текущим асинхронным операциям, сериализующиеся в следующем формате:
Ключ сериализации |
Описание единицы данных |
0x30090 |
идентификатор потока, выполняющего асинхронную операцию |
0x3003E |
идентификатор выполняющейся асинхронной операции |
0xC0091 |
описание выполняющейся асинхронной операции |
0x3003D |
состояние операции: стартовала, в процессе, завершена |
0x3003F |
идентификатор плагина, который осуществляет операцию |
Массив полученных данных по каждой асинхронной операции сериализуется в контейнере с результатом выполнения команды с ключом 0xD003C.
- Команда с ID = 117. Пустой обработчик команд.
8.2. Remote shell
Данный плагин имеет идентификатор pluginId = 5 и предоставляет всего две функции, которые реализуют механизм удаленной командной строки.
- Команда с ID = 200. Запускает процесс с заданной в параметрах командной строкой (ключ сериализации — 0xC00C8). Результатом выполнения команды является все, что запущенный процесс выдал в stdout и stderr, сериализованное ключом 0xC0004, а также статус выполнения операции или код ошибки, возникшей при запуске процесса (0×80003).
- Команда с ID = 201. Запускает удаленную командную строку посредством выполнения cmd /c start /b <заданная командная строка> и перенаправления stdin и stdout на удаленный узел либо перенаправляет stdin и stdout уже запущенного процесса на заданный удаленный узел. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0x300CE |
ID процесса, stdin или stdout которого необходимо перенаправить на удаленный узел |
0xС00СD |
командная строка запуска нового процесса, stdin или stdout которого будет перенаправлен на удаленный узел |
0xС00СС |
строка конфигурации соединения к удаленному узлу (подробнее в разделе «Архитектура транспортной подсистемы») |
0x300CA |
если данный параметр ненулевой, то требуется подтверждение подлинности клиента при установлении соединения с удаленным узлом (см. описание транспорта client и agent) |
Если ID процесса, заданный в параметрах, равен 0, то запускается новый процесс с заданной командной строкой. В противном случае командная строка игнорируется.
8.3. Processes
Данный плагин имеет идентификатор pluginId = 7 и предназначен для работы с процессами. Реализует следующие команды:
- Команда с ID = 400. Выполняет то же самое, что и команда с ID = 401 (см. далее), но посредством использования интерфейса IWbemServices. Также незначительно различается состав сведений, которые собираются о запущенных системных процессах. К сведениям, указанным в описании команды с ID = 401, также добавляется командная строка, с которой был запущен процесс (ключ сериализации — 0xC0064).
- Команда с ID = 401. Получает сведения о каждом запущенном процессе в системе. Собранный массив данных сериализуется в контейнер с ключом 0xD003C. Число процессов, сведения о которых содержатся в данном контейнере, не превышает 256. При достижении указанного порога промежуточный результат отправляется на С2, после чего сбор сведений продолжается. Если сбор сведений завершен и число процессов, сведения о которых записаны в контейнер, меньше 256, то контейнер отправляется на управляющий сервер в обычном порядке, вместе с результатом выполнения команды (см. раздел «Цикл приема и обработки команд»). Перечень сведений, собираемых о каждом процессе, вместе с ключами их сериализации приведен ниже:
Ключ сериализации |
Описание единицы данных |
0x30066 |
ID процесса |
0x3006A |
ID родительского процесса |
0xC006B |
Имя исполняемого файла процесса в кодировке UTF8 |
0x80065 |
ID сессии процесса |
0xA006C |
Время создания процесса в формате Unix timestamp |
0xC006D |
Домен пользователя, от имени которого создан процесс |
0xC006E |
Пользователь, от имени которого создан процесс |
0x3006F |
ID архитектуры процессора текущей системы |
0x30070 |
Разрядность процессора текущей системы |
В бэкдоре применяются следующие идентификаторы архитектуры процессора:
Наименование архитектуры |
ID процессора в бэкдоре |
X86_64 |
1 |
ARM, ALPHA |
2 |
PowerPC |
3 |
MIPS |
4 |
SHX |
6 |
IA32, IA64 |
7 |
Microsoft Intermediate Language |
8 |
Neutral |
9 |
- Команда с ID = 402. В качестве параметра принимает ID процесса, ключ сериализации — 0×30066. Завершает выполнение процесса с указанным ID.
- Команда с ID = 403. Создает процесс с заданной командной строкой от имени пользователя, создавшего заданную сессию (или процесс с заданным ID), установив в качестве родительского процесс с заданным ID. Команда принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
командная строка процесса, который надо запустить |
0x80065 |
ID сессии, в которой надо запустить данный процесс |
0x30066 |
ID процесса. Вновь запускаемый процесс будет запускаться от имени пользователя, создавшего процесс с указанным ID, если ID сессии не указан (меньше 0) |
0x3006A |
ID процесса, который будет указан как родительский у вновь создаваемого процесса |
В качестве результата команда сериализует ID вновь созданного процесса (ключ сериализации — 0×30066).
Команда с ID = 404. Пустая команда, не выполняет содержательных действий.Функция с ID = 405. Выполняет асинхронную операцию, заданную во внешней библиотеке (DLL). Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0x50069 |
буфер c DLL, реализующей асинхронную операцию |
0xC0067 |
имя функции, реализующей асинхронную операцию. Экспортируется заданной DLL |
0xC0064 |
строковый параметр, передаваемый функции, реализующей асинхронную операцию (предположительно описание операции) |
Обработчик команды рефлективно загружает заданную DLL в память текущего процесса бэкдора, вызывая точку входа (предположительно DllMain) c параметром fdwReason = DLL_PROCESS_ATTACH. После загрузки выполняет асинхронную операцию, вызывая в отдельном потоке функцию, экспортируемую DLL, имя которой задано в параметрах команды. Код выполнения указанной операции представлен ниже:
int __fastcall Processes::InvokeExternalAsyncOperation(EXTERNAL_MODULE_ASYNC_OP_CONTEXT *ctx) { __int64 opDescription; void (*externalAsyncOperation)(void); operationDescription = ctx->operationDescription; externalAsyncOperation = ctx->externalAsyncOperation; if ( opDescription ) (externalAsyncOperation)(0, 0, operationDescription, 0); else externalAsyncOperation(); return 0; }
По завершении асинхронной операции упомянутая DLL выгружается из памяти.
8.4. FIlesystem
Данный плагин имеет идентификатор pluginId = 9 и реализует функции для работы с файловой системой, предоставляя возможность выполнять следующие команды:
- Команда с ID = 300. Собирает сведения о заданном файле или каталоге. Принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к заданному файлу или каталогу |
0x30070 |
максимальное количество файлов или каталогов, сведения о которых будут включены в итоговый результат. По умолчанию оно равно 1024 |
0x3007B |
флаг, регулирующий формат выходных данных, далее — флаг формата |
0x3007E |
неизвестный параметр, при выполнении команды не используется |
Если путь не указан, возвращает перечень имен логических дисков с информацией об их типах, полученной посредством API-вызова GetDriveTypeA(). Если объект с заданным путем является файлом, команда собирает и сериализует следующие данные:
Ключ сериализации |
Описание единицы данных |
0x30067 или 0xB007C, если выставлен флаг формата |
атрибуты файла: является ли каталогом, имеются ли права на чтение, запись, выполнение (поле mode структуры _stat64 заголовка SYS\STAT.H) |
0xС006E |
имя учетной записи владельца файла |
0xC007D |
имя домена учетной записи владельца файла |
0x4006C |
размер файла |
0xA0069 |
время изменения файла, Unix timestamp |
0xA0068, включается в результат, если выставлен флаг формата |
время последнего доступа к файлу, Unix timestamp |
0xA006A, включается в результат, если выставлен флаг формата |
время создания файла, Unix timestamp |
0xС0066 |
имя файла |
Собранные сведения сериализуются в контейнер с ключом 0xD003C, если выставлен флаг формата, либо с ключом 0xD006D в противном случае. Если заданный объект файловой системы является каталогом, то указанные выше сведения функция получает для каждого находящегося в нем файла или подкаталога (нерекурсивно). Стоит отметить, что используемая процедура получения имени учетной записи и домена владельца файла требует наличия привилегии SeBackupPrivilege.
- Команда с ID = 301. Эта команда может применяться для отправки больших файлов с зараженного устройства на С2, а также для отслеживания изменений файлов, интересующих оператора бэкдора. Команда принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к файлу |
0x4006B |
смещение внутри заданного файла |
0x3007A |
неизвестный параметр, при выполнении команды не используется |
0x50071 |
MD5-хеш части содержимого файла от нулевого байта до заданного смещения |
Команда выполняется следующим образом:
- вычисляет MD5 от содержимого файла, начиная от нулевого байта до заданного смещения;
- если вычисленный хеш совпадает с заданным, то отправляет на С2 сведения о файле, а также его содержимое, начиная с заданного в параметрах смещения. Если вычисленный хеш отличается от заданного, на управляющий сервер вместе с метаданными отправляется все содержимое файла.
Сведения о файле включают в себя следующие сериализованные данные:
Ключ сериализации |
Описание единицы данных |
0x4006B |
текущее смещение внутри файла, начиная с которого данные были считаны |
0x4006С |
размер файла |
0xA006A |
время создания файла в формате Unix timestamp |
0xA0069 |
время изменения файла в формате Unix timestamp |
0xA0068 |
время последнего доступа к файлу в формате unix timestamp |
0x80003 |
статус текущей операции (код ошибки или 0, если успешно) |
- Команда с ID = 302. Эта команда может применяться для скачивания больших файлов с С2 на зараженное устройство либо для синхронизации изменений между версиями одного и того же файла на С2 и на зараженном устройстве. Команда принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к файлу |
0x30076 |
размер буфера, который будет использоваться для передачи содержимого файла |
0x30067 |
неизвестный параметр, при выполнении команды не используется |
Выполнение команды происходит следующим образом:
- на С2 отправляется размер файла (0×4006B) и MD5 его содержимого (0×50071), а также статус операции открытия файла (0×80003);
- с С2 получается смещение внутри заданного файла (0×4006B), начиная с которого содержимое файла будет перезаписано полученными с С2 данными, а также размер этих данных (0×40006C).
- Команда с ID = 303. Команда позволяет отправлять на С2 содержимое всех файлов, находящихся непосредственно в заданном каталоге (файлы в подкаталогах не обрабатываются), либо отправляет на С2 содержимое заданного файла. Команда может применяться в тех случаях, когда необходимо отслеживать изменения содержимого заданного каталога, отправляя на С2 только измененные либо вновь созданные файлы.
Принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к заданному объекту файловой системы (файлу или каталогу) |
0x50072 |
хеш-таблица со сведениями о состоянии файлов (подобнее о структуре см. ниже) |
0xC0077 |
маска файлов, игнорируемых при выполнении команды, в формате WinAPI-функции PathMatchSpec |
Cтруктура со сведениями о состоянии файла имеет следующий вид:
typedef struct { __int64 key; // ключ, вычисляется как djb2-хеш от пути файла __int64 offsetInFile; // смещение внутри файла __time64_t modificationTime; // время модификации файла int fileContentsMd5[4]; // MD5 от содержимого файла } FILE_STATE;
Хеш-таблица указанных структур описывает файлы, ранее передававшиеся на С2. Поля структуры отражают состояние файла на момент его предыдущей передачи.
Стоит отметить, что при вычислении ключа key с использованием алгоритма djb2 на основе пути файла специальные символы " * ? > < : заменяются на символ _, а символ \ — на /.
При выполнении команды для каждого файла из заданного каталога производятся следующие действия:
- Если путь к файлу удовлетворяет заданной в параметрах маске (ключ — 0xC0077), заданный файл игнорируется.
- На основании пути к файлу по указанному выше алгоритму вычисляется ключ.
- Во вычисленному ключу в хеш-таблице ищется запись, соответствующая заданному файлу. Если запись найти не удалось, файл в дальнейшем отправляется целиком.
- Если MD5-хеш содержимого файла и дата изменения совпадают с соответствующими полями в найденной записи хеш-таблицы, то содержимое файла отправляется начиная с заданного смещения. В противном случае файл в дальнейшем отправляется на С2 целиком.
- На С2 отправляются следующие сериализованные сведения о файле:
- На С2 отправляется содержимое содержимое файла полностью либо начиная с заданного смещения, с учетом условий шагов 4–5.
Ключ сериализации |
Описание единицы данных |
0xС0064 |
полный путь к файлу |
0x4006C |
размер файла |
0x4006B |
смещение, с которого будет начато чтение данных из текущего файла |
0xA0069 |
время изменения файла |
0x80003 |
код ошибки при работе с файлом или 0 в случае успеха |
Перед отправкой файлов на управляющий сервер также отправляются сериализованные метаданные о выполнении текущей операции:
Ключ сериализации |
Описание единицы данных |
0x4006C |
общий размер передаваемых файлов |
0x80003 |
статус операции (всегда равен 0) |
0x30074 |
количество файлов, содержимое которых будет передано |
0x4006B |
общий объем данных, которые планируется передать в рамках текущей команды (может не совпадать с общим размером файлов, так как некоторые файлы могут передаваться частично) |
Приведенная выше схема работы и состав пересылаемых данных сохраняются и в том случае, если в качестве параметра команды указан путь к файлу.
- Команда с ID = 304. В качестве входных данных принимает путь к файлу (0xС0064). Заполняет первые 0×10000000 байт заданного файла нулями, если размер файла больше 0×10000000 байт. В противном случае заполняет заданный файл нулями полностью.
- Команда с ID = 305. В качестве входных данных принимает путь исходного файла и путь модифицируемого файла (0xC0064). Копирует время создания, время последнего доступа и время последней записи исходного файла и устанавливает их в качестве соответствующих атрибутов модифицируемого файла.
- Команда с ID = 306. В качестве входных данных принимает путь к каталогу (0xC0064). Создает каталог с заданным путем.
- Команда с ID = 307. Удаление заданных файлов или каталога. Принимает следующие параметры:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к файлу или каталогу или маска в формате WinAPI-функции PathMatchSpec |
0x30067 |
флаг: если 0, то предыдущий параметр интерпретируется как путь к каталогу, в противном случае — как маска имени файла |
Если объект по заданному пути является файлом, то он просто удаляется. Если по заданному пути находится каталог, происходит следующее:
- если маска не задана, функция удаляет заданный каталог вместе со всем содержимым;
- в противном случае в каталоге рекурсивно удаляются файлы, пути к которым удовлетворяют заданной маске. Если маске соответствует имя подкаталога, то он удаляется полностью со всем своим содержимым.
Результат выполнения команды включает в себя сериализованные ключом 0×30074 сведения о количестве файлов, подлежавших удалению, и количестве фактически удаленных файлов.
- Команда с ID = 308. Собирает сведения о заданном каталоге. В качестве параметра принимает имя каталога (0xC0064). Получает общий размер файлов, находящихся в каталоге, общее количество подкаталогов, а также общее количество файлов, находящихся в заданном каталоге. Результат выполнения команды имеет вид строки, сериализованной ключом 0xC0004:
... GetDirectoryInfo(dirName, &totalFilesSize, &dirsNum, &filesNum); sprintf_s(Buffer, 0x1000ui64, "%lld bytes, %d dirs, %d files\n", totalFilesSize, dirsNum, filesNum); operationResult = ctx->AddOrReplaceItem(operationResult, OPERATION_RESULT_STRING, 0i64, Buffer, -1, &err); ...
- Команда с ID = 309. Копирует заданный каталог. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к исходному каталогу или маска в формате WinAPI-функции PathMatchSpec |
0xC0064 |
путь к каталогу назначения |
0x30067 |
флаг: если 0, то первый параметр интерпретируется как путь к каталогу, в противном случае — как маска имени файла |
Если путь к исходному каталогу задается маской, то копируются файлы, удовлетворяющие заданной маске. Результат выполнения команды включает в себя общее количество подлежавших копированию файлов и общее количество скопированных файлов, сериализованных ключом 0xC0004.
- Команда с ID = 310. Перемещает заданный каталог. Параметры, возвращаемые результаты и схема работы аналогичны команде c ID = 309.
- Команда с ID = 311. Принимает в качестве входных данных путь к заданному файлу (0xC0064). Читает файл и отправляет его содержимое на С2. Если размер файла не превышает 4096 байт, сериализует содержимое файла в контейнере с результатом выполнения команды (ключ — 0xC0004). В противном случае отправляет содержимое файла частями, сериализуя их следующим образом:
Ключ сериализации |
Описание единицы данных |
0xC0004 |
очередная часть файла, размером не более 4096 байт |
0x3000B |
флаг конца данных. Если выставлен в 0, то текущая часть файла — последняя |
- Команда с ID = 312. Данная команды читает и отправляет на С2 текстовые файлы. Необходимость реализации группы команд, работающих с текстовыми файлами, вероятно, обусловлена потребностью передавать на управляющий сервер результаты работы других команд, сохраненные в таких файлах. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к текстовому файлу |
0x30081 |
количество строк, которое необходимо прочитать из файла |
0x30082 |
длина строки |
Если количество строк, которое необходимо прочитать из файла, ненулевое, то функция считывает из файла заданное количество строк и отправляет их на С2, разбивая на буферы, не превышающие 4096 байт, сериализуя по аналогии с обработчиком команды с ID = 311. В противном случае из заданного файла считывается строка длиной, заданной в параметрах команды, но не более 4096 байт, после чего сериализуется в контейнере с результатом выполнения команды (ключ — 0xC0004).
- Команда с ID = 313. Позволяет считывать данные из текстового файла с произвольной строки. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к текстовому файлу |
0x30081 |
число строк, которое необходимо пропустить |
0x30082 |
начальное смещение внутри файла |
0x30083 |
флаг направления отсчета (0 — c конца файла, в противном случае — с начала файла) |
Считывает и отправляет на С2 содержимое текстового файла начиная с заданного смещения в файле, пропустив заданное число строк.
Если заданный флаг направления отсчета ненулевой, начальное смещение отсчитывается с начала файла. Далее команда пропускает заданное количество строк (конец строки — CRLF) и отправляет оставшуюся часть файла на С2. Если заданный флаг направления отсчета нулевой, начальное смещение отсчитывается с конца файла. Далее команда пропускает заданное количество строк, считывая их в обратном направлении от текущей позиции к началу файла. После этого часть файла от итоговой позиции до конца файла отправляется на управляющий сервер. Содержимое файла сериализуется и отправляется на С2 по аналогии с обработчиками команд c ID = 311, 312.
- Команда с ID = 314. Реализует асинхронную операцию по получению листинга заданного каталога. Результат сохраняется в текстовый файл. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0xC0064 |
путь к каталогу |
0xC0064 |
имя текстового файла для сохранения результатов |
0x30074 |
максимальная глубина вложенности объектов |
0x30083 |
флаг направления отсчета (0 — c конца файла, в противном случае — с начала файла) |
Для заданного каталога команда функция рекурсивно собирает и сохраняет в заданный файл следующие сведения:
- дату последней записи (год, месяц, день, час, минута в строке формата YYYYMMDDhhmm);
- размер файла;
- имя файла;
- глубину вложенности объекта по отношению к исходному каталогу.
Стоит отметить, что функция сбора указанной информации содержит в себе возможность исключать имена файлов и каталогов из обработки, однако в данной команде эта опция не применяется.
- Команда с ID = 315. Архивирует заданные файлы. Выполняется асинхронно. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0xD006D |
массив путей к файлам, которые необходимо заархивировать |
0x30074 |
размер массива с путями к файлам в байтах |
Пути к файлам задаются в виде массива подряд идущих структур, описывающих строку. Формат структуры: <длина пути, 4 байта><путь к файлу>. Файлы из заданного массива помещаются в общий ZIP-архив, путь к которому задается последним элементом указанного массива. Ход выполнения операции записывается в файл журнала, имя которого имеет формат: <имя zip-архива>.<длина имени zip-архива в шестнадцатеричном формате>. В начале выполнения операции в файл журнала записывается строка begin <имя zip-файла>. При обработке очередного файла в лог-файл записывается его имя. Если очередной объект из заданного списка является каталогом, он помещается в архив рекурсивно. Если файл по каким-либо причинам не удается заархивировать, в лог-файл добавляется строка skipped <имя файла>. Результат выполнения команды представляет собой сериализованную ключом 0xC0004 строку: Zip <количество каталогов> Dir(s), <количество файлов> File(s) to <имя zip-архива>.
8.5. Netscan
Данный плагин имеет идентификатор pluginId = 4 и предоставляет возможность получить различные сведения о сетевом окружении. Всего в нем реализовано восемь команд, которые подробно рассмотрены ниже.
- Команда с ID = 2000. Проверяет доступность удаленного сетевого узла с заданными IP-адресом и портом путем попытки установления с ним соединения по протоколу TCP. Параметры:
Ключ сериализации параметра |
Значение параметра |
0xD006D |
IPv4-адрес удаленного узла |
0x30074 |
порт удаленного узла |
0x30067 |
локальный порт |
0x30069 |
тайм-аут ожидания соединения в секундах |
Результат выполнения команды представляет собой сериализованную ключом 0xC0004 строку SUCCESS или FAILED в зависимости от того, удалось ли осуществить подключение.
- Команда с ID = 2001. Асинхронная операция, которая для заданного перечня IPv4-адресов получает соответствующие MAC-адреса. Параметры команды:
Ключ сериализации параметра |
Значение параметра |
0x30074 |
размер структуры с параметрами команды |
0xD006D |
структура с параметрами команды |
Параметры команды задаются в виде отдельной структуры, представленной ниже:
typedef struct { int flags; unsigned int initialHostIpv4Addr; int hostsInfoSize; char outFilePath[1024]; int waitTimeout; int localPort; int remotePort; char hostsInfo[]; } NETSCAN_OPERATION_PARAMETERS_SHORT;
Для каждого IPv4-адреса из заданного в данной структуре массива обработчик команды получает соответствующий ему MAC-адрес. IP-адреса задаются в поле hostsInfo в виде строк, разделенных символом CR. В этом случае в поле flags выставляется флаг DESIRED_HOSTS_AS_STRING_LIST (0x20). Если данный флаг не выставлен, то IP-адреса задаются диапазоном с начальным значением initialHostIpv4Addr и количеством адресов, по которым надо получить сведения, — hostsInfoSize. Каждый следующий адрес получается прибавлением единицы к предыдущему. Результат представляет собой буфер со строками вида IP\tMAC\r\n\t\r\n. Если в поле flags выставлен флаг WRITE_RESULT_TO_FILE (0x10), буфер записывается в текстовый файл, путь к которому задан в поле outFilePath структуры NETSCAN_OPERATION_PARAMETERS. Если флаг не выставлен, буфер сериализуется в контейнер с результатом выполнения команды с ключом 0xC0004.
Кроме того, если в поле flags выставлен флаг GET_DESIRED_INFO_LOCALLY (0×40), операция выполняется посредством получения локальной таблицы соответствий «IP-адрес — MAC», имеющейся на узле (API-вызов GetIpNetTable), в противном случае нужные сведения получаются отправкой ARP-запросов.
- Команда с ID = 2002. Принимает такие же параметры, как и команда с ID = 2001. Реализует асинхронную операцию, которая проверяет доступность заданного сетевого порта (поле remotePort структуры NETSCAN_OPERATION_PARAMETERS) на множестве сетевых узлов, заданных перечнем IPv4-адресов. Перечень IPv4-адресов задается так же, как и в предыдущей команде. Результат представляет собой буфер из строк вида IP:PORT\tSTATUS\r\n:<порт>\t\r\n. Сохранение результата происходит аналогично команде c ID = 2001. В целом логика работы этих двух команд схожа. Единственное различие заключается в том, что для данной команды не актуален флаг GET_DESIRED_INFO_LOCALLY (0×40), так как производимая операция всегда подразумевает сетевое взаимодействие с заданными узами.
- - доменное имя узла (если удается получить);
- - delay — общее время прохождения пакета от локального устройства к заданному сетевому узлу и обратно (в миллисекундах);
- - ttl — время жизни пакета, направляемого от заданного сетевого узла к локальному устройству.
- Величины delay и ttl получаются путем направления ICMP echo request к заданному сетевому узлу. Delay вычисляется как разница между временем отправки ICMP echo request и временем получения ответа от заданного сетевого узла. Величина ttl берется из соответствующего заголовка IP-пакета ответа на ICMP echo request. Стоит отметить, что для отправки ICMP-запроса в этом случае используются сырые сокеты, пакет формируется вручную. При этом содержимое icmp echo request пакета нехарактерно для стандартной утилиты ping из дистрибутива ОС, так как данные ICMP-пакета, генерируемого данной командой, предваряются timestamp, что больше характерно для Linux-систем. Остальные данные пакета ICMP echo request, помимо наличия timestamp, представляют собой строку !"#$%&’()*+’-./0123456789:;<=>?. Данная полезная нагрузка также отличается от пакетов, генерируемых утилитой ping Windows.
Результат выполнения команды представляет собой буфер, где для каждого заданного IPv4-адреса имеется строка следующего формата: HOSTNAME\tIP\tTTL\tDELAY\r\n<имя хоста>\t\t\tms\r\n. Логика обработки флагов, заданных в структуре NETSCAN_OPERATION_PARAMETERS, совпадает с обработчиком команды с ID = 2002.
- Команда с ID = 2004. Команда выполняет асинхронную операцию по сбору сведений о всех активных TCP-соединениях на локальном устройстве. В качестве параметра принимает сериализованную структуру NETSCAN_OPERATION_PARAMETRS_SHORT (ключ сериализации — 0xD006D), а также размер указанной структуры (0×30074). Структура NETSCAN_OPERATION_PARAMETRS_SHORT определяется следующим образом:
typedef struct { int flags; char outFilePath[256]; } NETSCAN_OPERATION_PARAMETERS;
Собираемые сведения включают в себя:
- локальный узел и порт;
- удаленный узел и порт;
- состояние TCP-соединения; представляет собой строку из следующего перечня: CLOSED, LISTEN, SYN-SENT, "SYN-RECEIVED, ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT, DELETE-TCB;
- PID и имя исполняемого файла процесса, который владеет TCP-соединением.
Результат представляет собой буфер, где для каждого найденного TCP-cоединения имеется строка следующего формата: TCP\t<локальный хост>:<локальный порт>\t<удаленный хост>:<удаленный порт>\t<состояние соединения>\t <имя процесса>. То, куда сохраняется результат — во внешний файл либо в контейнер с результатом работы команды, — определяется флагом WRITE_RESULT_TO_FILE (0×10) по аналогии с обработчиком команды с ID = 2001.
- Команда с ID = 2005. Команда с указанным ID отсутствует.
- Команда с ID = 2006. Реализует асинхронную операцию сбора сведений о заданных сетевых шарах. В качестве параметра получает структуру NETWORK_INFO_ENUM_SMB_SHARES_PARAM (0xD006D), а также размер этой структуры (0×30074). Структура NETWORK_INFO_ENUM_SMB_SHARES_PARAM определена следующим образом:
typedef struct { int flags; int initialHostIpv4Addr; int hostInfoTotalSize; char outFileName[1024]; char username[256]; char password[256]; char hostsInfo[]; } NETWORK_INFO_ENUM_SMB_SHARES_PARAM;
Сетевые шары заданы в виде перечня IPv4-адресов в таком же формате, в котором аналогичные перечни задаются для команд с ID 2001–2003. Для каждого IPv4-адреса из заданного перечня выполняются следующие действия:
- выполняется проверка, открыт ли на целевом узле порт 139 или 445. Если нет — обработка текущего адреса завершается;
- если один из указанных портов открыт, выполняется попытка подключения по протоколу SMB к указанному сетевому ресурсу, используя заданные имя пользователя и пароль (поля username и password структуры NETWORK_INFO_ENUM_SMB_SHARES_PARAM);
- при успешном подключении команда получает перечень всех ресурсов, доступ к которым предоставляется указанной шарой. Перечень формируется в виде списка строк. Для каждой шары формируется следующий текстовый буфер:
-------------------------------------------------------\r\n [+]\t<имя удаленного хоста>\t\t<порт подключения>\tAuth\t\r\n \\\\ "<пароль>" /u:"<имя пользователя | 'CURRENT_SESSION'>"\r\n <сведения о доступном сетевом ресурсе>\r\n ... <сведения о доступном сетевом ресурсе>\r\n
Строка со сведениями о доступном сетевом ресурсе имеет следующий формат:
<'-' если на ресурсе есть файлы, 'A' - в противном случае>\t<тип шары: 'Print'|'Device'|'Disk'|'IPC'>\t<сетевое имя ресурса>\t<описание ресурса>.
Указанные сведения собираются путем вызова WinAPI-функции NetShareEnum. Буфер с результатом выполнения команды сохраняется по аналогии с ранее рассмотренными командами, в зависимости от выставленного флага WRITE_RESULT_TO_FILE (0×10) в поле flags структуры NETWORK_INFO_ENUM_SMB_SHARES_PARAM.
- Команда с ID = 2007 (0×7D7). Получает на вход сериализованную структуру NETSCAN_SHARES_MANIPULATION_PARAMS (0xD006D), а также ее размер (0×30074). Структура определена следующим образом:
typedef struct { char localDeviceName[128]; char resourceShareName[128]; char domainName[64]; char userName[64]; char password[64]; int needToDeleteConnection; int needToEnumConnections; } NETSCAN_SHARES_MANIPULATION_PARAMS;
Эта команда выполняет следующие операции:
- если поле needToEnumConnections ненулевое — получает список всех действующих соединений к сетевым шарам на локальном устройстве, который затем в текстовом виде сериализует в контейнер с результатом выполнения команды (ключ — 0xC0004). Результат сбора сведений имеет следующий вид:
STATUS\tLOCAL\tREMOTE\r\n <строка с описанием соединения> ... <строка с описанием соединения>
Строка с описанием соединения имеет следующий формат:
<сведения о состоянии соединения: 'OK'|'Paused'|'Lost'|'Disconnected'|'NetErr'|'Connecting'|'Reconnecting'>\t<имя локального устройства(логический диск), перенаправляемое на общий ресурс>\t<имя разделяемого ресурса>;
- если поле needToDeleteConnection ненулевое — удаляет существующее соединение к сетевой шаре, заданное полем localDeviceName структуры NETSCAN_SHARES_MANIPULATION_PARAMS;
- если оба упомянутых параметра нулевые — создает соединение, используя resourceShareName как имя разделяемого ресурса, а поля userName и password — как реквизиты доступа.
- Команда с ID = 2008 (0×7D8). Выполняет то же самое, что и функция с ID = 2003.
8.6. Bridge
Данный плагин имеет идентификатор pluginId = 5, который совпадает с идентификатором плагина Remote shell, возможно вследствие некоторой рассогласованности в присвоении плагинам идентификаторов. Bridge реализует различные типы прокси-соединений, выступая в качестве промежуточного узла между узлами, которые, как правило, представляют из себя экземпляры бэкдора. Реализует три команды, описанные ниже.
Команда с ID = 501Данная функция создает в отдельном потоке клиент, который выполняет функции промежуточного узла, перенаправляя через себя сетевой трафик между двумя заданными узлами. Подобная активность может выполняться в двух разных режимах, которые можно обозначить условно как режим № 1 и режим № 2; они будут рассмотрены ниже. Команда принимает следующие аргументы:
- необходимость проверки аутентичности клиента (параметр NEED_TO_ENSURE_CLIENT_AUTHENCITY конфигурации создаваемых соединений), ключ сериализации — 0×301F4;
- размер промежуточного буфера для хранения передаваемых данных, ключ сериализации — 0×301F5;
- режим соединения — режим № 1, если значение параметра нулевое, и режим № 2 — в противном случае, ключ сериализации — 0×301FD;
- величина тайм-аута, по истечении которого соединение закрывается при отсутствии сетевых событий, ключ сериализации — 0×30200;
- строка конфигурации основного соединения, ключ сериализации — 0xC01F6.
Общая схема работы промежуточного узла (в данном контексте будем называть его «клиент») выглядит следующим образом:
- Клиент с помощью заданной в параметрах строки конфигурации подключается к основному узлу.
- Далее клиент ожидает команды на установление второго соединения с другим узлом, который мы назовем вторичным. Конфигурация соединения с вторичным узлом задается в параметрах самой команды.
- После установления соединения основной и вторичный узел обмениваются данными, клиент их ретранслирует.
Описанный процесс схематично можно изобразить так:
Данная функциональность при надлежащей реализации сервера позволяет, например, организовать передачу данных между бэкдором и оператором, подключаясь к auth server через промежуточный узел:
Кроме того, при надлежащей реализации функциональности основного сервера по такой схеме можно строить гибкие маршруты передачи данных между бэкдором и оператором, с учетом того, что функции промежуточного узла и auth server может выполнять любой инстанс бэкдора с рассматриваемым модулем (во всех исследованных экземплярах данный модуль был включен по умолчанию). Следует отметить, что экземпляра основного сервера в распоряжении исследователя не имеется, выше приведен лишь один из возможных сценариев использования описываемой функциональности.
В режиме № 1 команда connectCommand на установление соединения со вторичным узлом имеет размер 16 байт и должна удовлетворять следующим условиям:
- connectCommand[0] == 501
- connectCommand[1] ^ connectCommand[1] ^ connectCommand[2] == 0 Команда connectCommand рассматривается здесь как массив из четырех беззнаковых DWORD. После получения данной команды клиент создает новое, отдельное соединение с основным узлом, от которого получает строку с конфигурацией соединения для подключения к вторичному узлу. После подключения клиент посылает основному узлу код статуса выполнения этой операции размером 4 байта, 0 — если соединение создано успешно, либо код соответствующей ошибки. Далее основной и вторичный узлы переходят к обмену данными.
Режим № 2 отличается тем, что для передачи данных между основным и вторичным узлом не создается отдельного соединения: команды на подключение к новым и данные к уже подключенным вторичным узлам идут по одному и тому же основному соединению. Это влечет за собой необходимость добавлять дополнительные заголовки к передаваемым по основному соединению данным, которые нужны для того, чтобы различать адресата.
Различия между режимами № 1 и 2 представлены ниже.
Дополнительный заголовок буфера, передаваемого от основного сервера к клиенту, имеет следующий формат:
typedef struct { int dataSize; // размер передаваемых данных int connectionID; // идентификатор соединения int state; // состояние соединения char data[] // данные } BRIDGED_CONTROL_CONNECTION_DATA_HEADER;
Данные от клиента к вторичному узлу передаются, в свою очередь, уже без этого заголовка, as is. При передаче данных в рамках уже установленного соединения значение поля state заголовка выставляется в BRIDGED_PACKET_DATA_TRANSMISSION = 0×0. При возникновении ошибки передачи данных основному соединению передается пустой буфер с полем state заголовка, равным BRIDGE_CONNECTION_ERROR = 0×4 и connectionID c идентификатором соответствующего соединения.
Для установления нового соединения основной сервер отправляет клиенту буфер со строкой конфигурации нового вторичного соединения, полем state = BRIDGED_PACKET_ESTABLISH_NEW_CONNECTION=0×1 и идентификатором connectionID нового соединения. Генерация connectionID происходит на стороне управляющего сервера.
После успешного установления вторичного соединения клиент отправляет основному серверу пустой буфер с полем state = BRIDGED_PACKET_CONNECTION_ESTABLISHED (0×2) и соответствующим connectionID. При неудаче отправляется пустой буфер с полем state = BRIDGED_PACKET_ERROR.
Команда с ID = 502Данная функция запускает в отдельном потоке auth server (см. описание транспортов client, agent, auth, control). В качестве параметра команды принимается сериализованная строка c конфигурацией серверного соединения (ключ сериализации — 0xC01FA).
Команда с ID = 505Данная функция создает отдельный поток, в котором запускает прокси-сервер, реализующий свой кастомный протокол установления соединения с целевым узлом. Этот прокси-сервер можно назвать raw proxy, так как он может оперировать только на транспортном уровне, а именно — с транспортом TCP и транспортом UDP без установления соединения (raw). Следует отметить, что соединение от клиента к прокси может быть установлено с использованием любого доступного транспорта, поддерживаемого бэкдором. В свою очередь, соединение от прокси к целевому узлу может быть установлено только с использованием транспортов TCP и сырого UDP. Таким образом, raw proxy можно рассматривать в качестве своеобразного шлюза для обмена данными с иными, внешними сетевыми узлами.
В качестве параметра данная команда принимает сериализованный номер порта, на котором необходимо принимать входящее соединение (ключ сериализации — 0×201FE). Данный порт raw proxy «слушает» на всех доступных сетевых интерфейсах. Процедура установления соединения такова:
- Клиент устанавливает соединение с raw proxy.
- Клиент отправляет команду на подключение к целевому узлу. Команда имеет следующий формат:
typedef struct { int dataSize; // размер передаваемых данных int connectionID; // идентификатор соединения int state; // состояние соединения char data[] // данные } BRIDGED_CONTROL_CONNECTION_DATA_HEADER;
Если dstAddr не задан (нулевой), то имя целевого узла получается из строки конфигурации, длина которой не превышает 512 байт. Строка конфигурации имеет стандартный для бэкдора формат, однако значимым при ее разборе является лишь параметр !proto, задающий протокол транспортного уровня. Если в качестве протокола не указан UDP, то соединение устанавливается по протоколу TCP. Если строка конфигурации начинается с символа [, то используется IPv6-версия соответствующего протокола. При успешном соединении с целевым узлом сервер отправляет клиенту код 0×0000000000005A00 и стороны переходят к обмену данными. В случае ошибки отправляется код 0×0000000000005B00.
9. Архитектура сетевой подсистемы
9.1. Обзор компонентов транспортной подсистемы
Транспортная подсистема, которую реализует рассматриваемый бэкдор, очень развита. Код, реализующий те или иные компоненты указанной подсистемы, составляет порядка 70% от общего объема кода бэкдора.
Транспортная подсистема бэкдора включает в себя следующие сущности: фабрики, транспортные интерфейсы, транспорты и соединения. Фабрики отвечают за инстанцирование транспортных интерфейсов и соединений. Используемые фабрики при создании регистрируются в контексте оркестратора, и каждая из них имеет свой уникальный идентификатор, представляющий собой целое число. В исследованных экземплярах бэкдора по умолчанию используется только одна фабрика, с идентификатором 0×01.
Транспортный интерфейс предоставляет остальным компонентам бэкдора интерфейс:
- для установления исходящих (в качестве клиента) и приема входящих (в качестве сервера) соединений;
- приема и отправки данных удаленному узлу;
- получения сведений о состоянии соединения;
- завершения соединения.
Основное назначение транспортных интерфейсов — скрывать за собой внутреннюю иерархию транспортов и взаимодействие между собой различных уровней данной иерархии.
Транспорты представляют собой компоненты (классы), реализующие конкретный сетевой протокол. Транспорты образуют между собой иерархию. Каждый транспорт может быть вышестоящим или нижестоящим либо находиться на одном и том же уровне по отношению к другому транспорту. Вышестоящий транспорт всегда работает поверх транспорта нижестоящего уровня.
Названия транспортов приведены ниже в том виде, в котором они встречаются в коде бэкдора. Стоит отметить, что фактически реализуемый сетевой протокол транспорта с определенным названием может отличаться от стандартной реализации и иметь совершенно иной смысл и свойства, что будет показано в разделе с описанием конкретных транспортов.
Транспорты условно можно разделить в соответствии с уровнями модели OSI:
- транспортный, обеспечивающий надежную передачу данных между узлами сети: tcp, udp, pipe;
- сеансовый, обеспечивающий продолжительный обмен информацией в виде множества передач данных между узлами: ssl/http/https/proxy-http;
- прикладной — реализует высокоуровневую логику установления соединений и передачи данных между экземплярами бэкдора или бэкдором и С2: raw/auth/control/client/agent.
Расширение списка перечисленных здесь транспортов допускается путем установки дополнительных сетевых плагинов. Здесь приведены имена транспортов, встроенных в бэкдор по умолчанию.
Приведенная иерархия условна, в коде бэкдора отражения не находит и выведена для удобства рассмотрения свойств транспортов бэкдора. Несмотря на то что именованные каналы относятся к сеансовому уровню модели OSI, а протоколы HTTP, HTTPS — к прикладному, соответствующие транспорты бэкдора — pipe, http, https, proxy-http — в данном исследовании отнесены к другим уровням модели OSI исходя из контекста их использования и свойств протокола, фактически реализуемого конкретным сетевым транспортом.
Так, например, реализованный бэкдором транспорт pipe используется в коде исключительно для обеспечения надежной передачи данных между узлами, в связи с чем мы в контексте данного исследования относим его к транспортному уровню модели OSI. Транспорт http/https реализует протокол, отличающийся от стандартных http/https. Он работает поверх нижележащих транспортов и создает логический сеанс передачи данных, который может возобновляться, если соединение на транспортном уровне прервано. В этой связи и исходя из контекста его использования в бэкдоре в данном исследовании он отнесен к сеансовому уровню модели OSI.
Транспорт не инкапсулирует свои пакеты в другой транспорт того же уровня, несмотря на то что технически это может быть возможно. Так, транспорт tcp не будет работать поверх транспортов udp и pipe, инкапсулируя свои пакеты в указанные протоколы. При этом, например, транспорт http технически может работать поверх транспорта ssl, однако логически для такой конфигурации в бэкдоре создается отдельный транспорт сеансового уровня — https.
Стоит отметить, что транспорты прикладного уровня могут работать поверх друг друга, например когда вышестоящий транспорт реализует или дополняет процедуру установления соединения с другим узлом. При этом вышеуказанное правило относительно инкапсуляции пакетов сохраняет свою силу.
Каждый транспорт может работать либо как клиент, либо как сервер. Как сервер он работает в случае вызова метода (названного ListenToConnection), который принимает входящие запросы на установление соединений от клиентов.
Соединением, если не оговорено иное, мы будем называть иерархию конкретных экземпляров транспортов, которая используется для передачи данных между экземплярами бэкдора или бэкдором и C2.
9.2. Создание транспортных интерфейсов и соединений. Конфигурация соединений.
Как было сказано, транспортные интерфейсы и соединения инстанцируются фабрикой. В каждый момент времени транспортный интерфейс может владеть только одним соединением. При этом транспортный интерфейс может закрывать текущее соединение и открывать (инстанцируя с помощью фабрики) другое на основе ранее использованной либо вновь заданной конфигурации соединения.
Конфигурация соединения включает в себя идентификаторы транспортов, формирующих данное соединение, а также параметры, необходимые для их работы (имена узлов, IP-адреса, порты и т. д.).
Процедура создания транспортов обязана быть зарегистрирована в фабрике. В контексте фабрики каждый транспорт идентифицируется своим именем. Исключение составляют транспорты прикладного уровня: они регистрации в фабрике не подлежат и инстанцируются непосредственно транспортным интерфейсом.
Основным способом задания конфигурации соединений в бэкдоре является строка определенного формата, которую можно описать следующей грамматикой (в синтаксисе расширенной формы Бэкуса — Наура):
ConnectionConfig ::= ConnectionSubConfig {";" , ConnectionSubConfig} ConnectionSubConfig ::= [SessionProtocolName , "://"], ["["] , HostParameters , ({"|", SessionProtocolParameter} | [TransportProtocolSpecification]) HostParameters ::= Path | (HostName, ":", PortNumber) SessionProtocolName ::= Identifier SessionProtocolParameter ::= ("!" , ParameterName, "=", ParameterValue) TransportProtocolSpecification ::= ("!", "proto", "=", TransportProtocolName) TransportProtocolName ::= Idenitifier ParameterName ::= Idenitifier ParameterValue ::= (Idenitifier) | (Number) HostName ::= Identifier PortNumber ::= Number Identifier ::= (допускаются все печатные ascii-символы, кроме ";", "|", "://", "!") Path ::= (стандартный путь в файловой системе windows)
Более наглядно строку конфигурации соединения с заданным узлом можно представить так:
<имя протокола сеансового уровня>"://"<описание узла>"|!proto="<имя протокола транспортного уровня>"|"<имя параметра>"="<значение параметра>..."|!type="<имя протокола прикладного уровня>
Вначале, перед строкой «://», задается имя транспорта сеансового уровня, процедура создания которого обязана быть зарегистрирована в фабрике под соответствующим именем. Например, это может быть одно из имен http, https, ssl и.т. д. — список см. выше.
После строки «://» идет описание сетевого узла, с которым надо устанавливать соединение (либо слушать, если работаешь как сервер). Так, для транспорта http здесь будет URL. Если используется протокол транспортного уровня, для транспорта pipe здесь будет имя пайпа либо пара "host":"port" для транспортов udp/tcp.
Параметр !proto задает имя транспорта транспортного уровня (tcp, udp, pipe), поверх которого вновь созданное соединение будет работать. Если данный параметр не указан, по умолчанию будет выбран транспорт tcp.
Параметр !type задает имя транспорта прикладного уровня (auth, control, connect, agent). Если он не задан, тип транспорта выбирается исходя из контекста функционирования интерфейса. Например, в цикле приема или обработки команд от С2 по умолчанию будет использован транспорт agent.
Соединение до заданного узла может проходить через несколько промежуточных узлов. Соединение между соседними узлами в такой цепочке также задается строкой конфигурации. Строки конфигурации соединений промежуточных узлов отделены друг от друга символом «;». Таким образом, конфигурация соединения до конечного узла через несколько промежуточных в общем виде выглядит следующим образом:
<соединение до промежуточного узла № 1>;<соединение до промежуточного узла № 2>; ... <соединение до промежуточного узла № N>;<соединение до конечного узла>.
Ниже приведены несколько примеров конфигурации соединений:
- ssl://192.168.1.166:8530|!proto=udp;ssl://igloogawk.com — данная строка описывает соединение к командному серверу igloogawk.com через промежуточный узел 192.168.1.166. Конфигурация промежуточного узла — ssl://192.168.1.166:8530|!proto=udp — представляет собой соединение с транспортом сеансового уровня ssl, работающего поверх транспорта udp, использующего сетевой узел 192.168.1.166:8530 для удаленного подключения (эта строка извлечена из конфигурации семпла, и описывает соединение к С2). Конфигурация конечного узла — ssl://igloogawk.com — описывает соединение с транспортом ssl, работающего поверх транспорта tcp (как было сказано выше, данный транспорт задается по умолчанию при отсутствии явной спецификации транспорта).
- raw://192.168.1.166:2212|!proto=udp|!udp_type=raw — данная строка описывает вариант соединения к серверу 192.168.1.166:2212 (тоже используется как С2, точнее как промежуточный узел к нему). В качестве транспорта сеансового уровня выбран «псевдопротокол» raw, который не регистрируется в фабрике и не имеет отдельного компонента (класса) для своей реализации. Использование ключевого слова raw как имени транспорта сеансового уровня означает, что данное соединение использует только транспортный уровень для передачи данных. То есть по сути raw — это «пустой» протокол сеансового уровня. В качестве нижележащего транспорта здесь выбран udp. В конфигурации указан дополнительный параметр udp_type, значение которого — raw — указывает, что будет использована версия транспорта udp без поддержки сессий, надежной доставки и сборки данных в корректном порядке. Подробнее об этом см. в описании транспорта udp.
9.3. Конкретные транспорты
Функции интерфейса транспорта имеют приведенные ниже прототипы:
void CloseConnection(TRANSPORT *this); StatusCode ConnectToServer(TRANSPORT *this); StatusCode SendBuf(TRANSPORT *this, const char *buffer, int bufLen); StatusCode RecvBuf(TRANSPORT *this, const char *buffer, int bufCapacity); BOOL IsConnectionEstablished(TRANSPORT *this); StatusCode ListenToConnection(TRANSPORT *this); StatusCode AcceptConnection(TRANSPORT *this, TRANSPORT **acceptedConnection); char * GetPeerNameString(TRANSPORT *this); u_short GetPeerAddressData(TRANSPORT *this); __int64 GetTransportDescriptor(TRANSPORT *this); StatusCode SendSessionControlCmd(TRANSPORT *this, int cmd, const char *cmdArgs, unsigned int cmdArgsSize); StatusCode RecvSessionControlCmd(TRANSPORT *this, int *cmd, const char *cmdArgsBuf, unsigned int cmdArgsBufCapacity);
Интерфейс транспорта включает в себя стандартную функциональность:
- установления соединения с сервером (ConnectToServer);
- приема входящих соединений от клиентов (ListenToConnection, AcceptConnection);
- приема и отправки данных (RecvBuf, SendBuf);
- получения сведений о статусе соединения, а также информации об удаленном узле соединения (IsConnectionEstablished, GetPeerNameString, GetPeerAddressData).
При этом имеются нестандартные функции, в частности:
- получение дескриптора транспорта (GetTransportDescriptor);
- приема и передачи команд управления сеансом (RecvSessionControlCmd, SendSessionControlCmd).
Рассмотрим нестандартные функции интерфейса транспорта подробнее. Дескриптор транспорта представляет собой 64-битное целое число. В частности, для соединения, работающего поверх транспорта pipe, это будет HANDLE используемого именованного канала, для соединений поверх транспортов udp и tcp это будет дескриптор соответствующего сокета.
Такая абстракция вводится для обеспечения возможности единообразного «опроса» массива соединений на предмет наличия входящих данных или готовности к отправке данных. Для этого используется расширенный аналог функции int WSAAPI WSAPoll(LPWSAPOLLFD fdArray, ULONG fds, INT timeout), который отличается от оригинала тем, что в текущей реализации транспортной подсистемы бэкдора может единообразно опрашивать как сокеты, так и пайпы (как анонимные, так и именованные). В транспортной системе бэкдора применяются исключительно неблокирующие сокеты и пайпы.
Функция «опроса» регистрируется в фабрике. Таким образом, набор обрабатываемых видов дескрипторов может быть расширен путем создания новой фабрики и регистрации в ней соответствующей функции «опроса».
Упомянутые функции приема и передачи команд управления сеансом (RecvSessionControlCmd, SendSessionControlCmd) применяются только транспортами прикладного уровня и используются для обмена служебными командами при установлении соединения.
TCP
Данный транспорт относится к транспортному уровню. Применяется по умолчанию, если в конфигурации соединения транспортный уровень явно не задан. Реализован как поверх IPv4 (имя — tcp), так и поверх IPv6 (в конфигурации задается под именем tcp6). Протокол IPv6 применяется также в случае, если в строке конфигурации имя протокола сеансового уровня отделено от параметров узла последовательностью символов ://[, как, например в таком случае: ssl://[igloogawk.com.
По сути основные функции транспорта представляют собой обертку над API Winsock, весь объем сетевых операций реализуется внутри функций указанной библиотеки. В конфигурации может дополнительно указываться параметр bind_ip — локальный адрес, с которым будет ассоциирован используемый в транспорте сокет.
UDP
В соответствии с иерархией, приведенной в разделе «Обзор компонентов транспортной подсистемы», udp относится к транспортному уровню, что предполагает возможность надежной передачи данных между узлами.
В этом случае мы как раз имеем дело с ситуацией, когда название транспорта — udp — не соответствует свойствам фактически реализованного протокола, что будет подробнее рассмотрено далее. Пример конфигурации соединения с использованием данного транспорта:
raw://192.168.1.166:2212|!proto=udp|!udp_type=raw
Рассматриваемый транспорт может функционировать в одном из двух режимов:
- «сырой» UDP, когда данные передаются в простых UDP-датаграммах без какой-либо дополнительной логики над ними; данному режиму соответствует наличие параметра udp_type=raw в конфигурации соединения;
- connection oriented — протокол поверх UDP, который обеспечивает установление соединения между узлами, надежную доставку пакетов, гарантирует целостность передаваемых данных, что придает ему некоторое сходство с TCP. Поверхностное сравнение данного протокола с существующими аналогами, работающими поверх UDP, показывает некоторое отдаленное сходство с протоколами SCTP, RUDP. В целом это полноценный самописный протокол, обеспечивающий надежную передачу данных между узлами.
Далее при описании протокола UDP-транспорта будем по умолчанию иметь в виду режим его работы с установлением соединения.
UDP-транспорт логически подразделяется на обработчик соединений и интерфейсы, формируя между ними отношение «один ко многим». Обработчик соединений выполняется в отдельном потоке, который создается всегда в единственном экземпляре в рамках инстанса бэкдора при наличии хотя бы одного активного соединения поверх UDP-транспорта.
Обработчик соединений реализует всю логику протокола, отвечая:
- за опрос транспортных дескрипторов на предмет наличия входящих данных либо готовности к отправке данных;
- формирование и поддержку списка активных соединений (список является глобальным в рамках инстанса бэкдора);
- формирование исходящих и разбор входящих пакетов;
- установление и завершение соединений;
- обеспечение корректной cборки входящих данных.
Обмен данными между интерфейсом транспорта и обработчиком соединений происходит посредством анонимных пайпов, инициализирующихся при создании соединения. Так, например, с точки зрения интерфейса отправка данных выглядит просто как их запись в соответствующий пайп:
int __fastcall UdpTransport::SendBuf(UDP_TRANSPORT *this, const char *dataToSend, DWORD dataLen) { if ( this->connectionState == UDP_CONNECTION_ESTABLISHED ) return WritePipe(this->sendPipeInterfaceEndpoint, dataToSend, dataLen); else return ERR_NOT_CONNECTED; }
Указанные данные принимаются обработчиком соединений, который реализует всю логику отправки данных. То же самое с приемом данных: интерфейс просто читает их из соответствующего пайпа, куда их после приема и обработки записывает обработчик соединений:
int __fastcall UdpTransport::RecvBuf(UDP_TRANSPORT *this, char *buf, DWORD bufCapacity) { if ( this->connectionState >= UDP_CONNECTION_ESTABLISHED ) return ReadDataFromPipe(this->recvPipeTransportEndpoint, buf, bufCapacity); else return ERR_NOT_CONNECTED; }
Соответственно, указанным образом передаются не только данные для отправки и приема, но и служебные сообщения о статусе установления соединения. Таким же образом на вновь принятые соединения передаются указатели, если транспорт работает в режиме сервера:
int __fastcall UdpTransport::AcceptConnection(UDP_TRANSPORT *this, UDP_TRANSPORT **acceptedConnectionPtr) { int readPipe; int status; UDP_TRANSPORT *acceptedConnection; readPipe = this->recvPipeInterfaceEndpoint; acceptedConnectionPtr = 0i64; status = ReadDataFromPipe(readPipe, &acceptedConnectionPtr, 8u); if ( !status ) status = ERR_NETWORK_DATA_TRUNCATED; if ( acceptedConnection ) *acceptedConnectionPtr = acceptedConnection; return status; }
Формат пакета
Пакет состоит из заголовка и данных. Заголовок имеет следующий формат:
[размер данных(2 байта)][идентификатор соединения (stream id, 4 байта)][номер в последовательности(sequence number, 4 байта)][флаги(1 байт)][данные]
Назначение пакета определяется его флагами, которых используется всего четыре:
- UDP_SYN = 1 — установление соединения;
- UDP_ACK = 2 — подтверждение;
- UDP_FIN = 4 — завершение соединения;
- UDP_DEMAND_CHUNK = 8 — запрос пакета.
Подробнее про их использование — ниже.
Идентификатор соединения (stream id) позволяет узлу определять, к какому соединению относится тот или иной принятый пакет. Номер последовательности (sequence number) задает порядок, в котором необходимо осуществлять сборку пакетов в единый буфер при их приеме.
Установление и завершение соединения
Установление соединения происходит в два шага:
- Для установления соединения клиент направляет на сервер сообщение с установленным флагом UDP_SYN и идентификатором соединения клиента client_stream_id в теле пакета сообщения.
- Сервер отвечает клиенту сообщением с установленными флагами UDP_SYN|UDP_ACK и идентификатором соединения сервера server_stream_id.
Идентификаторы client_stream_id и server_stream_id имеют размер 4 байта и генерируются узлом случайно. Указанные идентификаторы не совпадают с аналогичными идентификаторами других UDP-соединений в рамках одного инстанса бэкдора. Указанные идентификаторы предназначены для того, чтобы определить конкретное соединение UDP-транспорта, которому предназначен тот или иной пакет. Для завершения соединения узел направляет корреспонденту пакет UDP_FIN, после чего закрывает соединение.
Передача данных
Передача данных происходит пакетами размером не более 1460 байт (включая заголовок пакета). Такая величина, вероятно, была выбрана для того, чтобы итоговый размер пакета не превосходил MTU для Ethernet.
Пакет с данными не имеет флагов. При получении пакета с данными узел отправляет адресату пакет UDP_ACK с sequence number принятого пакета. Принятые пакеты собираются в единый буфер в соответствии со своими sequence number и передаются на интерфейс.
В случае потери пакетов предусмотрен механизм их повторного запроса, который функционирует следующим образом:
- Для каждого соединения хранится sequence number следующего пакета, который необходимо отправить на интерфейс (с учетом того, что предыдущие направлены). Пусть он будет равным N.
- Предположим, что узел получает пакет с sequence number N+m, но предыдущие N+1, ... N+m-1 пакеты еще не получены. В таком случае узел требует отправить еще не пришедшие пакеты повторно, направляя пакеты UDP_DEMAND_CHUNK c sequence number N+1, ..., N+m-1. Такая процедура проводится для каждого пакета, пришедшего вне последовательности. Таким образом, пакеты UDP_DEMAND_CHUNK могут отправляться для одного и того же sequence number многократно.
- Если затребованный таким образом пакет был получен, узел направляет подтверждение UDP_DEMAND_CHUNK|UDP_ACK c sequence number полученного пакета.
С учетом того, что пакеты UDP_DEMAND_CHUNK могут отправляться многократно, при получении такого пакета узел не отправляет требуемый пакет сразу. Ведется счетчик таких требований, и отправка происходит, если их число достигнет 32. После этого счетчик обнуляется.
PIPE
Относится к транспортному уровню в соответствии с иерархией, приведенной в разделе «Обзор компонентов транспортной системы». Регистрируется в фабрике под именем pipe. Реализует транспорт поверх именованных каналов Windows. Пример конфигурации соединения, использующего данный транспорт: raw://name_of_the_pipe|!proto=pipe
Этот транспорт также представляет собой «обертку» над функциями для работы с именованными каналами WinAPI.
В частности, установление соединения (ConnectToServer) выполняется посредством API CreateFileA; прием и обработка входящих соединений (ListenToConnection, AcceptConnection) — через функции CreateNamedPipeA, ConnectNamedPipe; прием и передача данных — посредством API-вызовов ReadFile/WriteFile. Ниже приведен пример функции ListenToConnection данного транспорта:
__int64 __fastcall PipeTransport::ListenToConnection(PIPE_TRANSPORT *this) { __int64 result; unsigned int v3; int hDataPipe; HANDLE EventA; PIPE_TRANSPORT *i; this->isServer = 1; result = CreatePipeNoBlockingMode(&this->hControlPipeRead, &this->hControlPipeWrite, 0i64, 0); v3 = result; if ( (int)result < 0 ) return result; hDataPipe = (unsigned int)CreateNamedPipeA( this->transportPipePath, 0x40080003u, 0, 0xFFu, 0x100000u, 0x100000u, 0, 0i64); if ( hDataPipe < 0 ) return -GetLastError(); EventA = CreateEventA(0i64, 1, 0, 0i64); this->pipeOverlapped.hEvent = EventA; if ( !EventA ) return -GetLastError(); this->hDataPipeInstance = hDataPipe; if ( !ConnectNamedPipe(hDataPipe, &this->pipeOverlapped) ) { result = -GetLastError(); if ( result != -ERROR_IO_PENDING && result != -ERROR_PIPE_CONNECTED ) return result; v3 = 0; } if ( PipeTransport::ActiveConnectionsList ) { this->next = PipeTransport::ActiveConnectionsList; for ( i = PipeTransport::ActiveConnectionsList; i->next; this->next = i ) i = i->next; i->next = this; } else { PipeTransport::ActiveConnectionsList = this; } this->next = 0i64; if ( !PipeTransport::ConnectionsListHead && !CreateThread(0i64, 0i64, (LPTHREAD_START_ROUTINE)PipeTransport::CleanupInactiveConnections, 0i64, 0, 0i64) ) { return -GetLastError(); } return v3; }
При работе в режиме сервера все входящие соединения, созданные посредством транспортов pipe, помещаются в единый глобальный (в рамках экземпляра бэкдора) список установленных соединений (PipeTransport::ActiveConnectionsList, в примере выше). Также создается отдельный поток, который с периодичностью 1 раз в 100 секунд проходит этот список и удаляет все закрытые соединения (PipeTransport::CleanupInactiveConnections в листинге выше).
Примечательно, что при добавлении или удалении соединений из списка PipeTransport::ActiveConnectionsList не используется никаких примитивов синхронизации (критических секций, мьютексов и т. п.), несмотря на то что доступ к списку может осуществляться несколькими потоками. Вероятно, данный транспорт реализован как вариант коммуникации между логически изолированными сегментами сети и С2 (через промежуточный узел) для случаев, когда передача данных (команд) традиционными протоколами затруднена.
SSL
Данный транспорт относится к сеансовому уровню в соответствии с иерархией из раздела «Обзор компонентов транспортной системы» и реализует протокол SSL. Регистрируется в фабрике под именем ssl либо ssl3.
По аналогии с TCP-транспортом, представляет собой «обертку» над функциями библиотеки wolfSSL, статически скомпонованной с рассматриваемым семплом, которая и реализует весь объем операций по установлению и завершению соединения, приему и отправке данных.
Вариант транспорта c именем ssl реализует протокол TLS версии 1.3, вариант ssl3 реализует протокол SSL версии 3.
При установлении соединения сертификат сервера не верифицируется.
HTTP (HTTPS)
Этот транспорт относится к сеансовому (в терминах данного бэкдора). Работает он поверх транспорта нижележащего (транспортного) уровня, который должен обеспечивать надежную передачу данных между узлами. Следует иметь в виду, что данный транспорт не соответствует спецификации протокола HTTP.
HTTP-транспорт реализован в виде двух логических компонент:
- интерфейс;
- отдельный поток, непосредственно осуществляющий прием и передачу данных (далее — поток).
Взаимодействие интерфейса с потоком осуществляется посредством анонимных пайпов. Например, при вызове метода отправки данных интерфейса SendBuf(TRANSPORT *this, char *buf, DWORD bufLen) данные для передачи просто записываются в соответствующий пайп, а всю работу по приему и отправке данных непосредственно по сети осуществляет отельный поток. Этот поток создается всегда в единственном экземпляре. Он обрабатывает все существующие соединения HTTP-транспорта, каждое из которых регистрируется глобально в рамках процесса бэкдора.
Основные параметры транспорта: host, url, версия протокола, а также дополнительные заголовки задаются в конфигурационной строке данного транспорта.
Установление соединения осуществляется следующим образом:
- Клиент отправляет POST-запрос на заданный в конфигурационном файле url.
- Сервер при приеме соединения направляет ответ HTTP/<версия протокола> 200 (в отличие от стандартного ответа об успешном выполнении операции HTTP/<версия протокола> 200 OK). Стоит отметить, что в указанном протоколе изменен смысл заголовка Content-Length. В стандартном HTTP здесь указывается длина тела сообщения (та часть, которая следует после заголовков и двойного CRLF). В данном же протоколе Content-Length содержит длину всего HTTP-пакета, включая заголовки и тело сообщения. В теле каждого из упомянутых запросов и ответов имеется дополнительный заголовок следующего формата:
struct HttpChunkHeader { char encodeKey; // ключ, с помощью которого кодируется указанный заголовок DWORD dataSize; // размер данных, следующих сразу после заголовка QWORD connectionId; // идентификатор соединения char checkSum; // контрольная сумма }
Контрольная сумма checkSum вычисляется здесь как побайтовая сумма по модулю 2 (xor) всей предыдущей части заголовка пакета. После вычисления контрольной суммы все заголовки и данные пакета (кроме encodeKey) кодируются однобайтовым xor с ключом encodeKey. При этом каких-либо данных с указанными заголовками не передается, несмотря на то что данный формат предполагает такую возможность. Таким образом, обмен заголовками при установлении соединения нужен прежде всего для того, чтобы сообщить клиенту сгенерированный сервером идентификатор вновь установленного соединения (connectionId).
После установления соединения осуществляется обмен данными между узлами. При этом данные передаются в неизменном виде, без каких-либо дополнительных HTTP-заголовков и преобразований.
Данный транспорт может функционировать в двух режимах:
- Connection: keep-alive — после полного получения или передачи данных оставляет транспортное соединение открытым.
- Connection: close — закрывает транспортное соединение каждый раз после полной передачи или получения данных.
Режим функционирования определяется наличием соответствующих заголовков в запросах на установление соединения.
При возникновении ошибок соединения транспортного уровня в реализацию данного протокола встроена возможность возобновления транспортного соединения. Так, если ошибка транспорта возникла на стороне клиента, он устанавливает соединение с сервером заново, передавая в заголовке запроса (HttpChunkHeader) соответствующий connectionId прерванного соединения.
HTTPS-транспорт — это описанный транспорт http, фактически работающий поверх транспорта SSL.
PROXY-HTTP (PROXY-HTTPS)
Данный транспорт представляет собой «обертку» над HTTP- и HTTPS-транспортом, дополняя ее функциональностью установки соединения через HTTP-прокси с возможностью аутентификации. Данный транспорт поддерживает два метода аутентификации — Basic и NTLM.
При установлении соединения с HTTP-прокси клиент пытается применить Basic-аутентификацию. Если это не удается, осуществляется NTLM-аутентификация.
9.4. Транспорты прикладного уровня
Как уже было отмечено выше, протоколы прикладного уровня работают поверх транспортов сеансового уровня. В отличие от протоколов предыдущих уровней, функции создания рассматриваемых транспортов не регистрируются в фабрике, их инстанцирование осуществляется непосредственно транспортным интерфейсом при создании нового соединения. Таким образом, набор транспортов прикладного уровня задан в бэкдоре достаточно жестко и не подлежит расширению через систему сетевых плагинов.
Анализируемый образец бэкдора содержит четыре транспорта прикладного уровня: auth, control, client, agent. В конфигурации транспорта протокол прикладного уровня задается значением параметра !type, например: ssl://igloogawk.com|!type=auth. Также для задания транспорта прикладного уровня может применяться ключевое слово raw, использование которого означает, что соединение не использует никаких протоколов указанного уровня и отправляет данные в необработанном виде.
Транспорты control, client, agent работают поверх транспорта auth. Рассмотрим каждый из них подробнее.
AUTH
Основная функция данного транспорта — предоставление механизма передачи команд управления сеансом для вышележащих протоколов прикладного уровня. Для реализации этого транспорт auth определяет методы SendSessionControlCmd, RecvSessionControlCmd, а также реализует свою собственную процедуру установления соединения, переопределяя метод EstablishConnection. В исследованных образцах бэкдора иные транспорты, предоставляющие средства управления сеансом на прикладном уровне, отсутствуют, auth — единственный такой транспорт.
Для этого транспорта есть два режима установления соединения: напрямую, либо через промежуточный узел. При установлении соединения напрямую реализуется следующий протокол:
- Клиент устанавливает соединение с сервером на транспортном и сеансовом уровнях.
- Клиент отправляет на сервер команду COMMAND_CONNECT (код команды — 0×00) без аргументов.
- Если сервер отправляет клиенту в ответ команду COMMAND_CONNECT_RESPONSE (код команды — 0×01) без аргументов, то соединение считается установленным.
- Если сервер отправляет клиенту в ответ команду COMMAND_RECONNECT (код команды — 0×08), то процедура установления соединения начинается заново с шага № 1. Схематично он представлен следующим образом:
В указанном выше описании, а также в описании протоколов иных транспортов прикладного уровня все команды передаются и принимаются посредством методов SendSessionControlCmd/RecvSessionControlCmd.
Установление соединения через промежуточный узел (в дальнейшем будем обозначать его «прокси») осуществляется следующим образом:
- Клиент устанавливает соединение с прокси на транспортном и сеансовом уровнях.
- Клиент отправляет на прокси команду COMMAND_CONNECT_TO_PEER (код команды — 0×02), передавая в качестве аргумента строку конфигурации соединения до последующей промежуточной (либо конечной) точки в сетевом маршруте.
- Прокси устанавливает соединение со следующей промежуточной точкой либо напрямую, если нет промежуточных узлов, либо по описываемому протоколу при наличии промежуточных узлов.
- При завершении установления соединения прокси с промежуточным узлом прокси отправляет клиенту команду COMMAND_CONNECTION_ESTABLISHED (код команды — 0×06), без аргументов. После этого соединение считается установленным и осуществляется уже непосредственно передача данных.
Схематично этот протокол представлен ниже:
Команды в данном транспорте передаются в следующем формате:
typedef struct { char key; char commandValueDecodingTableId; char encodedCommand; int argumentSize; char headerCheckSum; char argumentData[]; } COMMAND_PACKET;
Поле argumentSize при формировании пакета конвертируется в BigEndian. Иные элементы заголовка при формировании пакета изменениям не подвергаются. При формировании пакета также вычисляется контрольная сумма — headerCheckSum, которая представляет собой побайтную сумму по модулю 2 (xor) полей commandDecodingTableId, encodedCommand, argumentSize. При приеме команды производится проверка данной контрольной суммы.
После вычисления контрольной суммы на сформированный пакет, включая заголовки и все данные, накладывается однобайтовый xor ключом key. Этот ключ случайно генерируется при формировании пакета. Гарантируется, что он будет ненулевым.
Коды команд не передаются в явном виде. Для кодирования применяется таблица подстановки, которая должна совпадать у клиента, сервера и у всех промежуточных узлов. EncodedCommand — значение команды после кодирования таблицей, commandValueDecodingTableId — идентификатор (номер) таблицы среди всех таких таблиц бэкдора. Несмотря на то что таблиц подстановок, используемых разными транспортами, может быть несколько, в исследованных образцах используется единственная таблица (с номером 1), представленная ниже:
char commandEncodingTable = [0x24, 0x64, 0x13, 0xA4, 0xB2, 0x2C, 0x4F, 0x9F, 0xAF];
AGENT и CLIENT
Транспорты agent и client используются в качестве основного транспорта прикладного уровня при организации соединения с С2. Для осуществления соединения с их использованием необходим дополнительный узел, который мы обозначим как auth server (данное название в образцах не встречается, вводится здесь для удобства описания). Также обозначим узлы, между которыми реализуется соединение указанными транспортами, как узел 1, узел 2. Транспорты agent и client являются парными в том смысле, что если узел 1 подключен к auth server при помощи транспорта agent, то узел 2 может быть подключен к auth server только транспортом client и наоборот.
Соединение, с использованием которого экземпляр бэкдора получает команды для выполнения, имеет тип agent. Исходя из контекста использования можно предположить, что соединение, с использованием которого оператор бэкдора передает команды для выполнения на auth server, имеет тип client.
Возможная схема взаимодействия представлена на диаграмме ниже:
При этом возможность функционировать как auth sever имеется в каждом экземпляре бэкдора, что дает возможность использовать практически любой работающий инстанс бэкдора (уже зараженные устройства в интересующей сети, скомпрометированные серверы в интернете) как С2 при наличии у оператора возможности подключиться к узлу с бэкдором в качестве клиента. Здесь создается логическое соединение «зараженный узел — устройство оператора бэкдора», которое может проходить опосредованно, через множество скомпрометированных узлов.
При поступлении нового соединения типа agent auth server принимает его (на нижележащих транспортных уровнях), а также устанавливает по протоколу транспорта auth, поверх которого работают agent и client. При этом используется вариант установления соединения без промежуточного узла (см. описание транспорта auth). После этого производится поиск подходящего свободного соединения типа client. При отсутствии такового новое соединение остается в ожидающем состоянии. При наличии или появлении любого подходящего свободного соединения типа client между ним и вновь принятым соединением при посредничестве auth server устанавливается сеанс и происходит передача данных (тоже при посредничестве auth server). Описанное верно и для нового соединения типа client. Два свободных, т. е. находящихся в ожидающем состоянии соединения auth и client считаются подходящими при одновременном наличии или отсутствии в параметрах обоих соединений необходимости проверки их подлинности. Данный параметр задается в конфигурации транспорта (ключ сериализации NEED_TO_ENSURE_CLIENT_AUTHENCITY — 0×30047).
Рассмотрим протокол установления соединения между узлами agent и client подробнее. Как уже было отмечено выше, узлы client и agent при посредничестве auth server устанавливают между собой сеанс, по которому обмениваются зашифрованными данными. Данные шифруются алгоритмом RC4 на сеансовом ключе, вырабатываемом сторонами при установлении соединения. Для выработки сеансового ключа используется протокол Диффи — Хеллмана на эллиптических кривых (ECDH) с эфемерными открытыми ключами (ECDHE). Такая схема позволяет скрывать содержимое данных, передаваемых от auth server.
Протоколы установления сеанса различаются в зависимости от вышеупомянутого параметра NEED_TO_ENSURE_CLIENT_AUTHENCITY (0×30047). Если он установлен, в процедуру установления сеанса добавляются дополнительные шаги для проверки подлинности корреспондентов, а именно — для подтверждения того факта, что сторонами соединения не являются сторонние клиенты, не предусмотренные операторами бэкдора.
Рассмотрим подробнее протокол установления сеанса от лица клиента, использующего транспорт agent.
- Клиент устанавливает соединение c auth server (далее в описании — сервер) на транспортном и сеансовом уровнях.
- Клиент отправляет на сервер команду COMMAND_CONNECT_AGENT (код команды — 0×3) с аргументом connect request, который содержит в себе данные, необходимые для установления соединения. Connect request содержит следующие сериализованные данные:
- эфемерный открытый ключ для выработки сеансового ключа (ключ сериализации — 0x50052);
- тип сжатия передаваемых данных (ключ сериализации — 0x30051);
- данные о том, нужно ли проверять подлинность клиента NEED_TO_ENSURE_CLIENT_AUTHENCITY (ключ сериализации — 0x30047).
- При отсутствии необходимости проверки подлинности (NEED_TO_ENSURE_CLIENT_AUTHENCITY == 0) сервер находит подходящий client, после чего отправляет agent команду COMMAND_CONNECT_CLIENT (код команды — 0x4) с аргументом connect response. Данный аргумент включает:
- сообщение connect request, полученное от client при установлении им сеанса с auth server. Содержимое сообщения описано в п. 2;
- peer info — строковое представление сведений об узле client (например, строка "IP-адрес:порт"), ключ сериализации — 0xC0048. Для client отправляется команда COMMAND_CONNECT_AGENT с аргументом connect response, который включат в себя аналогичные компоненты — запрос на установления соединения и сведения об узле. После обмена данными сообщениями стороны вырабатывают сеансовый ключ и соединение считается установленным. Передача данных между ними осуществляется через сервер.
- При необходимости проверки подлинности сторон (NEED_TO_ENSURE_CLIENT_AUTHENCITY == 1), сервер отправляет agent команду COMMAND_RECONNECT, с аргументом message to sign (ключ сериализации — 0x5004F) — сообщение, которое необходимо подписать, представляющее собой случайно сгенерированный массив размером 32 байта.
- Agent вычисляет цифровую подпись полученного сообщения, используя схему цифровой подписи, основанной на эллиптической кривой Эдвардса (EdDSA). Закрытый ключ задается в конфигурации транспорта (ключ сериализации — 0x50049). После этого agent повторно отправляет на сервер команду COMMAND_CONNECT_AGENT с аргументом connect request signed, который включает в себя:
- сообщение message to sign (ключ сериализации — 0x5004F);
- его цифровую подпись (ключ сериализации — 0x50050);
- открытый ключ для проверки подписи (ключ сериализации — 0x5004E);
- вновь сгенерированный эфемерный открытый ключ для выработки сеансового ключа (ключ сериализации — 0x50052);
- тип сжатия передаваемых данных (ключ сериализации — 0x30051);
- данные о том, нужно ли проверять подлинность клиента NEED_TO_ENSURE_CLIENT_AUTHENCITY (ключ сериализации — 0x30047).
- Сервер на своей стороне проводит валидацию принятого сообщения, которое должно удовлетворять следующим условиям:
- полученное сообщение message to sign должно совпадать с отправленным;
- полученный открытый ключ для проверки сообщения (ключ сериализации — 0x5004E) должен совпадать с открытым ключом, заранее заданным в сервере (для исследованных семплов это hardcoded-массив 6E 98 0C 6B 8F 5F 70 5C 27 61 54 05 03 DF 64 C5 FA 28 92 5D 5A 94 6C 21 F7 7F 4F 00 B4 11 E5 A1).
- Полученная цифровая подпись должна быть валидной. После успешной проверки подлинности происходит установление сеанса по схеме, описанной в пункте 3. Для соединения типа client процедура установления соединения и проверки подлинности аналогична за тем лишь исключением, что вместо команды COMMAND_CONNECT_AGENT отправляется команда COMMAND_CONNECT_CLIENT.
Схематично данный протокол можно изобразить следующим образом:
Передача данных в соединениях рассматриваемого типа осуществляется в буферах, предваряемых заголовками следующего формата:
<размер передаваемых данных, 4 байта> [<порядковый номер сообщения - seq_num, 4 байта><размер данных до (сжатия), 4 байта> <сами данные>]
При этом порядковый номер сообщения, размер данных до сжатия и сами данные шифруются алгоритмом RC4 на выработанном при установлении соединения сеансовом ключе. При получении очередного буфера данных сторона отправляет подтверждение в виде пустого буфера со следующим заголовком:
<размер передаваемых данных == 4, 4 байта> [ <порядковый номер сообщения seq_num, 4 байта> <порядковый номер буфера, получение которого мы подтверждаем, 4 байта> ]
Все поля указанного пакета, кроме первого, также шифруются сеансовым ключом. При этом повторной отправки данных, получение которых не подтверждено, не производится. Этот механизм применяется для учета статистики передачи данных сеанса, о котором будет рассказано ниже.
Существует возможность предварительного сжатия передаваемых данных алгоритмом LZ4 в случае, если обе стороны поддерживают такую возможность. Доступные режимы сжатия данных задаются в конфигурации транспорта (COMPRESSION_TYPE, ключ сериализации — 0×30051) и имеют значения от 1 до 3. При этом, если задан режим сжатия n, сторона поддерживает все режимы сжатия, начиная с 1 до n. При передаче данных во время сеанса согласуется режим сжатия, имеющий наименьшее значение из доступных режимов для каждой из сторон.
В исследованных экземплярах, если значение параметра COMPRESSION_TYPE равно 1, то передача происходит без сжатия. Если значение параметра равно 2 или 3, используется сжатие LZ4. Каких-либо отличий у режимов сжатия 2 и 3 не имеется.
Сжатие передаваемого в рамках сеанса очередного буфера не применяется в следующих случаях:
- Если размер случайно выбранного 256-байтного участка передаваемого буфера в сжатом виде составляет более 80% от исходного размера.
- Если размер передаваемого буфера меньше 256 байт.
- Если по каким-либо причинам не удалось сжать передаваемый буфер.
В ходе передачи данных обеими сторонами сеанса ведется учет статистики передачи данных; вычисленные показатели сериализуются в конфигурации соединений сторон. Вычисляются следующие показатели:
- Скорость отправки сжатых данных, COMPRESSED_DATA_SEND_RATE, ключ сериализации — 0×30055.
- Скорость отправки несжатых данных, RAW_DATA_SEND_RATE, ключ сериализации — 0×3004B.
- Скорость приема сжатых данных, COMPRESSED_DATA_RECV_RATE, ключ сериализации — 0×30056.
- Скорость приема несжатых данных, RAW_DATA_RECV_RATE, ключ сериализации — 0×3004C.
Какой-либо функциональности для работы с указанными показателями в исследованных семплах не обнаружено.
CONTROL
Транспорт control является вспомогательным и нужен для получения сведений о состоянии auth server. В текущей реализации бэкдора данный протокол поддерживает только возможность получения количества соединений типа agent (отдельно для соединений с проверкой подлинности и без таковой), которые находятся в «ожидающем» состоянии.
Можно предположить, что клиент, использующий данный транспорт, может применяться оператором бэкдора, например, для отслеживания новых соединений типа agent от находящихся в управлении бэкдоров и подключения к ним соответствующих соединений типа client.
Реализация варианта клиентской части транспорта control присутствует во всех исследованных экземплярах бэкдора, однако она является в некотором роде урезанной, так как всего лишь устанавливает соединение с auth server, не направляя ему никаких команд и не получая от него никаких сведений.
Протокол установления соединения схож с аналогичными протоколами транспортов auth и client, за тем исключением, что при установлении соединения типа control проверка подлинности осуществляется подключаемого клиента осуществляется всегда. Также отличаются коды направляемых команд и содержание аргументов.
Установление соединения происходит по следующей схеме:
- Клиент подключается к серверу на нижележащих уровнях транспорта.
- Клиент отправляет серверу команду COMMAND_CONNECT_CONTROL (код команды — 0×5) с аргументом, содержащим сериализованный флаг NEED_TO_ENSURE_CLIENT_AUTHENTICITY (0×30047). Значение этого параметра в контексте данного протокола определяет, сведения о каких соединениях мы хотим получать: о требующих проверки подлинности (значение флага = 1) или о соединениях, не требующих проверки подлинности (значение флага = 0).
- Сервер при получении данной команды по аналогии с сообщениями message to sign транспорта agent генерирует случайное сообщение длиной 32 байта и отправляет клиенту в сериализованном виде (ключ — 0×5004F) в качестве аргумента к команде COMMAND_RECONNECT (код команды — 0×8).
- клиент на своей стороне подписывает полученное сообщение цифровой подписью на основе алгоритма (EdDSA) с использованием закрытого ключа, хранящегося в конфигурации транспорта и снова отправляет серверу команду COMMAND_CONNECT_CONTROL (0×5) со следующими сериализованными данными в качестве аргумента:
- сообщение message to sign (ключ сериализации — 0x5004F);
- его цифровую подпись (ключ сериализации — 0x50050);
- открытый ключ для проверки подписи (ключ сериализации — 0x5004E);
- вновь передаваемый параметр NEED_TO_ENSURE_CLIENT_AUTHENTICITY (ключ сериализации — 0x30047).
- Сервер валидирует полученный ответ в соответствии с критериями, указанными в описании транспортов client и agent. При этом допускается, чтобы открытый ключ, переданный для проверки, совпадал либо с открытым ключом проверки соединений client и agent, либо со вторым возможным ключом:
char pubKey2 = [0xB8, 0x29, 0x7D, 0xF4, 0x02, 0x42, 0x32, 0xEF, 0x60, 0xA3, 0x80, 0x23, 0x91, 0x4F, 0x5D, 0x12, 0x61, 0x9D, 0xAE, 0xE8, 0x57, 0x10, 0x17, 0xE9, 0xB5, 0xB2, 0x9A, 0x3F, 0xE0, 0xA6, 0x45, 0x0D];
Если валидация не проходит, сервер закрывает соединение. При успешном прохождении валидации сервер отправляет клиенту команду COMMAND_CONNECTION_ESTABLISHED (код команды — 0×6), соединение считается установленным и сервер переходит к обработке команд от клиента.
Схематично этот процесс можно изобразить следующим образом:
Как уже было сказано, клиент может направить серверу только одну команду: CONTROL_GET_FREE_AGENTS_COUNT (код команды — 0×4) — получить количество свободных соединениях типа agent (с проверкой подлинности или без, в зависимости от значения параметра NEED_TO_ENSURE_CLIENT_AUTHENTICITY, указанного при установлении соединения). В качестве аргумента к данной команде может передаваться сериализованное значение тайм-аута (ключ — 0×3004A; в секундах), в течение которого следует ожидать новых соединений в случае, если свободных не имеется. Результат выполнения отправляется сервером как аргумент команды COMMAND_CONNECTION_ESTABLISHED (0×06). Если свободных соединений не имеется, сервер отправляет клиенту команду COMMAND_CONTROL_NO_DATA (0×07) без аргументов. После отправки ответа цикл приема и обработки команды повторяется.
Заключение
Проведенный анализ показывает, что для группы Dark River характерен тщательный выбор объектов атаки и их точечный характер, о чем свидетельствуют целенаправленные рассылки, посвященные сфере деятельности атакуемых предприятий. При этом указанные атаки сложно атрибутируются: используемая сетевая инфраструктура для каждого атакуемого объекта разворачивается отдельно и не имеет каких-либо пересечений с другими вредоносными кампаниями.
Основной инструмент группировки, бэкдор MataDoor, имеет модульную архитектуру и сложную, проработанную систему сетевых транспортов с возможностью гибкого выбора способа коммуникации между оператором бэкдора и зараженным устройством. В целом анализ кода показывает, что в разработку данного инструмента вложены существенные ресурсы.
Исследование показывает, что российские предприятия оборонно-промышленного сектора остаются объектом целенаправленных атак, инструментарий реализации которых продолжает усложняться и совершенствоваться. Деятельность группировки Dark River — характерный пример активности с целью шпионажа и кражи конфиденциальной информации.
Авторы: Денис Кувшинов и Максим Андреев при участии команд incident response и threat intelligence PT Expert Security Center.
Приложения
Вердикты продуктов Positive Technologies
YARA правила
exploit_win_ZZ_CVE202140444__Exploit__MSHTML__RCE__Artifact |
tool_win_ZZ_OfficeTemplate__Downloader__Encoding__Artifact |
apt_win_CN_APT41__Trojan__ExportEngineLoaderString |
Поведенческие правила
Trojan.Win32.Evasion.a |
Trojan.Win32.Generic.a |
Trojan.MachineLearning.Generic.a |
Create.Process.NetworkScanner.NetworkScanning |
Create.Process.Reg.RegistryModify |
Create.Process.Regsvr32.RestrictionsBypass |
Create.Process.Rundll32.RestrictionsBypass |
Create.Process.Whoami.Reconnaissance |
Create.Query.WMI.CheckVM |
Delete.Process.TerminateProcess.Evasion |
Read.NetShare.RPC.Enumeration |
Read.Process.Handle.Enumeration |
Read.Registry.Key.NetAdapterId |
Read.Registry.Key.NetInterfaces |
Read.System.Info.Reconnaissance |
Read.System.RemoteResources.Enumeration |
Read.File.Name.Enumeration |
Сетевые правила
PT Network Attack Discovery
BACKDOOR [PTsecurity] Matadoor Magic 10009938 |
BACKDOOR [PTsecurity] Matadoor Magic 10009939 |
BACKDOOR [PTsecurity] Matadoor Magic 10009940 |
BACKDOOR [PTsecurity] Matadoor CnC 10009941 |
BACKDOOR [PTsecurity] Matadoor 10009946 |
BACKDOOR [PTsecurity] Possible Matadoor HTTP in UDP Request 10009947 |
BACKDOOR [PTsecurity] Possible Matadoor HTTP in UDP Response 10009948 |
PT Sandbox
BACKDOOR [PTsecurity] Possible Matadoor 10009942 |
BACKDOOR [PTsecurity] Possible Matadoor HTTP in UDP 10009950 |
BACKDOOR [PTsecurity] Possible Matadoor 10009951 |
BACKDOOR [PTsecurity] Possible Matadoor Response 10009952 |
BACKDOOR [PTsecurity] Possible Matadoor Multiple HTTP Request 10009955 |
BACKDOOR [PTsecurity] Possible Matadoor Multiple HTTP Response 10009956 |
BACKDOOR [PTsecurity] Possible Matadoor HTTP in UDP 10009959 |
BACKDOOR [PTsecurity] Possible Matadoor Multiple HTTP in UDP Request 10009960 |
MITRE
ID |
Имя |
Описание |
Initial access |
|
|
T1566.001 |
Phishing: Spearphishing Attachment |
Группировка Dark River использует фишинговые рассылки с вредоносным вложением |
Execution |
|
|
T1059.003 |
Command and Scripting Interpreter: Windows Command Shell |
ВПО группировки Dark River имеет функциональность удаленной командной строки |
T1106 |
Native API |
ВПО группировки Dark River использует функции WinAPI для запуска новых процессов |
T1129 |
Shared Modules |
ВПО группировки Dark River может загружать дополнительные модули для исполнения вредоносной функциональности |
Persistence |
|
|
T1543.003 |
Create or Modify System Process: Windows Service |
Для закрепления на узле группировка Dark River создает вредоносные сервисы |
Defence evasion |
|
|
T1622 |
Debugger evasion |
Некоторые экземпляры MataDoor запакованы протектором Themida, который определяет наличие отладчика |
T1140 |
Deobfuscate/Decode Files or Information |
Исполняемые файлы MataDoor обфусцированы протектором для затруднения его обнаружения и анализа |
T1036.004 |
Masquerading: Masquerade Task or Service |
Имена исполняемых файлов MataDoor подбираются так, чтобы быть похожими на имена исполняемых файлов легитимного ПО, развернутого на узле |
T1112 |
Modify Registry |
MataDoor хранит свою конфигурацию в зашифрованном виде в реестре |
T1027.002 |
Obfuscated Files or Information: Software Packing |
Все экземпляры MataDoor содержат отдельные функции, защищенные протектором, виртуализирующим отдельные функции |
T1620 |
Reflective Code Loading |
MataDoor рефлективно загружает PE-модули и плагины в адресное пространство своего процесса |
T1218.010 |
System Binary Proxy Execution: Regsvr32 |
MataDoor может запускаться через системную утилиту Regsvr32 |
T1218.011 |
System Binary Proxy Execution: Rundll32 |
MataDoor может запускаться через системную утилиту Rundll32 |
Discovery |
|
|
T1083 |
File and Directory Discovery |
MataDoor может собирать сведения о файлах и каталогах на скомпрометированном узле |
T1135 |
Network Share Discovery |
MataDoor может собирать сведения о разделяемых сетевых ресурсах в скомпрометированной сети |
T1046 |
Network Service Discovery |
MataDoor может собирать сведения о сетевых узлах с заданными открытыми портами |
T1057 |
Process Discovery |
MataDoor может собирать сведения о процессах, запущенных на зараженном узле |
T1018 |
Remote System Discovery |
MataDoor может собирать сведения о доступности сетевых узлов в скомпрометированной сети |
T1082 |
System Information Discovery |
MataDoor может собирать на локальной машине сведения о версии ОС, а также имя компьютера |
T1016 |
System Network Configuration Discovery |
MataDoor может собирать сведения об IP- и MAC-адресах локального сетевого адаптера скомпрометированного узла |
T1049 |
System Network Connections Discovery |
MataDoor может собирать сведения об активных TCP-соединениях на скомпрометированном узле |
T1033 |
System Owner/User Discovery |
MataDoor получает и передает на С2 имя текущего пользователя скомпрометированного узла |
T1124 |
System Time Discovery |
MataDoor может собирать сведения о системном времени на зараженном узле |
Collection |
|
|
T1560.002 |
Archive Collected Data: Archive via Library |
MataDoor может архивировать собранные данные с использованием статически скомпонованной библиотеки zlib |
T1074.001 |
Data Staged: Local Data Staging |
MataDoor может сохранять промежуточные результаты сбора данных в локальных файлах для последующей отправки на С2 |
T1005 |
Data from Local System |
MataDoor может собирать данные с зараженного узла |
Command and control |
|
|
T1071 |
Application Layer Protocol |
MataDoor может использовать протоколы прикладного уровня, такие как SMB, а также самописный протокол, похожий на HTTP для организации связи с С2 |
T1132 |
Data Encoding: Standard Encoding |
MataDoor может сжимать передаваемые по сети данные алгоритмом LZ4 |
T1572.001 |
Encrypted Channel: Symmetric cryptography |
MataDoor применяет алгоритм RC4 для шифрования сетевого трафика |
T1572.002 |
Encrypted Channel: Asymmetric cryptography |
MataDoor содержит статически скомпонованную библиотеку WolfSSL для шифрования сетевого трафика |
T1008 |
Fallback Channels |
MataDoor может использовать альтернативные конфигурации сетевого соединения к С2 |
T1095 |
Non-Application Layer Protocol |
MataDoor может осуществлять сетевые соединения как поверх протокола TCP, так и по самописному протоколу, основанному на UDP-датаграммах |
T1571 |
Non-Standard Port |
MataDoor может устанавливать сетевое соединение с С2, используя нехарактерные для того или иного протокола порты |
T1572 |
Protocol Tunneling |
MataDoor формирует иерархическую систему протоколов, в которой верхний уровень инкаспулируется в нижний, туннелируя таким образом сетевой трафик |
T1090.001 |
Proxy: Internal Proxy |
MataDoor может формировать канал до C2 через прокси, находящийся в скомпрометированной сети |
T1090.002 |
Proxy: External Proxy |
MataDoor может формировать канал до C2 через прокси, находящийся во внешней сети |
T1090.003 |
Proxy: Multi-hop Proxy |
MataDoor может формировать канал связи с С2 через цепочку промежуточных узлов |
Exfiltration |
|
|
T1030 |
Data Transfer Size Limits |
MataDoor делит передаваемые данные на чанки, размер которых может варьироваться |
T1041 |
Exfiltration Over C2 Channel |
MataDoor может передавать данные по существующему каналу коммуникации с С2 |
IOCs
Файловые индикаторы
Loader service
sha256 |
sha1 |
md5 |
2019322c33b648c9d3f7c8a17a990860044c03ed7bd2fc9e82139c22e9bc5635 |
3d4c3856f86c1dac1fe644babe87f1e5b6c6636f |
1f19f7db272cc5ec22eb08987aaffcab |
207f386ebeb29e64e6b7fd10929217e1a664f06e6cc503e8798f57e0af2e5267 |
3f8016bafb700595490b732b92f8501201f0c9af |
01f3a22bf3e409154e79e067370ed98a |
2ba653faef17d9ea623be1138f6f420be27c95d8ad7ee1ea0d15ae718895176d |
bf8f0b845c8f13b4386b7204add3c5d2e504b4c6 |
4d1e16e2b914243e0c63017676956a73 |
748b9e94dc62e1fa364e9daec7d4bbb94a69b304cb81e1a1b6d302be47381a94 |
9cc89d708fcc2b114f6589d8077f66395d4b68ba |
fd7de2b8572f35f0f6f58bba6ff2360e |
9b632505c27fa8ee58f680599fcc0b1794439af17a8c95df9f413e227e34798c |
8a3d32cb67bbf600c81577f4c2dd0a5e601c43d4 |
538505d57722f6f6e747f7f1517f9c7d |
b822db93cde13ee2b2faf41e5a6096782bda7a71ef028641d2ce6ad9db777b67 |
d3d38d113fcaf3ea2e1b8bc5c32182141f918246 |
b52439640b7f0e0273f0d15bb3af6198 |
c8399484d20c0ebed376cc8147e003cf4d930b5130392ae0e14cee0cec42d484 |
6da222a04b4d0ad74f7ab186d235b55a9bcf7a18 |
cc26e5fda0083f750d7748eeaea45350 |
ec70414b2295392cf7200b99747922a5648c4d2882140bd04c7661030aabe928 |
ae0bf4a92b37da3ca4dbd965bc646a747b7ceaf4 |
317f1027095bc41de8fbcfce2c764ac4 |
MataDoor
sha256 |
sha1 |
md5 |
0085a02b9ba24afd266116df43acbd4f57fc8822af4929e7d17b59f2ceae9418 |
9320a614916bbfaa31853d785ffe0ed0fc7b54f4 |
79fc7ed090bc935881e7c242e40071a2 |
3c1cfc2b8b7e5c2d713ec5f329aa58a6b56a08240199761ba6da91e719d30705 |
87e3e59f6653ae1306461bf9683bda92f442d77f |
fe93382464347be4361c7e8fb131a668 |
566835ce413271ddca8d5014c912dda8ba7e5e45573a7da6ba8e20d72541a2ca |
73d6694a0339cc4083f66395b6b4b3da324e2113 |
6f736eac915c2b647bfbba9e5dccf0cb |
660bfbeeaf674e4e95c43bb61d7d0aec88154527e1218e51c1cb75d77c8acdda |
6251126c3a44d5f8a72f0790ae8aba1b195cb5b2 |
610303b58eb5d039c15061e48b743d17 |
ec1205a050693f750dd6a984b68eb2533539a34a5602744127d1b729b22f42fd |
73055a139a248cccb2b6f4360f072f7626b4ce7c |
20ee5ab5724339f16c19be92d0912bb6 |
fdf50a01a8837c9f4280f3e7f7e336f3cbf93a30c78b48aa50c05b45a7f2ee5b |
4a65848af705b2d2b23af0b0795f0ec8bfdc0c69 |
34e3e94f9955c101640b44926bc44393 |
Фишинговые документы
sha256 |
sha1 |
md5 |
0b06fb7f53bb7963ec2ff89d832b831763706e44d206a4d0a8c813ebee633e22 |
f463b1cf8d6dd8004edf047b4dea3c4e283f0ffb |
fcbe52f671d2f20b292c3057320d89a9 |
2e068beb40f8901b698d4fc2f5766564c8324d5ba95fb0a0ffa841f5da5c7e72 |
178b11323f921c0216bedefdd575a9c5a055b9fa |
98e94d7be1d59c17f6bcf3ce09661f83 |
4f544e8756373520e98ed12b921ea7e05a93cf0152405ef3ac65133f7c8660a1 |
e0f4924aeb8befbf6a78411f910d2c148de7c5ff |
c587cdbadc3573149c8b1a78fbbd876f |
84674acffba5101c8ac518019a9afe2a78a675ef3525a44dceddeed8a0092c69 |
4b35d14a2eab2b3a7e0b40b71955cdd36e06b4b9 |
41dacae2a33ee717abcc8011b705f2cb |
a1797d212560de7fd187d0771e8948bd8e0e242bed0ca07665f78076f4e23235 |
09413b5d9d404398bc163bfe239e5f8d149ff412 |
a1fc74b7fb105252aba222f5099fbd04 |
b0a4a1998a1be57d5b9b9ce727d473f46dfc849a3369ee8323d834bebf5ca70a |
647497d00704316a7414d357834ed3f7f111a85f |
bb93392daece237207b6e32fb5fb4f00 |
d00073956786fb8a6b7168b243fa2ea8bb3dff345c020913638ce45c44b78dde |
6924b5219448733c43be7f569b1040d468b939f1 |
0818cda2299b358e1ddf4ea59249a6c4 |
Сетевые индикаторы
fetchbring.com |
cameoonion.com |
kixthstage.com |
fledscuba.com |
capetipper.com |
cravefool.com |
trendparlye.com |
merudlement.com |
ipodlasso.com |
aliveyelp.com |
beez.com |
bestandgood.com |
bettertimator.com |
biowitsg.com |
cakeduer.com |
casgone.com |
diemonge.com |
e5afaya.com |
editngo.com |
eimvivb.com |
endlessutie.com |
flowuboy.com |
futureinv-gp.com |
ganjabuscoa.com |
getmyecoin.com |
iemcvv.com |
interactive-guides.com |
investsportss.com |
ismysoulmate.com |
justlikeahummer.com |
metaversalk.com |
mlaycld.com |
moveandtry.com |
myballmecg.com |
nuttyhumid.com |
otopitele.com |
outsidenursery.com |
primventure.com |
pursestout.com |
reasonsalt.com |
searching4soulmate.com |
speclaurp.com |
sureyuare.com |
tarzoose.com |
wemobiledauk.com |
wharfgold.com |
xdinzky.com |
zeltactib.com |