В течение 2024 года несколько российских организаций обращались к команде по реагированию на киберинциденты экспертного центра Positive Technologies (PT ESC IR) для расследования инцидентов, между которыми удалось обнаружить сходство. В рамках анализа вредоносная активность инцидентов была объединена в один кластер и связана с группой Goffee, атакующей российские организации с помощью фишинга с 2022 года.

В этой статье мы расскажем про вредоносное ПО, которое используют злоумышленники в атаках, в том числе речь пойдет о ранее неизвестном рутките под Linux, который был замечен в одном из инцидентов.


Исходя из всех обнаруженных активностей группировки Goffee удалось выявить ряд ключевых моментов:

  • Характерное использование UDP-bind-соединений и туннелирование трафика

  • Использование опенсорсных инструментов:

    • Impacket;

    • Chisel;

    • TinyShell;

    • Sliver, попадающего в систему с Nim-дроппером.

  • Использование новых уникальных инструментов, которые не были атрибутированы ни к одной группировке ранее:

    • DQiuc — bind-shell-сервер, работающий на протоколе QUIC;

    • BindSycler — bind-SSH-туннель на Go, завернутый в упаковщик Ebowla;

    • MiRat — небольшой шеллкод, агент Mythic;

    • Sauropsida — модифицированный kernelmode/usermode-руткит Reptilia.

  • Большинство обнаруженных вредоносных файлов были упакованы разными инструментами и алгоритмами, в зависимости от операционной системы:

    • Для Windows характерно использование уже известного ранее опенсорсного упаковщика Ebowla. В качестве ключа для расшифровки полезной нагрузки злоумышленник загружает в зараженную систему файл в папку C:\Users\Public\Image. Его название служит отправной точкой для генерации ключа для расшифровки.

    • Для Linux-файлов характерно использование упаковщика с одним и тем же алгоритмом RolMod13, описание которого будет ниже. Кроме упаковки полезной нагрузки этот алгоритм используется для расшифровки строк и для шифрования сообщений для C2.

Загрузчик

В процессе изучение вредоносных файлов был замечен наиболее часто используемый алгоритм расшифровки, благодаря которому эти файлы были атрибутированы к одной кампании. Алгоритм похож на сильно модифицированный rol13. В честь этого он назван RolMod13, так как его отличает добавлением XOR-ключа и сдвига в зависимости от текущего индекса массива. Рассмотрим сам алгоритм:

def decrypt_module(size, ea, xor_dword):
	bb = get_bytes(ea, size)
	out = b''
	for i in range(0, size, 4):
    	dword = struct.unpack("<I", bb[i:i+4])[0]
    	dword ^= rol((size - i) ^ xor_dword, (size - i) % 0xd, 32)
    	out += struct.pack("<I", dword)
	return out

Алгоритм распаковки и загрузки кода в память состоит из следующих действий:

  1. Сначала расшифровывают строки алгоритмом выше. Для удобства тут уже указан результат расшифровки.

    Результат расшифровки строк
    Результат расшифровки строк

    В качестве параметров в функцию fnStrDecrypt передается указатель на зашифрованный массив байтов, его длина и DWORD — XOR-ключ.

  2. Далее проверяется команда запуска текущего процесса. Если она не соответствует указанной в конфигурации строке, то вредонос перезапускается с заданным окружением и командной строкой из конфигурации при помощи execve, а текущий процесс на этом завершается. Изначальный интерпретатор команды при этом не меняют. В данном примере процесс запускается с параметрами /usr/sbin/rsyslogd -n -iNONE и окружением с PATH=/sbin:/bin:/usr/sbin:/usr/bin.

    Linux Loader
    Linux Loader
  3. После проверки полезная нагрузка расшифровывается алгоритмом RolMod13 и загружается в память с сохранением исходных аргументов и переменных окружения. Расшифрованная полезная нагрузка является ELF-файлом со статически связанными библиотеками.

Sauropsida

Sauropsida — это руткит, созданный на основе опенсорсного проекта под Linux Reptile. Сам руткит состоит из usermode-части и ядерного модуля. Инструмент позволяет злоумышленнику осуществлять удаленное управление системой, а также скрывать следы своего присутствия. Программно вредонос сильно не изменился, если провести сравнение с оригинальной версией. Исходя из строк внутри ВПО и специфического заголовка SAU для лог-сообщений, можно предположить, что образец назван злоумышленниками Sauropsida (что с латыни переводится как «рептилия»).

