В марте этого года мы выпустили статью о новых инструментах Shedding Zmiy. В том инциденте были обнаружены не только руткиты Puma и Kitsune, но и еще несколько новых утилит, написанных на Rust, которые могут использовать злоумышленники для закрепления в системе и постэксплуатации. Техники, которые используют эти вредоносы и то, как они «взаимодействуют» между собой, показались нам интересными, поэтому мы решили описать их подробнее.
В этом материале мы рассмотрим, как утилиты помогают повысить привилегии, каким образом происходит их закрепление в системе и дальнейшая маскировка и, наконец, какие возможности получает злоумышленник, когда эти вредоносы попадают в систему.
Octopus
Octopus — это интерактивная утилита командной строки для Linux-систем, написанная на языке Rust. Она используется злоумышленниками для повышения привилегий в системе и дальнейшего закрепления. В упомянутом выше инциденте мы не видели Octopus, его семплом с нами поделились коллеги по индустрии. Вероятно, два других вредоноса появились в системе именно благодаря ему.
Повышение привилегий
У Octopus есть меню с большим количеством режимов:
Как видим, в нем реализовано два способа повышения привилегий:
- Эксплуатация уязвимостей в системе.
- Эксплуатация мисконфигураций с использованием GTFO.
Утилита имеет на борту несколько эксплойтов для известных уязвимостей:
- pwnkit — CVE-2021-4034,
- looney-tunables — CVE-2023-4911,
- baron — CVE-2021-3156,
- route4-filter — CVE-2022-2588.
Помимо эксплуатации этих уязвимостей Octopus также предоставляет функциональность для их выявления, поскольку хранит в себе данные об уязвимых операционных системах и версиях пакетов. Фрагмент таких данных показан ниже:
{'by_distro': {'Centos': {'by_codename': {'6': {'cve_conditions': {}},
'7': {'cve_conditions': {'CVE-2021-3156': {'Or': [{'PackageVersion': {'package':
'sudo',
'patched_version':'0:1.8.23-10.el7_9.1'}},
{'PackageVersion': {'package':
'sudo-devel',
'patched_version': '0:1.8.23-10.el7_9.1'}}]},
'CVE-2021-4034': {'Or': [{'PackageVersion': {'package':
'polkit',
'patched_version': '0:0.112-26.el7_9.1'}},
{'PackageVersion': {'package':
'polkit-devel',
'patched_version': '0:0.112-26.el7_9.1'}},
{'PackageVersion': {'package':
'polkit-docs',
'patched_version': '0:0.112-26.el7_9.1'}}]},
...
Таким образом, Octopus может автоматически выбирать, какую уязвимость лучше использовать для текущей системы жертвы.
Также утилита может анализировать систему на наличие исполняемых файлов, у которых установлен бит suid или атрибут security.capability = CAP_SUID. Если такие файлы найдены, Octopus проверяет, есть ли они в базе данных GTFO, которую он хранит внутри себя. Фрагмент этой базы данных:
{'cap_setuid': {'gdb': [{'script': "./gdb -nx -ex 'python import os; "
"os.setuid(0)' -ex '!sh' -ex quit"}],
'node': [{'script': "./node -e 'process.setuid(0); "
'require("child_process").spawn("/bin/sh", '
"{stdio: [0, 1, 2]})'"}],
'perl': [{'script': "./perl -e 'use POSIX qw(setuid); "
'POSIX::setuid(0); exec "/bin/sh";\''}],
'php': [{'script': 'CMD="/bin/sh"\n'
'./php -r "posix_setuid(0); '
'system(\'$CMD\');"\n'}],
'python': [{'script': "./python -c 'import os; os.setuid(0); "
'os.system("/bin/sh")\''}],
'ruby': [{'script': "./ruby -e 'Process::Sys.setuid(0); exec "
…
Если скрипт есть в базе GTFO, то Octopus предлагает использовать его для получения оболочки с повышенными привилегиями.
После того как злоумышленнику удалось повысить свои привилегии, у него появляется возможность закрепиться в системе жертвы, используя Octopus и встроенные в него утилиты.
Закрепление в системе
Для закрепления в системе внутри Octopus хранится несколько исполняемых файлов — это библиотеки Leech и исполняемый файл Mycelium, они закодированы CBOR и сжаты с помощью zstd. Leech и Mycelium будут описаны далее, но для начала рассмотрим процесс патчинга системных сервисов, в результате которого в системе закрепляются «нежелательные гости».
Для патчинга в меню Octopus есть три пункта: glue, netpatch и netpatch-update. Последние два связаны и являются автоматизацией первого, glue дает возможность злоумышленнику полностью сконфигурировать Leech-библиотеку и запустить процесс патчинга. Для этого есть три типа целей:
- bin,
- bin-network,
- bin-atexit.
От вида цели зависит, какой тип Leech будет использоваться и в какой последовательности будет происходить патчинг. Цели bin- и bin-network будут пропатчены в первую очередь, также для них можно задать параметр interval, через который будет происходить проверка и при необходимости повторный патчинг уже на стороне самой Leech-библиотеки, если не задан параметр once. Отличие этих типов заключается лишь в том, что в bin будет встроена basic-lib, а в bin-network — network-lib. Эти библиотеки будут описаны позже.
Bin-atexit патчатся в последнюю очередь, и их проверка и повторный патчинг будут проводиться в конце работы Leech-библиотек. В bin-atexit встраивается basic-lib. В качестве целей help Octopus рекомендует выбирать пакетные менеджеры. Возможно, такой совет обусловлен тем, что Leech в пакетном менеджере сможет выжить даже после обновления всех остальных зараженных пакетов.
Также параметром glue можно задать имя встроенного бинаря или передать путь к какому-то стороннему исполняемому файлу, который будет встроен в Leech для дальнейшего использования. В анализируемом семпле встроенной нагрузкой был Mycelium.
Теперь рассмотрим непосредственно сам процесс патчинга. Для начала Octopus подготавливает Leech-библиотеки — встраивает конфиг и добавляет выбранную полезную нагрузку, если это необходимо. Утилита «приклеивает» конфиг и полезную нагрузку к библиотеке (закодированные и упакованные), создавая при этом дополнительную секцию с типом 0x7 — NOTE секция.
Далее подготовленные Leech-библиотеки помещаются в папку, где хранится системная glibc. Стандартными именами для них являются:
- libzst.so.0 для basic-lib,
- libsockopt.so.1 для network-lib,
- libsockopt.so.2 для network-lib mosquito.
Также у файлов устанавливаются новые временные метки (с помощью системного вызова utimensat) и атрибуты selinux, такие же как у файла glibc. Это делается для того, чтобы файлы библиотек меньше выделялись среди других файлов в директории. После всего этого библиотеки готовы для встраивания.
Для того чтобы заставить процесс-жертву подгружать Leech-библиотеку при запуске, Octopus редактирует исполняемый файл, меняя секции, которые использует динамический линковщик во время загрузки ELF в память.
Octopus создает дополнительный LOAD-сегмент в конце файла и копирует туда .dynamic- и .dynstr-секции, в которых описаны динамические зависимости ELF файла.
При копировании в конец .dynstr-секции добавляется имя вредоносной библиотеки, а в начало новой .dynamic-секции добавляется запись с тегом DT_NEEDED и смещением до имени вредоносной библиотеки. Таким образом, у исполняемого файла появляется еще одна зависимость в виде Leech-библиотеки, которая загрузится раньше всех остальных библиотек, что позволит произвести перехват функций. Такого же эффекта злоумышленник мог бы достичь, если бы запускал процесс с установленной переменной окружения LD_PRELOAD. Но техника с переменной окружения является более заметной и хорошо известна DFIR-специалистам.
После того как процесс патчинга завершился, Octopus пытается скрыть факт изменения этих файлов, для чего он исправляет контрольные суммы пакетов, которые лежат в директории /var/lib/dpkg/info, если в системе используется пакетный менеджер dpkg — стандартный пакетный менеджер в Debian-системах, например, таких как Ubuntu. Другие пакетные менеджеры Octopus версии 16.7.0, который мы анализировали, не поддерживает.
Теперь, когда контрольные суммы исправлены и проверка по ним целостности пакетов не выявит изменений Octopus должен избавиться еще от одной улики — аномалии во временных метках файлов. Поскольку изменяются только .md5sums-файлы, их временные метки mtime (время последнего изменения содержимого файла) будут выделяться на фоне остальных. Значения mtime-метки пользователь может увидеть, например, с помощью команды ls -l. Octopus меняет эту временную метку для того, чтобы измененные .md5sums-файлы не выделялись на фоне остальных.
Тем не менее аномалию все еще можно обнаружить, если посмотреть на другую временную метку — ctime (время последнего изменения метаданных файла). Изменить эту метку так же, как mtime, не получится. Посмотреть значение ctime можно с помощью команды ls -lc или stat. Для наглядности аномалия во временных метках файла .md5sums представлена ниже:
Пункт меню netpatch делает то же самое, что и glue, но при этом самостоятельно выбирает цели. В этом режиме Octopus сам ищет сервисы с именами: crond, sshd, postgres, nginx, apache2, lighttpd, mariadb и пытается их пропатчить. А если запустить Octopus в режиме netpatch-update, то у злоумышленника появляется возможность обновить конфигурацию Leech-библиотек и Mycelium или полностью заменить их на новые.
Таким образом, Octopus помогает злоумышленникам сначала повысить свои привилегии в системе, а затем закрепиться в ней с помощью патчинга различных системных сервисов, что делает его очень опасным инструментом в арсенале злоумышленников.
Leech
Leech — вредонос для Linux-систем, который может использоваться атакующими для поддержания присутствия в системе и для удаленного управления. Он становится следующим после Octopus звеном в цепочке получения контроля над системой.
Техническое описание
Leech распространяется в виде so-библиотеки для того, чтобы подгружаться в процессе работы зараженных сервисов. Как мы уже говорили, существует два типа Leech-библиотек:
- basic-lib,
- network-lib.
Basic-lib в процессе работы проверяет зараженные сервисы и заражает их заново в случае, если они каким-то образом были исправлены, — например, с помощью обновления пакета. Сам процесс патчинга в Leech практически ничем не отличается от Octopus. Цели для патчинга вредонос берет из конфига, который был встроен в него предварительно. Вот пример конфигурации:
Config {
interval: Some(
43200s,
),
interval_spread: Some(
14400s,
),
background_paths: ["/usr/sbin/cron"],
atexit_paths: ["/usr/bin/dpkg"],
network_paths: [
"/usr/sbin/sshd",
"/usr/sbin/nginx"
],
env: [],
basic_lib: "libzst.so.0",
network_lib: "libsockopt.so.1",
package_manager: Some(
Dpkg,
),
packages: Some(
[ "cron", "nginx", "dpkg", "openssh-server"],
),
}
В ней указан интервал, с которым библиотека будет проводить проверку, и все зараженные цели.
Network-lib, помимо того что делает basic-lib, еще и перехватывает входящие подключения к зараженному сервису. Это происходит в момент вызова функций accept или accept4 процессом-жертвой. Leech перехватывает определенные подключения и дальше с ними взаимодействует. Это объясняет выбор таких целей, как, например, sshd или nginx, так как к ним зачастую можно получить доступ извне, что вызовет меньше подозрений, чем подключение к нестандартному порту без легитимного сервиса.
Для того чтобы начать взаимодействие с Leech-библиотекой, на порт, прослушиваемый зараженным сервисом, нужно отправить TLS 1.3 Client Hello. В таком случае библиотека обратит внимание на это подключение и начнет его обработку. В пакете Client Hello вредонос будет искать TLS-расширение GREASE-типа. Этот тип расширения описан в RFC 8701. Он создан для того, чтобы находить узлы в сети, которые не поддерживают актуальную версию TLS. У таких расширений есть зарезервированные значения типов: 0x0A0A, 0x1A1A, 0x2A2A и так до 0xFAFA. В теле такого расширения обычно содержатся случайные байты. Как таковой смысловой нагрузки они не несут и работу обычных приложений не нарушают. Это объясняет, почему злоумышленники выбрали именно этот тип — зашифрованная внутри тела расширения полезная нагрузка выглядит не так подозрительно.
TLS-расширение, используемое злоумышленниками, имеет следующую структуру:
|
Смещение |
Содержимое |
|---|---|
|
0..2 |
Тип расширения |
|
2..4 |
Длина тела расширения |
|
4..8 |
magic bytes (0x33325052) |
|
8..N-4 |
payload |
|
N-4..N |
CRC32 предыдущих байтов (от 4 до N-4) |
Magic bytes, payload и контрольная сумма зашифрованы с помощью xor. В качестве ключа используется 32-байтный Client Random.
Если тело расширения прошло все проверки, начинается обработка полезной нагрузки. Она сериализована с помощью CBOR и может иметь различную структуру. Структура определяется по полю, которую разработчик назвал Marker::service. Дальнейшая работа вредоноса будет зависеть от того, какой сервис выбран злоумышленником. Таких сервисов несколько, в этом разделе мы остановимся на одном, так как он непосредственно касается самой Leech-библиотеки.
Его имя — lrex. Возможно, это расшифровывается как Leech Remote Execute, так как в задачи этого сервиса входит исполнение команд, которые передал злоумышленник. В этом случае payload будет иметь следующую структуру:
{
0:"lrex", # Marker::service
1:task, # ExecuteTask:task
2:{0:sig, 1:cert} # ExecuteTask::auth - {0:Auth::signature, 1:Auth::certificates}
}
В payload-структуре содержится как сам task, так и данные для верификации злоумышленника. Проверяется переданный сертификат, а также подпись task’а. Если проверка не пройдена, выполнение прерывается. В случае успеха вредонос обрабатывает саму команду, которая также сериализована и закодирована с помощью CBOR:
{
0:issued, # Task::issued - unix время создания таска
1:expires_in, # Task::expires_in - количество секунд, в течение которых таск действителен
2:"/path/to/executable", # Task::command
3:[arg1, arg2] # Task::args
}
Таким образом, злоумышленник может уже исполнять какие-то команды с помощью самой Leech-библиотеки. Но этим контроль над системой не ограничивается, так как эта «матрешка» может нести в себе еще один вредонос — Mycelium. Он распаковывается и помещается в memfd c именем kernel, откуда и происходит запуск. Так картина зараженной системы выглядит после запуска всех трех вредоносов:
Стоит отметить, что у network-lib есть облегченная версия — Mosquito. В инциденте его использование не было замечено, но Octopus может вместо Leech патчить сервисы с помощью этой lite-версии. В комплектацию этого вредоноса входит только сетевая часть, функции патчинга отсутствуют. Для того чтобы уменьшить размер исполняемого файла, в нем используется минимальное количество Rust-крейтов.
Mycelium
Mycelium является третьим и завершающим инструментом в этом наборе. Мы уже видели упоминание этого инструмента, когда исследовали веб-панель злоумышленников. Он расширяет пространство возможностей для постэксплуатации, добавляя еще три сервиса к тому, что был описан выше.
Архитектурно этот вредонос является асинхронным приложением, написанным с использованием крейта Tokio. Этот крейт довольно часто встречается во вредоносах, написанных на Rust, которые каким-то образом взаимодействуют с сетью. Помимо этого, в его работе используется мультиплексирование для более эффективной обработки подключений.
У Mycelium есть несколько режимов работы: пассивный, когда он ожидает входящих подключений, и активный, когда он подключается к злоумышленнику самостоятельно. В активном режиме вредонос использует конфигурацию, которая хранится закодированной в base64 в переменной окружения TOKEN. Пример конфигурации:
MyceliumConfig {
client_connection: Some(
MyceliumClientConnection {
host: "192.168.56.101",
port: 1337,
ping_interval: 200s,
success_reconnect_interval: 43200s,
failure_reconnect_interval: 14400s,
},
),
}
В пассивном режиме Mycelium сначала демонизируется, используя double fork, после чего сканирует все процессы в системе и просматривает их имена, для того чтобы выбрать себе новое. Имя выбирается с целью мимикрировать под какой-нибудь служебный процесс. Например, в инциденте, который мы расследовали, Mycelium был запущен с именем [kintegrityd]. Оригинальный сервис служит для проверки целостности данных, а мимикрирующий под него процесс отличается от оригинального наличием квадратных скобок в имени.
Взаимодействие в системе
Поскольку в одной системе может быть несколько зараженных сервисов, может случиться так, что будет запущено несколько образцов Mycelium. В таком случае вредоносам придется «договориться» — кто будет «хозяйничать» в системе.
Для этого Mycelium использует AF_UNIX (локальный) сокет. Файл этого сокета может быть создан в нескольких местах на диске:
/run/dbus/auxiliary_bus_socket
/var/run/dbus/auxiliary_bus_socket
$XDG_RUNTIME_DIR/systemd/generic
/run/user/systemd/generic
В начале работы Mycelium отправляет в него свой pid и версию. Если какой-то другой образец пришлет сообщение с версией ниже либо равной, чем его собственная, более новый Mycelium завершит процесс с помощью kill. Таким образом, в системе остается единственный образец с самой актуальной версией.
На фрагментах ниже, где представлен вывод команды strace, можно увидеть обмен информацией о pid и версии вредоносов:
Но только этим взаимодействие через этот сокет не ограничивается. Mycelium не получает подключения извне напрямую. Это происходит через Leech, с помощью обращения к конкретным сервисам. В прошлом разделе мы описали один из них, а Mycelium, в свою очередь, реализует еще три сервиса. И если Leech-библиотека получает запрос от злоумышленника, в котором тип сервиса отличается от lrex, то файловый дескриптор этого подключения с помощью sendmsg() будет передаваться в Mycelium, где и происходит обработка этого запроса.
Вот как это выглядит в выводе утилиты strace: