В конце 2023 года команда Solar 4RAYS в рамках проведения Compromise Assessment обнаружила атаку на один из органов исполнительной власти. Мы выявили образцы многоступенчатого ВПО, которое на финальной стадии разворачивает на целевой системе имплант, названный нами DFKRAT. Разработанный в шпионских целях вредонос позволяет атакующим, среди прочего, похищать данные из файловой системы жертвы. Мы провели расширенное исследование и нашли другие образцы, отличающиеся технически, но используемые той же группой злоумышленников на протяжении 2021–2023 годов против русскоязычных целей. В настоящий момент мы не можем отнести кластер этой активности к какой-либо известной группировке, поэтому обозначили его как NGC2180. В статье мы подробно опишем найденное ВПО и приведем индикаторы компрометации.
Краткое содержание отчета
- Мы обнаружили шпионский имплант DFKRAT на системах одного из заказчиков в конце 2023 года.
- В открытых источниках были найдены и другие версии этого ВПО и прослежена их эволюционная цепочка с 2021 года, кластер этой активности мы назвали NGC2180.
- Первичный вектор заражения в случае ранних версий, по всей видимости — таргетированное фишинговое письмо, начиненное загрузчиком. В последних версиях вектор остался неизвестным.
- Вредоносная активность осуществляется полезной нагрузкой, которая доставляется загрузчиками в более ранних версиях и дропперами, эксплуатирующими DLL side-loading, — в последних.
- Ключевой функционал DFKRAT -— эксфильтрация файлов, поддержка интерактивного шелла, потенциальная загрузка дополнительного ВПО с сервера управления С2.
- В качестве С2 для актуальной версии имплантов использовались скомпрометированные серверы Национального центра научных исследований в Греции и индонезийской компании.
- Нам удалось найти и проанализировать фрагмент управляющего сервера.
В публичном пространстве не было найдено образцов последней версии вредоноса, обнаруженной нами на зараженных системах, однако мы нашли более ранние версии, отличающиеся менее продвинутой реализацией.
II версия — конец 2023 года (актуальная)
Во время расследования инцидента на системах жертвы были обнаружены два различных образца ВПО, представляющих собой промежуточную стадию дропперов, при этом финальная стадия с полезной нагрузкой в обоих случаях была одинаковой.
В то время как первичный вектор заражения в этой версии остался не определен, известно, что вредонос-дроппер в обоих случаях доставлялся на хост в виде трех файлов:
- легитимный исполняемый файл, импортирующий функцию из вредоносной библиотеки;
- вредоносная библиотека, внедряющая шелл-код в легитимный процесс;
- бинарный файл, содержащий шелл-код и конечную нагрузку в виде зашифрованного PE-файла.
C:\Program Files\CUAssistant\Download\
C:\Program Files\HP\Shared\bin\
Дроппер #1
В первом исследованном случае все файлы, принимающие участие в атаке, находились в директории C:\Program Files\CUAssistant\Download\:
- ExportController.exe — легитимный исполняемый файл, импортирующий функцию из вредоносной библиотеки;
- CoreFoundation.dll — вредоносная библиотека, внедряющая шелл-код;
- config — бинарный файл, содержащий шелл-код и конечную нагрузку в виде зашифрованного PE-файла.
Цепочка запуска вредоносного компонента начинается с исполняемого файла ExportController.exe (MD5: 379b72a6fadeb462ec884ca868025b6c). Он является компонентом QuickTime (разработанная Apple мультимедийная платформа) и подписан цифровой подписью, отозванной еще в 2017 году:
| Name | Apple Inc. |
| Issuer | VeriSign Class 3 Code Signing 2010 CA |
| Valid From | 12:00 AM 07/29/2015 |
| Valid To | 11:59 PM 08/27/2017 |
| Thumbprint | 173A28539CA6DAB5AC8C3B995ABAA692F95C5FC4 |
| Serial Number | 2B 20 EB 33 80 79 2A B0 11 F6 62 C0 64 FD B4 73 |
Для запуска вредоносного функционала злоумышленники эксплуатируют технику DLL Side-Loading — так ExportController.exe импортирует и вызывает пару функций CFDataCreate и CFRelease из библиотеки CoreFoundation.dll. Обе функции не несут в себе никакого функционала, а их импорт носит символический характер для вызова функции DllEntryPoint, внутри которой и находится вредоносный код
| MD5 | bb64fcb014d913b92975fccd5fa3e9b1 |
| SHA1 | 277624720ede4ed518ac7af599147c7f6bbdf7a4 |
| SHA256 | 144572ee5214f7289d1f71a44d90eb4ba2ac7d48f6e6b5b166f8ca7fe89afe9c |
| File name | CoreFoundation.dll |
| File type | PE32 executable (DLL) Intel 80386 |
| File size | 139 Kb |
| Comp. timestamp | 2023-08-05 06:53:19 UTC |
Вредоносный код стартует созданием изначально легитимного процесса со следующей командной строкой:
C:\Windows\System32\dllhost.exe /Processid:{C59AY5E4-1B4C-90A8-0907-A28FABCA1221}
После чего открывается файл config, который располагается в директории с исполняемым файлом и его содержимое инжектится в предварительно созданный процесс с помощью функций:
- ZwAllocateVirtualMemory
- ZwWriteVirtualMemory
- ZwQueueApcThread
- ZwResumeThread
Данный способ не создает дополнительного потока, а переводит созданный при инициализации процесса поток на необходимый адрес.
Интересной особенностью данного дроппера также является прямая работа с вызовами функций WinAPI, которые импортируются напрямую из ntdll.dll. Работая в 32-битном режиме, он сначала находит номер нужного API вызова, а затем через WinAPI вызывает эту функцию по ID следующей инструкцией:
call dword ptr fs:[0C0h]; wow64cpu!X86SwitchTo64BitMode
Для поиска номера нужной функции на первом этапе cоставляется таблица из адресов функций и их захешированных имен (используются только те, названия которых начинаются с Zw). Далее таблица сортируется по адресу функций, а последовательный номер в ней является номером искомой функции. Код алгоритма можно найти в Приложении I.
Внедренное таким образом содержимое файла config представляет собой шелл-код, который расшифровывает себя при помощи ключа 0x9708767C, используя следующий алгоритм:
key = 0x9708767C
_DWORD *encrypted_shellcode;
for (int i = 0; i < shellcode_length; i++) {
encrypted_shellcode[i] ^= key;
key += encrypted_shellcode[i];
}
Далее управление передается на расшифрованную часть, которая содержит в себе PE-файл полезной нагрузки (MD5: 8ce681f1c9ddf69a4ad4d53de57e404f) и код, который загружает его в память и передает управление на Entrypoint.
Дроппер #2
Во втором исследованном случае все файлы, участвовавшие в атаке, находились в директории C:\Program Files\HP\Shared\bin\:
- cdosys.dll.exe — легитимный исполняемый файл, импортирующий функцию из вредоносной библиотеки;
- d3d10_1.dll — вредоносная библиотека, внедряющая шелл-код;
- Dfk — бинарный файл, содержащий шелл-код и конечную нагрузку в виде зашифрованного PE-файла.
Работа вредоноса начинается с запуска cdosys.dll.exe (MD5: d7881fc5e3f93b39d3e84ccf988cc392) и дальнейшей эксплуатации метода DLL Side-Loading. Файл является компонентом ManyCam — софта для стриминга видео от компании Visicom Media, его оригинальное имя в составе легитимного ПО — hw_feature_tester.exe. Он подписан соответствующей валидной цифровой подписью:
| Name | Visicom Media Inc. |
| Issuer | DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1 |
| Valid From | 12:00 AM 01/19/2022 |
| Valid To | 11:59 PM 01/18/2025 |
| Thumbprint | 1CF5550BA6165341E28D773228634AE84CECBF23 |
| Serial Number | 0E 8A C3 0E E7 30 1E BF FE 46 B4 FE 3E D0 76 EB |
В процессе работы файл загружает вредоносную библиотеку d3d10_1.dll и вызывает одну из экспортных функций D3D10CreateDevice1. Сама библиотека предназначена для загрузки шелл-кода в память вновь созданного целевого процесса.
| MD5 | 1da3ce8c4267bf982e43472a08fa2ca1 |
| SHA1 | 88c5c02581b4b7fd3897d440f41d2bada9b6e704 |
| SHA256 | a7aa8a58a7f78b56623ed6475cb952ce82aec84e6ceebb2a8ffeea76edd3b486 |
| File name | d3d10_1.dll |
| File type | PE32 executable (DLL) Intel 80386 |
| File size | 284 Kb |
| Comp. timestamp | 2013-02-20 12:18:18 UTC |
По всей видимости, в этом случае NGC2180 воспользовались техникой Timestomping, а именно: подделали дату компиляции вредоносной библиотеки, чтобы ввести в заблуждение потенциальных исследователей и системы защиты.
Примечательно, как атакующие воспользовались уже существующими возможностями выбранного ими исполняемого файла. В зависимости от аргументов командной строки, с которыми осуществлялся запуск cdosys.dll.exe, менялся и поток исполнения:
- сdosys.dll.exe
Создает и запускает службу WinInit (display name WinInit Start Service).
binPath= C:\Program Files\HP\Shared\bin\cdosys.dll.exe check
Таким образом обеспечивается персистентность импланта на хосте жертвы; - cdosys.dll.exe check
Создает дочерний процесс cdosys.dll.exe check -s; - cdosys.dll.exe check -s
Запускает основной функционал дроппера.
При запуске основного функционала вызовом CreateProcessW создается легитимный процесс с валидной цифровой подписью C:\Windows\System32\winrshost.exe (Host Process for WinRM's Remote Shell plugin). Далее открывается файл Dfk для инжекта его содержимого в созданный процесс. Внедрение выполняется с помощью следующих вызовов:
- NtAllocateVirtualMemory;
- NtWriteVirtualMemory;
- GetThreadContext;
- SetThreadContext;
- ResumeThread.
| MD5 | dc9f192bb1f6db275feab8e8cfef28b9 |
| SHA1 | c899f4ae89f38ce5c8d25a2878cef43980930f2b |
| SHA256 | 144572ee5214f7289d1f71a44d90eb4ba2ac7d48f6e6b5b166f8ca7fe89afe9c |
| File name | Dfk |
| File type | Shellcode |
| File size | 13.13 Kb |
struct Dfk {
char shellcode[1136];
char egg[10] = { 0x02, 0x06, 0x12, 0x16, 0x22, 0x26, 0x32, 0xA8, 0xB6, 0x3B };
int rc4_key_size = 0x400; // 1024 bytes
char rc4_key[rc4_key_size]; // Ключ RC4 для расшифровки полезной нагрузки
int pe_file_size = 0x2c00;
char pe_file[pe_file_size]; // Полезная нагрузка
}
Перед записью нагрузки расшифровываются первые 1136 байт шелл-кода кастомным алгоритмом (код расшифровки можно найти в Приложении I). Далее с помощью функции NtWriteVirtualMemory происходит запись содержимого Dfk в созданный процесс и передача управления на расшифрованный шелл-код.
В процессе анализа мы заметили интересную особенность: вызов CreateProcessW без установленного флага 0x00000004 (CREATE_SUSPENDED). Таким образом, управление на шелл-код передается в running-потоке и исполняется в штатном режиме, что противоречит документации вызова GetThreadContext из MSDN:

Сам шелл-код расшифровывает уже PE-файл, следующий далее в структуре файла Dfk, с помощью алгоритма RC4 и зашитого ключа длиной 1024 байт
343638440F253B4F60541D332E5C44522B324A466033310D5338405250251E63522A2B42180C14474A16181B5610353B5B163345333B51534A2B400E1D5A4A4D4B26474441272C3E5E1C1F1A4E3722565951543B0C3B5A4541424516524B5237235B480E4A2A114D5360450E353B16551B45291B3A4A521B145B1512620B57441B3B33360C3D3D2F46294340142F1D3111240F47232A0D2322234A11160C214E192A12203060344D220C1D32571830251047315360362B1E3A1B2622333B12634248123A283B4E3A48452E5A3B525427190B2112390E0D3F522A22291A4D32281C2A45352B1C604D3B455E2D0E185D554E233A1C2C513E3D160C3020101E155D34
На последнем этапе осуществляется подготовка к исполнению расшифрованного файла:
- Резолв необходимых API-вызовов по хешу для дальнейшей загрузки PE в память (LoadLibraryExA, GetProcAddress, VirtualAlloc);
- Выделение RWX секции в памяти текущего процесса под загрузку PE;
- Маппинг секций PE-файла на выделенную память;
- Заполнение таблицы импорта;
- Передача управления на entrypoint PE-файла (MD5: 2e131ee69a4eee238a5353f34a81faed).
Финальная стадия — полезная нагрузка DFKRAT
Как мы отмечали ранее, первый и второй дроппер заметно отличаются друг от друга используемыми техниками, но в конечном счете оба разворачивают на системе один и тот же имплант, названный нами DFKRAT. Его образцы в обоих исследованных нами случаях имели минимальные различия, поэтому рассмотрим функционал DFKRAT на примере одного из них:
| MD5 | 2e131ee69a4eee238a5353f34a81faed |
| SHA1 | da491c0e1dfdf632c7e03b223d975f1b5c84c63f |
| SHA256 | 173ef7cc57507c21606926fbd2f2d8ea1c65fc911585ca857a2865d890be4eff |
| C2 | hxxps://mail.inn.demokritos[.]gr/yui/2.7.0/fonts/foot.jsp |
| File type | PE32 executable Intel 80386 |
| File size | 11 Kb |
| Comp. timestamp | 2023-10-12 06:20:23 UTC |
После запуска импланта в памяти целевого процесса устанавливается мьютекс с именем «loasd6asdg6». Если такой мьютекс уже существует, то вредонос завершает работу.
Далее вычисляется уникальный ID жертвы, использующийся при коммуникации с управляющим сервером (С2). Для этого в ключ реестра (HKLM | HKCU):SOFTWARE\Classes\CLSID:CLSID записывается значение, вычисляемое вызовом QueryPerformanceCounter. Данная ветка реестра является перенаправляемой, поэтому при запуске исследуемых сэмплов на системах с архитектурой процессора отличной от x86 актуальной веткой реестра будет: HKCU:SOFTWARE\Classes\WOW6432Node\CLSID:CLSID.
Конечный ID жертвы вычисляется по формуле:
victim_id = HKCU:SOFTWARE\Classes\WOW6432Node\CLSID:CLSID + 0x4C
Вредонос коммуницирует с С2 с помощью POST-запросов, которые формируются по следующему шаблону:
%s?name=%x&id=%08x
Где id – ранее вычисленный ID жертвы, а параметр name характеризует тип запроса к С2 и может принимать следующие значения:
| name | Название | Описание |
|---|---|---|
| 1000 | QueryControl | Первичное соединение |
| 1111 | WriteHost | Получить команду |
| 1112 | WriteSDat | Отправить результат команды |
Перед отправкой на С2 тело запроса шифруется алгоритмом RC4 на ключе Y%523^%$3fttT3f. Кроме того, используется специфичное значение User-Agent, которое также захардкожено в импланте - Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.04472.106 Safari/537.36.
Перед описанием протокола общения с С2, рассмотрим особенность реализации обертки над функцией Sleep. При расчете времени ожидания используются криптографически случайное значение длиной 4 байта – rnd, генерируемое WinAPI CryptGenRandom(), и два значения, передающиеся аргументами функции-обертки, min_lim и max_lim:
Sleep(min_lim + rnd % (max_lim - min_lim))
Итоговая продолжительность “сна” при такой реализации будет варьироваться в интервале (max_lim, min_lim):
Данная реализация функции Sleep используется для ухода от систематичности обращений (запросы через одинаковые промежутки времени) к С2, которая характерна для различных семейств ВПО. По-видимому, такая техника является попыткой замаскировать сетевую активность импланта от защитных решений.
Для инициализации первичного соединения с С2 имплант отправляет запрос со значением параметра name = 1000:
- Если сервер не вернул данные, то вредонос засыпает на 5-15 минут перед повторной попыткой инициализации.
- Если же данные были получены, то вредонос переходит к отправке следующего типа запроса.
Запрос с параметром name = 1111 информирует сервер о готовности получать команды:
- При успешном соединении с сервером, имплант парсит полученные данные.
В ином случае вызывается Sleep протяженностью 30-60 секунд, после чего запрос повторяется. - Если же после четырех таких попыток соединение с сервером не установлено, то имплант засыпает на 10-20 минут и возвращается к этапу инициализации соединения.
- Если сервер доступен и данные корректно распарсились, то имплант выполняет команду, отправляет результат на С2 и ожидает 1-3 секунды новых данных от сервера. Если сервер не отвечает, он возвращается к этапу инициализации соединения.
Запрос с параметром name = 1112 предназначен для отправки на С2 результата выполненной команды.
Протокол взаимодействия импланта и С2 имеет следующую структуру данных, которая справедлива как для соединений server → host, так и для host → server:
- cmd_number – уникальный идентификатор команды
- cmd_data_size – размер зашифрованных полезных данных
- encr_cmd_data – зашифрованные алгоритмом RC4 полезные данные (имя и содержимое прочитанного\записанного файла, ввод\вывод интерактивного шелла, время ожидания для команды Sleep).
Список команд, которые способен выполнять имплант:
| cmd_number | Описание |
|---|---|
| 0x2121 | Запустить интерактивный шелл (cmd.exe) |
| 0x2122 | Выгрузить файл с сервера в директорию %PUBLIC% |
| 0x2123 | Загрузить файл на север |
| 0x2124 | Ждать указанное время |
| 0x2125 | Завершить работу (ExitProcess) |
Отдельно отметим реализацию команды ожидания (0x2124). Ее выполнение происходит за счет функции Sleep, но вызывается она не один раз с установленным временем сна, а каждую секунду до тех пор, пока заданное количество секунд не истечет. Такая реализация имеет смысл, если процесс сна будет прерываться или состояние контролируется какой-либо переменной и необходимо не блокировать поток. Но в данном случае никаких параметров, кроме счетчика оставшегося времени, не проверяется.
Как видно из списка возможных команд, имплант предоставляет злоумышленнику расширяемые возможности по манипуляциям в атакуемой системе (от эксфильтрации пользовательских данных до потенциальной загрузки с С2 другого ВПО).
I версия – 2022
В ходе расследования мы обнаружили несколько исторических образцов. По всей видимости, они относятся к атакам, произошедшим в 2021-22 годах, которые мы атрибуцируем к тому же кластеру NGC2180. Речь идет о версии импланта DFKRAT, отличающейся от описанной выше, а также о другом механизме его доставки на целевую систему, а именно с помощью загрузки с удаленного сервера.
Загрузчик
В этом случае нам удалось восстановить практически весь процесс kill-chain, который начинается с документа-приманки, по-видимому, присланного жер