Общая схема работы Sauropsida
Общая схема работы Sauropsida

Семпл загружен в систему, накрытый модифицированным UPX. В данном случае классические байты 55 50 58 21 были заменены на A1 D8 D0 D5.

Usermode part

Usermode-часть представляет из себя многофункциональный инструмент: в зависимости от аргументов командной строки (или их отсутствия) он может выступать в роли загрузчика руткита, reverse shell или коннектора для управления руткитом. При первоначальной загрузке без аргументов и при запуске из-под root начинает расшифровывать kernel module алгоритмом RolMod13 и запускает модуль при помощи syscall init_module. В качестве параметров передает следующую строку:

exe_path=%s ld_sym=0x%

Здесь первый параметр — это путь до исполняемого файла Sauropsida, второй параметр — адрес функции kallsyms_lookup_name.

В последней версии usermode-модуля используется следующий список ключей для различных параметров запуска: RBHSUdN:P:I:t:s:p:r:F:M:O:. Расшифруем значение данных команд:

R : получить root

B : bind connection — установить флаг для создания bind-соединения

F : имя файла, который нужно скрыть из системы

H : скрыть ядерную часть руткита из списка модулей

I : IP-адрес, соединение с которым надо скрыть

M : закрепить модуля ядра в системе

N : имя процесса для скрытия

O : имя демона для скрытия

P : PID процесса для скрытия

S : остановить скрытие самого себя

U : unload, выгрузка ядерного модуля

d : debug mode

p : port для reverse или bind shell

r : connection delay

t : target ip для reverse shell

s : secret, сессионный ключ для reverse shell

Данные команды выполняются в модуле ядра. При каждом запуске с новыми параметрами usermode-модуль посылает ioctl характерного формата модулю ядра для общения с ним.

Sauropsida, процесс передачи команд в модуль ядра
Sauropsida, процесс передачи команд в модуль ядра

Как видно на рисунке выше, константы 0xA1306656 и 0xFEB18432 выступают словами-маркерами, по которым руткит ориентируется, что команда послана именно ему. Все, что заключено между этими константами, считается командой и ее параметрами. Описание команд будет ниже, в разделе «Модуль ядра».

Стоит отметить, что в случае указания параметра target ip происходит немедленная настройка reverse-shell-соединения (если указан только параметр port и установлен флаг -B, то bind shell). В данном случае обязательно указывается параметр secret: он используется в качестве сессионного ключа в соединении с C2.

Reverse и bind shell базируются на TinyShell. В нем также используются HMAC SHA-1 и AES для защиты сессии. В файл /tmp/righthere.txt записывается журнал сессии. Соединение происходит по протоколу UDP, для его реализации используются API-вызовы recvfrom и sendto.

Reverse shell имеет стандартные команды:

1 : отправить файл

2 : загрузить файл

3 : принять команду и выполнить ее при помощи bash

4 : обновить delay

5 : heartbeat

