В конце прошлого года в нашу лабораторию за помощью обратилась зарубежная телекоммуникационная компания, сотрудники которой обнаружили в корпоративной сети подозрительные файлы. В ходе поиска следов вредоносной активности аналитики выявили образец весьма интересного бэкдора. Его анализ показал, что мы имеем дело с очередным модульным APT-бэкдором, использующимся хакерской группой Winnti.
Последний раз деятельность Winnti попадала в наше поле зрения, когда мы анализировали модификации бэкдоров ShadowPad и PlugX в рамках расследования атак на государственные учреждения стран Центральной Азии. Оба эти семейства оказались схожи концептуально и имели примечательные пересечения в коде. Сравнительному анализу ShadowPad и PlugX был посвящен отдельный материал.
В сегодняшней статье мы разберем бэкдор Spyder — именно так окрестили найденный вредоносный модуль наши вирусные аналитики. Мы рассмотрим алгоритмы и особенности его работы и выявим его связь с другими известными инструментами APT-группы Winnti.
Чем примечателен Spyder
Вредоносный модуль представляет собой DLL-библиотеку, которая на зараженном устройстве располагалась в системной директории C:\Windows\System32 под именем oci.dll. Таким образом, модуль был подготовлен для запуска системной службой MSDTC при помощи метода DLL Hijacking. По нашим данным, файл попал на компьютеры в мае 2020 года, однако способ первичного заражения остался неизвестным. В журналах событий мы обнаружили записи о создании служб, предназначенных для старта и остановки MSDTC, а также для исполнения бэкдора.
Log Name: System
Source: Service Control Manager
Date: 23.11.2020 5:45:17
Event ID: 7045
Task Category: None
Level: Information
Keywords: Classic
User:
Computer:
Description:
A service was installed in the system.
Service Name: IIJVXRUMDIKZTTLAMONQ
Service File Name: net start msdtc
Service Type: user mode service
Service Start Type: demand start
Service Account: LocalSystem
Log Name: System
Source: Service Control Manager
Date: 23.11.2020 5:42:20
Event ID: 7045
Task Category: None
Level: Information
Keywords: Classic
User:
Computer:
Description:
A service was installed in the system.
Service Name: AVNUXWSHUNXUGGAUXBRE
Service File Name: net stop msdtc
Service Type: user mode service
Service Start Type: demand start
Service Account: LocalSystem
Также мы нашли следы запуска других служб со случайными именами, их файлы располагались в директориях вида C:\Windows\Temp\<random1>\<random2>, где random1 и random2 являются строками случайной длины из случайных символов латинского алфавита. На момент проведения исследования исполняемые файлы этих служб отсутствовали.
Интересной находкой стала служба, свидетельствующая об использовании утилиты для удаленного исполнения кода smbexec.py из состава набора Impacket. С её помощью злоумышленники организовали удаленный доступ к командной оболочке в полуинтерактивном режиме.
Исследуемый вредоносный модуль oci.dll был добавлен в вирусную базу Dr.Web как BackDoor.Spyder.1. Это название пришло к нам из артефактов бэкдора. В одном из найденных образцов остались функции ведения отладочного журнала и сами сообщения, при этом те из них, которые использовались при коммуникации с управляющим сервером, содержали строку «Spyder».
Бэкдор примечателен рядом интересных особенностей. Во-первых, oci.dll содержит основной PE-модуль, но с отсутствующими файловыми сигнатурами. Заполнение сигнатур заголовка нулями предположительно было сделано с целью затруднения детектирования бэкдора в памяти зараженного устройства. Во-вторых, полезная нагрузка сама по себе не несет вредоносной функциональности, но служит загрузчиком и координатором дополнительных плагинов, получаемых от управляющего сервера. С помощью этих подключаемых плагинов бэкдор выполняет основные задачи. Таким образом, это семейство имеет модульную структуру, как и другие семейства бэкдоров, используемые Winnti, — упомянутые ранее ShadowPad и PlugX.
Анализ сетевой инфраструктуры Spyder выявил связь с другими атаками Winnti. В частности инфраструктура, используемая бэкдорами Crosswalk и ShadowPad, описанными в исследовании Positive Technologies, перекликается с некоторыми образцами Spyder. График ниже наглядно показывает выявленные пересечения.
Принцип действия
Бэкдор представляет собой вредоносную DLL-библиотеку. Имена функций в таблице экспорта образца дублируют экспортируемые функции системной библиотеки apphelp.dll.
Функционально образец является загрузчиком для основной полезной нагрузки, которую хранит в секции .data в виде DLL, при этом некоторые элементы DOS и PE заголовков равны нулю.
Работа загрузчика
Загрузка выполняется в функции, обозначенной как malmain_3, вызываемой из точки входа DLL через две промежуточные функции-переходника.
Далее следует стандартный процесс загрузки PE-модуля в память и вызов точки входа загруженного модуля (DllMain) с аргументом DLL_PROCESS_ATTACH, а после выхода из нее — повторный вызов с DLL_PROCESS_DETACH.
Работа основного модуля
В основном модуле значения всех сигнатур, необходимых для корректной загрузки файла, приравнены к нулю.
IMAGE_DOS_HEADER.e_magic
IMAGE_NT_HEADERS64.Signature
IMAGE_NT_HEADERS64.FileHeader.Magic
Кроме того, TimeDateStamp и имена секций также имеют нулевое значение. Остальные значения корректны, поэтому после ручной правки необходимых сигнатур файл можно загрузить для анализа в виде PE-модуля.
Анализ основного модуля затруднен, так как периодически используются нетипичные способы вызова функций. Для хранения и обработки структур используется библиотека UT hash. Она позволяет преобразовывать стандартные C-структуры в хеш-таблицы путем добавления одного члена типа ut_hash_handle. При этом все функции библиотеки, такие как добавление элементов, поиск, удаление и т. д., реализованы в виде макросов, что приводит к их принудительному разворачиванию и встраиванию (inline) компилятором в код основной (вызывающей) функции.
Для взаимодействия с управляющим сервером используется библиотека mbedtls.
Функция DllMain
В начале исполнения проверяется наличие события Global\\BFE_Notify_Event_{65a097fe-6102-446a-9f9c-55dfc3f45853}, режим исполнения (из конфигурации) и командная строка, затем происходит запуск рабочих потоков.
Модуль имеет встроенную конфигурацию следующей структуры:
struct cfg_c2_block
{
int type;
char field_4[20];
char addr[256];
}
struct cfg_proxy_data
{
DWORD dw;
char str[256];
char proxy_server[256];
char username[64];
char password[32];
char unk[128];
};
struct builtin_config
{
int exec_mode;
char url_C2_req[100];
char hash_id[20];
char string[64];
char field_BC;
cfg_c2_block srv_1;
cfg_c2_block srv_2;
cfg_c2_block srv_3;
cfg_c2_block srv_4;
cfg_proxy_data proxy_1;
cfg_proxy_data proxy_1;
cfg_proxy_data proxy_1;
cfg_proxy_data proxy_1;
int CA_cert_len;
char CA_cert[cert_len];
};
Поле hash содержит некое значение, которое может являться идентификатором. Это значение используется при взаимодействии с управляющим сервером и может быть представлено в виде строки b2e4936936c910319fb3d210bfa55b18765db9cc, которая по длине совпадает с SHA1-хешами.
Поле string содержит строку из одного символа: 1.
CA_cert — сертификат центра сертификации в формате DER. Он используется для установки соединения с управляющим сервером по протоколу TLS 1.2.
В функции DllMain предусмотрено создание нескольких рабочих потоков в зависимости от ряда условий.
Основной поток — thread_1_main
Поток запроса нового сервера — thread_2_get_new_C2_start_communication
Поток исполнения зашифрованного модуля — thread_4_execute_encrypted_module
Основной поток
Вначале бэкдор проверяет версию ОС, затем подготавливает структуру для инициализации функций и структуру для хранения некоторых полей конфигурации. Процедура выглядит искусственно осложненной.
В структуру funcs_struc типа funcs_1 заносятся 3 указателя на функции, которые будут поочередно вызваны внутри функции init_global_funcs_and_allocated_cfg.
В функции set_global_funcs_by_callbacks происходит вызов каждой функции-инициализатора по очереди.
Общий порядок формирования структур выглядит следующим образом:
1) каждой функции передаются две структуры: первая содержит указатели на некоторые функции, вторая — пустая;
2) каждая функция переносит указатели на функции из одной структуры в другую;
3) после вызова функции-инициализатора происходит очередное перемещение указателей на функции из локальной структуры в глобальный массив структур по определенному индексу.
В итоге после всех нестандартных преобразований получается некоторое количество глобальных структур, которые объединены в один массив.
В конечном итоге вызов функций можно представить следующим образом.
Сложные преобразования — копирование локальных структур с функциями и их перенос в глобальные структуры — вероятно, призваны усложнить анализ вредоносного образца.
Затем бэкдор при помощи библиотеки UT hash формирует хеш-таблицу служебных структур, ответственных за хранение контекста сетевого соединения, параметров подключения и т. д.
Фрагмент кода формирования хеш-таблицы.
Стоит отметить, что здесь располагается значение сигнатуры, которое позволяет определить используемую библиотеку: g_p_struc_10->hh.tbl->signature = 0xA0111FE1;.
Для рассматриваемого бэкдора характерно распределение значимых полей и данных по нескольким создаваемым для этого структурам. Эта особенность при анализе затрудняет создание осмысленных имен для структур.
После подготовительных действий переходит к инициализации подключения к управляющему серверу.
Инициализация соединения с управляющим сервером
После ряда подготовительных действий бэкдор разрешает хранящийся в конфигурации адрес управляющего сервера и извлекает порт. Адреса в конфигурации хранятся в виде строк: koran.junlper[.]com:80 и koran.junlper[.]com:443. Далее программа создает TCP-сокет для подключения. После этого создает контекст для защищенного соединения и выполняет TLS-рукопожатие.
После установки защищенного соединения бэкдор ожидает от управляющего сервера пакет с командой. Программа оперирует двумя форматами пакетов:
пакет, полученный после обработки протокола TLS, — «транспортный» пакет»;
пакет, полученный после обработки транспортного пакета, — «пакет данных». Содержит идентификатор команды и дополнительные данные.
Заголовок транспортного пакета представлен следующей структурой.
struct transport_packet_header
{
DWORD signature;
WORD compressed_len;
WORD uncompressed_len;
};
Данные располагаются после заголовка и упакованы алгоритмом LZ4. Бэкдор проверяет значение поля signature, оно должно быть равно 0x573F0A68.
После распаковки полученный пакет данных имеет заголовок следующего формата.
struct data_packet_header
{
WORD tag;
WORD id;
WORD unk_0;
BYTE update_data;
BYTE id_part;
DWORD unk_1;
DWORD unk_2;
DWORD len;
};
Поля tag и id в совокупности определяют действие бэкдора, то есть обозначают идентификатор команды.
Данные структуры заголовков используются в обоих направлениях взаимодействия.
Порядок обработки команд сервера:
верификация клиента;
отправка информации о зараженной системе;
обработка команд по идентификаторам.
В структуре, отвечающей за взаимодействие с управляющим сервером, есть переменная, которая хранит состояние диалога. В связи с этим перед непосредственным выполнением команд необходимо выполнение первых двух шагов, что можно рассматривать как второе рукопожатие.
Этап верификации
Для выполнения этапа верификации значения полей tag и id в полученном от управляющего сервера первичном пакете должны быть равны 1.
Процесс верификации состоит в следующем:
1. Бэкдор формирует буфер из 8-байтного массива, который следует после заголовка пакета и поля hash_id, взятого из конфигурации. Результат можно представить в виде структуры:
struct buff
{
BYTE packet_data[8];
BYTE hash_id[20];
}
2. Вычисляется SHA1-хеш данных в полученном буфере, результат помещается в пакет (после заголовка) и отправляется на сервер.
Отправка информации о системе
Следующий полученный пакет от управляющего сервера должен иметь значения tag, равное 5, и id, равное 3. Данные о системе формируются в виде структуры sysinfo_packet_data.
struct session_info
{
DWORD id;
DWORD State;
DWORD ClientBuildNumber;
BYTE user_name[64];
BYTE client_IPv4[20];
BYTE WinStationName[32];
BYTE domain_name[64];
};
struct sysinfo_block_2
{
WORD field_0;
WORD field_2;
WORD field_4;
WORD system_def_lang_id;
WORD user_def_lang_id;
DWORD timezone_bias;
DWORD process_SessionID;
BYTE user_name[128];
BYTE domain_name[128];
DWORD number_of_sessions;
session_info sessions[number_of_sessions];
};
struct sysinfo_block_1
{
DWORD unk_0; //0
DWORD bot_id_created;
DWORD dw_const_0; //0x101
DWORD os_version;
WORD dw_const_2; //0x200
BYTE cpu_arch;
BYTE field_13;
DWORD main_interface_IP;
BYTE MAC_address[20];
BYTE bot_id[48];
WCHAR computer_name[128];
BYTE cfg_string[64];
WORD w_const; //2
WORD sessions_size;
};
struct sysinfo_packet_data
{
DWORD id;
sysinfo_block_1 block_1;
sysinfo_block_2 block_2;
};
Поле sysinfo_packet_data.id содержит константу — 0x19C0001.
Примечателен процесс определения бэкдором значений MAC-адреса и IP-адреса. Вначале программа ищет сетевой интерфейс, через который прошло наибольшее количество пакетов, затем получает его MAC-адрес и далее по нему ищет IP-адрес этого интерфейса.
Версия ОС кодируется значением от 1 до 13 (0, если возникла ошибка), начиная с 5.0 и далее по возрастанию версии.
В поле sysinfo_packet_data.block_1.cfg_string помещается значение string из конфигурации бэкдора, которое равно символу 1.
Обработка команд
После верификации и отправки системной информации BackDoor.Spyder.1 приступает к обработке главных команд. В отличие от большинства бэкдоров, команды которых имеют вполне конкретный характер (забрать файл, создать процесс и т. д.), в данном экземпляре они носят скорее служебный характер и отражают инструкции по хранению и структурированию получаемых данных. Фактически все эти служебные команды нацелены на загрузку новых модулей в PE-формате, их хранение и вызов тех или иных экспортируемых функций. Стоит отметить, что модули и информация о них хранятся в памяти в виде хеш-таблиц с помощью UT-hash.
tag | id | Описание |
6
| 1 | Отправить на сервер количество полученных модулей. |
2 | Сохранить в памяти параметры получаемого модуля. | |
3 | Сохранить в памяти тело модуля. | |
4 | Загрузить сохраненный ранее модуль. Поиск выполняется в хеш-таблице по идентификатору, полученному в пакете с командой. Модуль загружается в память, вызывается его точка входа, затем получаются адреса 4 экспортируемых функций, которые сохраняются в структуре для дальнейшего вызова. Вызвать экспортируемую функцию № 1. | |
5 | Вызвать экспортируемую функцию № 4 одного из загруженных модулей, затем выгрузить его. | |
6 | Отправить в ответ пакет, состоящий только из заголовка data_packet_header, в котором поле unk_2 равно 0xFFFFFFFF. | |
7 | Вызвать экспортируемую функцию № 2 одного из загруженных модулей. | |
8 | Вызвать экспортируемую функцию № 3 одного из загруженных модулей. | |
5 | 2 | Отправить на сервер информацию о текущих параметрах подключения. |
4 | - | Предположительно экспортируемая функция № 1 может возвращать таблицу указателей на функции, и по этой команде программа вызывает одну из данных функций. |
После обработки каждого полученного от сервера пакета бэкдор проверяет разницу между двумя значениями результата GetTickCount. Если значение превышает заданное контрольное значение, то отправляет на сервер значение сигнатуры 0x573F0A68 без каких-либо дополнительных данных и преобразований.
Заключение
Рассмотренный образец бэкдора для целевых атак BackDoor.Spyder.1 примечателен в первую очередь тем, что его код не исполняет прямых вредоносных функций. Его основные задачи — скрытое функционирование в зараженной системе и установление связи с управляющим сервером с последующим ожиданием команд операторов. При этом он имеет модульную структуру, что позволяет масштабировать его возможности, обеспечивая любую функциональность в зависимости от нужд атакующих. Наличие подключаемых плагинов роднит рассмотренный образец с ShadowPad и PlugX, что, вкупе с пересечениями сетевых инфраструктур, позволяет нам сделать вывод о его принадлежности к деятельности Winnti.