Ядерные LPE в Linux на практике

Применение ядерных эксплойтов в Linux - весьма опасный метод повышения прав доступа. Неудачная отработка эксплойта способна привести к нарушению целостности памяти ядра, за которым часто следует kernel panic и сопутствующая ему потеря данных, а в особо неудачных случаях - даже повреждение файловой системы.

Такие эксплойты имеет смысл использовать только когда все остальные, более безопасные для работоспособности системы способы не применимы. Это вполне вероятно, т.к. наличие классических мисконфигураций sudo/cron/suid вне CTF-like сценариев вряд ли встречается часто, особенно, если первичный доступ к системе осуществляется после RCE в Web-приложении или сетевом сервисе, для учеток которых админам обычно незачем добавлять дополнительные права.

Эпоха относительно простых в эксплуатации юзерспейсных уязвимостей CVE-2021-3156 Baron Samedit и CVE-2021-4034 Pwnkit, актуальных для очень длинной линейки версий sudo и pkexec, явно подходит к концу, а более новые уязвимости вроде CVE-2023-4911 Looney tunables сложнее в эксплуатации и затрагивают меньшую линейку версий. В то же время, появляется все больше kernelspace-эксплойтов, способных работать на больших линейках версий ядер, что позволяет достаточно эффективно их применять на системах, где указанные выше уязвимости уже не актуальны.

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

Эта тема уже не раз поднималась, поэтому основной целью данной статьи является именно поиск более удобных и универсальных подходов к такому тестированию.

Проблема выбора эксплойта

Основная проблема при применении эксплойтов на практике состоит в том, что значительная часть публично доступных PoC явно заточены под конкретную сборку ядра и без глубокой адаптации не могут быть применены на других ядрах.

Наличие в коде эксплойта адресов (часто указываемых в виде смещений относительно kernel base) определенных функций в ядре, адресов инструкций процессора, используемых в ROP-цепочках, смещений полей различных структур и других магических констант - все это указывает на то, что эксплойт скорее всего может быть применен только на конкретном билде, обычно указываемом где-то в комментариях или описании, даже если многие другие версии ядра имеют ту же уязвимость.

Адаптация подобных эксплойтов под другие билды вполне возможна, но это может потребовать много времени, хорошим примером подобной уязвимости и эксплойта под неё является история с CVE-2017-11176, закрытом в версии 4.11.9.

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

Сбор информации о системе

Для примера применения такого подхода возьмем образ минимальной инсталляции CentOS 8.0.1905 с ядром 4.18.0-80.11.2.el8_0.x86_64, скомпилированным 24 сентября 2019 года. Так как CVE-2019-15666 в этом дистрибутиве пофиксили только в январе 2020, выполним попытку её эксплуатации на нашем тестовом стенде.

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

В случае CentOS мы можем получить образ ядра, но initramfs не доступен для чтения:

-rw-------. 1 root root  25M Dec 19  2019 initramfs-4.18.0-80.11.2.el8_0.x86_64.img
-rwxr-xr-x. 1 root root 7.6M Sep 24  2019 vmlinuz-4.18.0-80.11.2.el8_0.x86_64

Для того чтобы его получить, можно воспользоваться командой dracut для генерации своего initramfs со всеми текущими модулями:

dracut --force --add-drivers "`cut -f1 -d ' ' /proc/modules`" /tmp/initrd.img `uname -r`

В других дистрибутивах initramfs обычно доступен на чтение, но может быть закрыт образ ядра, поэтому проще узнать версию пакета с целевым ядром и скачать его где-нибудь в архивах репозиториев.

# Debian/Ubuntu way
dpkg -S /boot/vmlinuz-6.2.0-37-generic
linux-image-6.2.0-37-generic: /boot/vmlinuz-6.2.0-37-generic
# RedHat way
rpm -qf /boot/vmlinuz-4.18.0-80.11.2.el8_0.x86_64 
kernel-core-4.18.0-80.11.2.el8_0.x86_64

Примеры источников старых пакетов для различных дистрибутивов:

Подготовка универсальной среды для тестирования эксплойтов

После получения ядра и initramfs нам остается только подготовить корневую файловую систему, где и будут выполняться все тесты. В идеале, подобная среда должна точно соответствовать дистрибутиву, но для экспресс-тестов может подойти что-то куда более простое.

Возьмем минимальный образ корневой ФС для x64 из проекта micro-rootfs и немного модифицируем его под наши нужды:

