Исследуем Spyder – еще один бэкдор группировки Winnti
В конце прошлого года в нашу лабораторию за помощью обратилась зарубежная телекоммуникационная компания, сотрудники которой обнаружили в корпоративной сети подозрительные файлы. В ходе поиска следов вредоносной активности аналитики выявили образец весьма интересного бэкдора. Его анализ показал, что мы имеем дело с очередным модульным 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\
Интересной находкой стала служба, свидетельствующая об использовании утилиты для удаленного исполнения кода 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, оно должно быть равно 0×573F0A68.
После распаковки полученный пакет данных имеет заголовок следующего формата.
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 содержит константу — 0×19C0001.
Примечателен процесс определения бэкдором значений 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. Если значение превышает заданное контрольное значение, то отправляет на сервер значение сигнатуры 0×573F0A68 без каких-либо дополнительных данных и преобразований.
Заключение
Рассмотренный образец бэкдора для целевых атак BackDoor.Spyder.1 примечателен в первую очередь тем, что его код не исполняет прямых вредоносных функций. Его основные задачи — скрытое функционирование в зараженной системе и установление связи с управляющим сервером с последующим ожиданием команд операторов. При этом он имеет модульную структуру, что позволяет масштабировать его возможности, обеспечивая любую функциональность в зависимости от нужд атакующих. Наличие подключаемых плагинов роднит рассмотренный образец с ShadowPad и PlugX, что, вкупе с пересечениями сетевых инфраструктур, позволяет нам сделать вывод о его принадлежности к деятельности Winnti.