;7(Zu9YTsA7qQ#vw : конец соединения

Модуль ядра

При загрузке ядерного модуля в первую очередь управление передается функции sauropsida_init. Если загрузка произошла без параметров или названия файла sauropsida_12345 (вероятно, артефакт тестовых запусков), то происходит автозагрузка модуля.

sauropsida init
sauropsida init

Автозагрузка нужна, если запуск произошел после перезагрузки компьютера или был некорректен. При этом происходит извлечение сохраненных параметров и команд из /etc/timeinfo, которые необходимо выполнить при автозагрузке. Речь о структуре этого файла пойдет ниже.

При нормальном запуске инициализируют все необходимые хуки функций при помощи опенсорсного движка khook. Список всех перехватов, которые есть в Sauropsida, но отсутствуют в Reptile:

  • sys_recvmsg и __x64_sys_recvmsg — скрытие пакетов от netlink;

  • __x64_sys_sendmsg и sys_sendmsg — скрытие пакетов от netlink;

  • __x64_sys_getdents64, sys_getdents и __x64_sys_getdents — скрытие файлов при листинге директории (см. описание аналогичной методики);

  • perf_event_fork, proc_exec_connector — скрытие процесса по его имени.

При этом информация о возможности перехвата таких функций, как proc_exec_connector, perf_event_fork, не распространена, это уникальная техника, используемая в данном вредоносе. В то же время у Sauropsida практически отсутствует функциональность для сокрытия содержания файлов, которая присутствует в Reptile.

Говоря про базовые перехваты, заимствованные из Reptile, стоит упомянуть про перехват функции inet_ioctl. Он используется для получения всех поступающих от пользовательского модуля команд. В ioctl проверяются все входящие сообщения и ищутся слова-маркеры. Если они найдены, значит, между двумя маркерами находится команда. Таким образом в рутките вычисляют номер передаваемой команды из usermode-части Sauropsida.

Sauropsida, пример обработки команд
Sauropsida, пример обработки команд

Вот список обрабатываемых команд:

0: скрыть самого себя

1: скрыть процесс по PID

2: скрыть процесс по имени

3: скрыть демон

4: ничего не делает

5: получить root

6: скрыть сетевое соединение

7: отменить скрытие сетевого соединения

8: скрыть bind-соединение по указанному порту

9: скрыть файл (по inode)

10: вернуть состояния — скрыт или не скрыт собственный модуль

11: сохранить все объекты, которые передавались для скрытия в ядерный модуль, в базу данных (об этом ниже)

Но где же C2 во всей этой истории? После создания хуков руткит запускает Port Knocking. Для всех UDP-пакетов проверяется начало: если пакет начинается с hax0r_or_not_ или mag1c, то получен магический пакет. Данный пакет ожидается на зараженной машине и содержит зашифрованную конфигурацию. В первом случае получают C2 для reverse shell, во втором — bind shell. Пакет также расшифровывается алгоритмом RolMod13.

Sauropsida, алгоритм RolMod13
Sauropsida, алгоритм RolMod13

После расшифровки переданного магического пакета десериализуют его данные. Структура пакета следующая:

(hax0r_or_not_|mag1c)?<encoded(<ip_str>\s<port_str>)>

После идет перезапуск usermode-модуля c параметрами для включения reverse shell или bind shell.

Sauropsida, запуск reverse или bind shell из kernel
Sauropsida, запуск reverse или bind shell из kernel

Для промежуточного хранения информации используются отдельный swap-файл (по умолчанию это файл /etc/timeinfo). Иногда команда требует несколько промежуточных действий, и тогда в swap-файл записываются зашифрованные данные. Информация записывается фрагментами следующего вида:

0    1     3        3+size
|type| size| encr_data|

Данные шифруются XOR с байтом 0xE1. Классификация типов, используемых для записи данных:

7: service name

6: process name

5: file inode

4: nothing

3: ip address

2: ld_sym — второй параметр при загрузке модуля

1: exe_path — путь загрузки

0: команда для автозапуска

DQuic

DQuic представляет собой UDP bind shell, туннелирующий трафик при помощи протокола QUIC. Для реализации протокола используется открытая библиотека picoquic. В ходе сравнения открытого кода библиотеки с дизассемблированными кодом функций семпла было обнаружено, что используется достаточно старая версия данной библиотеки. Это дает нам предположить, что разработка инструмента началась еще в 2023 году.

При первом запуске после распаковки выполняется fork, и работа продолжается в дочернем процессе. Далее происходит инициализация конфига и последующая расшифровка сертификата и приватного ключа для установки соединения. Расшифровка происходит по уже известному алгоритму RolMod13.

DQuic, инициализация конфига
DQuic, инициализация конфига

Вначале инициализируется QUIC-контекст при помощи quic_create. В качестве параметров указывается сертификат, приватный ключ, alpn (в нашем случае udp), а также callback-функция для обработки команд. В качестве параметра этой callback-функции передается ранее заполненный конфиг. Функция quic_create принадлежит библиотеке picoquic, не будем останавливаться на ней и рассмотрим подробнее передаваемую callback-функцию. Данная функция обрабатывает стандартные события picoquic:

  • picoquic_callback_stream_data — получение данных и выполнение команды от пира при помощи функции event_handler;

  • picoquic_callback_stream_fin — получение FIN от пира и остаточное выполнение скопившихся команд при помощи функции event_handler;

  • picoquic_callback_prepare_to_send — подготовка данных для отправки;

  • picoquic_callback_ready — отправка данных на указанный в конфиге порт.

Если же вернуться в main, то мы увидим обработчик пакетов picoquic_packet_loop_win, который поддерживает в цикле и обрабатывает функцией loop_callback пакеты, направленные серверу по указанному bind_port. В нашем случае порт был нулевым, что означает подключение к любому свободному порту.

DQuic, main обработчик пакетов
DQuic, main обработчик пакетов

Внутри loop_callback происходит подключение к C2 в функции fninitClient, а также переподключение по истечении большого промежутка времени.

DQuic, инициализация клиента
DQuic, инициализация клиента

При подключении клиента-злоумышленника в качестве функции обработчика используется тот же qevent_handler, который и будет основным обработчиком туннеля DQuic.

DQuic, указание обработчика сообщений клиента
DQuic, указание обработчика сообщений клиента

Перейдем к его внутреннему устройству.

Диспетчер команд в DQuic
Диспетчер команд в DQuic

Диспетчер команд может находится в пяти состояниях (см. рис. выше). Первое состояние является интерактивным обработчиком, который управляет выполнением текстовых команд от клиента.

Режим shell имеет команду help, благодаря которой можно понять возможности DQuic:

This is an internal command shell. Supported commands are:
    help - print this message
    get [remote file] [local directory] - get file from the remote node
    put [local file] [remote directory] - put file into the remote node
    socks [action] [action params...] - SOCKS5 proxy server control, actions:
        add [direction] [{IP}:port] - add proxy server on specified port, IP is optional
            dir - direct proxy (from local to remote)
            rev - reverse proxy (from remote to local)
        remove [port] - remove proxy server on specified port
        show - show all proxy servers
    exit - exit internal shell

Команды get и put переводят диспетчер в состояния 2 и 3 соответственно.

При вызове команды socks add в состоянии shell диспетчер перейдет в состояние 4. Он создаст сокет для указанной прокси. Затем выполнение перейдет к состоянию 5, которое и будет отвечать за чтение-запись внутри созданной прокси.

BindSycler

BindSycler — это написанный на Golang shell, туннелирующий трафик при помощи протокола SSH. В зависимости от настраиваемых параметров может работать как в режиме TCP, так и UDP. Сам инструмент обфусцирован при помощи инструмента garble, в связи с чем часть имен функций отсутствует, а большинство библиотек и типов переименовано.

В начале работы BindSycler настраивает собственный конфиг:

struct config
{
  bool Debug_flag; // включение режима отладки
  bool KnockBack_flag; // установка соединения с сервером
  strstr DialAddress; // адрес для установления соединения
  strstr DialNetwork; // сеть для установления соединения (tcp/udp)
  bool BindServer_flag; // запуск bind-сервера
  strstr BindAddress; // адрес для привязки сервера
  strstr BindNetwork; // сеть для привязки сервера  (tcp/udp)
  strstr BindTlsServer_Flag; // запуск bind-сервера в режиме TLS
  strstr FixtureSsh_ServerKey; // ключ SSH
  slice FixtureAuthorized_KeyPub; // Ключ для аутентификации
  double SyclePeriod;
  __int64 SycleJitterFactor;
  bool TlsKnockBack_flag; // установка соединения с сервером в режиме TLS
  strstr TlsDialAddress; // адрес для установления соединения в режиме TLS
  strstr TlsDialNetwork; // сеть для установления соединения (tcp/udp) в режиме TLS
  __int64 TlsCyclePeriod;
  double TlsCycleJitterFactor;
  strstr BindTlsServerAddress; // адрес для привязки сервера в режиме TLS
  strstr BindTlsServerNetwork; // сеть для привязки сервера (tcp/udp) в режиме TLS
  __int64 Pointer; // Не используется, вероятно дополнительные ключи для TLS
};

Значения полей FixtureSsh_ServerKey и FixtureAuthorized_KeyPub передаются в секции данных самого инструмента.

В зависимости от параметров конфигурации сети происходит инициализация bind-сервера и dial-соединение с C2. Флаги KnockBack_flag, TlsKnockBack_flag показывают, устанавливать ли соединение с сервером, в то время как BindServer_flag и BindTlsServer_Flag отвечают за формат включенного сервера. При этом эти флаги не взаимоисключаемые: BindSycler может «поднять» несколько серверов и общаться с несколькими C2. За концы туннеля отвечают две функции — binder и sycler. Первая используется для установки bind-соединения для ожидания входящих соединений. Затем она перенаправляет все в обработчик для SSH.

BindSycler, запуск binder
BindSycler, запуск binder

Вторая с заданными временными параметрами SyclePeriod и SycleJitterFactor поддерживает постоянное соединение с сервером.

BindSycler, запуск sycler
BindSycler, запуск sycler

Для обработки задач от сервера используется функция donkey.

BindSycler, запуск dokney
BindSycler, запуск dokney

Она устанавливает dial-соединение с сервером. После этого новое соединение попадает в единую функцию-обработчик — SSH Connection. Основная функция ssh во многом списана с примера из опенсорсной библиотеки ssh golang. Используются те же названия классов и та же структура вложенности, а также похожая кодовая база, хоть и расширенная под функции APT-группировки.

BindSycler, сравнение функций handshake
BindSycler, сравнение функций handshake

Поддерживаются четыре типа ssh channel:

  • ssh: в этом случае происходит переподключение по новому адресу;

  • direct-tcpip: подключение для туннелирования трафика от клиента;

  • session: поддерживает три команды:

    • exec: выполнение заданной команды при помощи os.exec.Command();

    • shell: перенаправление управления в интерпретатор (дефолтный cmd.exe);

    • subsystem: включение передачи файлов по SFTP;

  • forward-tcpip: подключение для туннелирования трафика для bind-соединения.

Интересно, что при использовании в системе русской локали создаются потоки для ввода с другой кодировкой — чтобы при подключении не ломалась кодировка и названия файлов отображались верно. Это еще раз указывает на то, что злоумышленники ориентировались на российский сегмент в своих атаках.

BindSycler, преобразования кодировки для потоков ввода-вывода
BindSycler, преобразования кодировки для потоков ввода-вывода

MiRat

MiRat — это агент фреймворка Mythic. Он представляет собой небольшой шелл, загружаемый в систему при помощи Sideloading.

Первое, что бросается в глаза, это использование динамического импорта на стадии загрузки первого этапа шеллкода. В качестве функции хеширования использован алгоритм FNV-1a с предварительным переносом всех строк в верхний регистр.

MiRat, dynamic API resolve
MiRat, dynamic API resolve

Затем управление передается на API CreateThread, в качестве параметра передается функция размером чуть меньше 2 МБ. Переходим в дизассемблер и находим, что один из базовых блоков функции занимает аномально большое место. Присмотревшись внимательно, можно заметить, что все это место занято побайтовым копированием шеллкода на стек, из-за чего появилась такая огромная функция. На эту область памяти вызывают VirtualProtect, после чего выполнение переходит к полезной нагрузке.

MiRat, дроп шеллкода на стек
MiRat, дроп шеллкода на стек

При запуске бэкдор создает мьютекс 6536bc83-5a38-4678-bfab-b2a723a86788 для того, чтобы избежать запуска нескольких экземпляров ВПО. После этого переходит к основной функциональности. Стоит заметить, что весь бэкдор состоит из одной нераздельной функции, делающей его анализ менее приятным. Для начала работы бэкдор подгружает динамически импорт, используя все тот же алгоритм FNV-1a с измененным начальным значением.

Бэкдор записывает почти каждое свое действие в файл history.hcl, оставляя запись формата %d/%d/%d %d:%d:%d %s %s, где первое — это дата, затем время, строка-комментарий и аргумент для нее. Данные не хранятся в чистом виде: перед записью в журнал каждая строка шифруются с XOR-ключом 49dd9765-c4c5-47d2-9c00-75c26a7d28b4.

При этом журнал создается по полному пути, указанному в программе. Документ создается в папке C:\\Users\\<username>\\Documents\\WSM\\history.hcl, где username использовался характерный для компьютера, на котором бэкдор запустили. Если создать по данному пути файл журнала не выйдет, то MiRat завершит свое выполнение. Из этого можно сделать вывод, что злоумышленники создали образец под атаку на конкретную организацию.

Сам MiRat является достаточно простым агентом Mythic: сперва он устанавливает защищенное соединение с C2, для идентификации жертвы генерирует случайный 20-символьный ID. После этого бэкдор ожидает команд от сервера. Сам MiRat может выполнять следующие команды:

  • exit — завершить работу;

  • sleep — установить новые значения для параметров jitter и interval в конфигурации агента;

  • shell — запустить указанную в качестве параметра команду при помощи cmd /S /c;

  • shell_inject — запуск шеллкода, полученного от сервера, в отдельном thread в рамках текущего процесса.


Подробнее о сетевой инфраструктуре (сетевом профиле), цепочках атак и атаках группировки на российские организации можно почитать в нашем блоге на сайте команды экспертного центра безопасности Positive Technologies. Там же можно изучить индикаторы компрометации, зафиксированные техники и тактики злоумышленников, а также вердикты продуктов Positive Technologies

Климентий Галкин

Cпециалист группы киберразведки TI-департамента экспертного центра безопасности Positive Technologies

Варвара Колоскова

Специалист группы исследования сложных угроз TI-департамента экспертного центра безопасности Positive Technologies

Комментарии (0)