# Скачиваем файл
wget https://people.linaro.org/~loic.poulain/micro-rootfs/micro-rootfs-dev-x86.ext2.gz
# Распаковываем
gunzip micro-rootfs-dev-x86.ext2.gz
mv micro-rootfs-dev-x86.ext2 rootfs.ext2
# Расширяем образ до 2 Гб
dd if=/dev/zero of=rootfs.ext2 count=0 bs=1M seek=2048
e2fsck -f rootfs.ext2
resize2fs rootfs.ext2
# Монтируем образ
mkdir polygon
sudo mount rootfs.ext2 polygon
# Добавляем юзера без прав
cd polygon
sudo mkdir -p home/user
sudo chown 2000:2000 home/user
echo user:x:2000:2000:user:/home/user:/bin/sh | sudo tee -a etc/passwd
# Добавляем файл sudoers для проверки работы эксплойта
cp /etc/sudoers etc/sudoers

Соберем отреверсенную версию эксплойта и добавим её в образ:

gcc -static lucky0_RE.c -lpthread -o lucky0
cp lucky0 polygon/home/user
sudo umount polygon

Запуск и тестирование эксплойта

Для запуска VM используем эмулятор Qemu, т.к. в отличие от других средств виртуализации он позволяет очень удобно загружаться в наш образ просто подавая в качестве аргументов файлы с ядром и initrd, а ещё в нём очень легко указывать опции ядра и применяемые на таргете аппаратные защиты SMEP и SMAP.

Скрипт для запуска VM:

#!/bin/bash

qemu-system-x86_64 \
        -no-reboot \
        -m 1024M \
        -cpu qemu64,+smep \
        -nographic \
        -kernel vmlinuz.img \
        -append 'console=ttyS0 loglevel=3 debug root=/dev/sda edd=off selinux=0 init=/bin/sh rw' \
        -monitor /dev/null \
        -initrd initrd.img \
        -hda rootfs.ext2 \
        -snapshot

После открытия консоли VM запустим эксплойт и после нескольких неудачных попыток увидим что он успешно дописал нашего юзера в sudoers, что доказывает уязвимость этой версии ядра в данной конфигурации системы.

Автоматизация тестирования с помощью out-of-tree

При необходимости протестировать эксплойт на большой линейке ядер или для развертывания среды для отладки этого ядра будет гораздо удобнее автоматизировать этот процесс с помощью инструмента out-of-tree, поддерживающего дистрибутивы CentOS, Debian, Oracle Linux и Ubuntu.

Подготовим стенд:

out-of-tree kernel install --distro=Ubuntu --ver=16.04 --kernel=4.4.0-21-generic

Проведем анализ эксплуатабельности для CVE-2017-16995 из примера в репозитории:

$ out-of-tree pew --kernel='Ubuntu:4.4.0-21-generic'
10:35PM INF log tag=1726342510
10:35PM INF start distro_release=16.04 distro_type=Ubuntu kernel=4.4.0-21-generic
[*]          Ubuntu-16.04 {4.4.0-21-generic}:  BUILD SUCCESS   LPE SUCCESS 
10:35PM INF Success rate: 1.00, Threshold: 1.00

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

out-of-tree debug --kernel=Ubuntu:4.4.0-21-generic

Подключимся к стенду по ssh для подготовки окружения:

ssh -vp 50022 root@127.0.0.1

Запомним адрес commit_creds для быстрой проверки символов:

grep commit_creds /proc/kallsyms

После подготовки окружения можем начать отладку с помощью gdb:

gdb> target remote 127.0.0.1:1234

Для удобства отладки получим отладочные символы для ядра с launchpad:

wget http://launchpadlibrarian.net/254434620/linux-image-4.4.0-21-generic-dbgsym_4.4.0-21.37_amd64.ddeb
sudo dpkg -i linux-image-4.4.0-21-generic-dbgsym_4.4.0-21.37_amd64.ddeb

Подгрузим их в GDB и проверим, что адрес сходится:

gdb> symbol-file /usr/lib/debug/boot/vmlinux-4.4.0-21-generic
gdb> p commit_creds

Заключение

Ядерные эксплойты имеют очень разную природу и успех их применения часто зависит от внешних обстоятельств: типа уязвимости и метода её эксплуатации, настроек дистрибутива, уровня нагрузки на систему и множества иных факторов. Учесть и воспроизвести на стенде их все очень сложно, поэтому даже применение рассмотренных в статье методов не гарантирует что эксплуатация уязвимости на проде пройдет удачно.

С другой стороны, само наличие стенда и возможности вести отладку переводит эксплуатацию подобных уязвимостей из категории непонятной черной магии в что-то, что можно изучать, менять и в конце концов разбираться, как и почему это работает в том или ином случае.

Happy hacking!

Last updated