Содержание статьи
В статьях «Разбираем V8» и «Куча приключений» мы проложили себе путь к пользователю r4j на хардкорной виртуалке RopeTwo. Чтобы добраться до рута, остается последний шаг, но какой! Нас ждет ROP (не зря же виртуалку так назвали) и kernel exploitation. Мозги будут закипать, обещаю! Запасайся попкорном дебаггером и поехали!
Разведка
Как и в случае с флагом пользователя из предыдущей статьи, первым делом запускаем LinPEAS и внимательно смотрим, за что можно зацепиться. В глаза бросаются две подозрительные строчки:
[+] Looking for Signature verification failed in dmseg[ 13.882339] ralloc: module verification failed: signature and/or required key missing - tainting kernel--[+] Readable files belonging to root and readable by me but not world readable-rw-r----- 1 root r4j 5856 Jun 1 2020 /usr/lib/modules/5.0.0-38-generic/kernel/drivers/ralloc/ralloc.ko
Видим, что в системе от пользователя root загружен неподписанный модуль ядра, доступный нам для чтения. А это значит, что впереди kernel exploitation!
Статический анализ
Первое, что нам нужно, — это скачать себе ralloc.ko и натравить на него «Гидру».
Видим, что ralloc — это LKM, который выполняет различные операции с памятью при получении системных вызовов ioctl. По сути, это самописный драйвер управления памятью (Superfast memory allocator, как описывает его сам автор), очевидно, что не без уязвимостей.
LKM (loadable kernel module) — объектный файл, содержащий код, который расширяет возможности ядра операционной системы. В нем реализованы всего четыре функции:
- выделение памяти в адресном пространстве ядра (kmalloc) — вызов
ioctl
;0x1000 - очищение памяти в адресном пространстве ядра (kfree) — вызов
ioctl
;0x1001 - копирование информации из адресного пространства пользователя в пространство ядра (
memcpy(
) — вызовkernel_addr, user_addr, size) ioctl
;0x1002 - копирование информации из адресного пространства ядра в пространство пользователя (
memcpy(
) — вызовuser_addr, kernel_addr, size) ioctl
.0x1003
Ниже дизассемблированный и приведенный в читаемый вид листинг этих функций:
case 0x1000: // Функция выделения памяти ядра if ((size < 0x401) && (idx < 0x20)) { if (arr[idx].size== 0) { ptr = __kmalloc(size, 0x6000c0); arr[idx].data = ptr; if (ptr != 0) { arr[idx].size = size_alloc + 0x20; return_value = 0; } } } break;case 0x1001: // Функция освобождения памяти ядра if ((idx < 0x20) && arr[idx].data != 0)) { kfree(arr[idx].ptr); arr[idx].size = 0; return_value = 0; } break;case 0x1002: // Функция копирования из user space в kernel space if (idx < 0x20) { __dest = arr[idx].data; __src = ptrUserSpace; if ((arr[idx].data != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) { if ((ptrUserSpace & 0xffff000000000000) == 0) { memcpy(__dest, __src, size & 0xffffffff); result = 0; } } } break;case 0x1003: // Функция копирования из kernel space в user space if (idx < 0x20) { __dest = ptrUserSpace; __src = arr[idx].data; if ((__src != 0x0) && ((size & 0xffffffff) <= arr[idx].size)) { if ((ptrUserSpace & 0xffff000000000000) == 0) { memcpy(__dest, __src, size & 0xffffffff); result = 0; } } } break;
Посмотри внимательно на листинг. Возможно, ты найдешь уязвимость, она почти сразу бросается в глаза! А пока займемся подготовкой стенда.
Разворачиваем стенд
Очевидно, что для отладки ядра нам понадобится виртуальная машина. Да не одна, а целых две! Одна сыграет роль хоста, где установлено ядро с отладочными символами и где мы применим отладчик GDB, вторая будет запускаться в режиме KGDB (отладчик ядра Linux). Связь между виртуальными машинами устанавливается либо по последовательному порту, либо по локальной сети. Схематично это выглядит так.
Существует несколько сред виртуализации, на которых можно развернуть стенд: VirtualBox, QEMU (самый простой вариант) или VMware. Я выбрал первый вариант. Если захочешь попрактиковаться с QEMU, то на GitHub есть руководство.
Также я нашел неплохое видео, которое подробно показывает настройку VirtualBox для отладки ядра.
Остановимся на главных моментах. Первым делом посмотрим, какая версия ядра используется в RopeTwo:
r4j@rope2:
Release: 19.04
5.0.0-38-generic
Скачиваем и разворачиваем ВМ с Ubuntu 19.04. Далее устанавливаем ядро нужной версии (и свои любимые средства отладки и утилиты):
apt-get install linux-image-5.0.0-38-generic
Теперь можно сделать клон ВМ. На хост нам нужно загрузить ядро с символами отладки. Нам нужен файл linux-image-unsigned-5.
(838,2 Мибайт).
На таргете нужно включить режим отладки ядра (KGDB). Для этого сначала настроим загрузчик, изменим в файле /
следующие строчки:
GRUB_CMDLINE_LINUX="kgdboc=ttyS0,115200"GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=0 nokaslr"
Тем самым мы даем KGDB команду слушать подключения отладчика на порте ttyS0, а также отключаем KASLR (kernel address space layout randomization) и очистку консоли.
Выполняем команду update-grub
, чтобы записать параметры в загрузчик. После этого можно удостовериться, что значения попали в конфиг GRUB, — ищи их в файле /
.
Если бы мы хотели отлаживать само ядро, было бы необходимо добавить параметр kgdbwait
, чтобы загрузчик остановился перед загрузкой ядра и ждал подключения GDB с хоста. Но так как нас интересует не само ядро, а LKM, то это не требуется.
Далее проверим, что у нас в системе включены прерывания отладки:
root@target:/
CONFIG_MAGIC_SYSRQ=y
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x01b6
CONFIG_MAGIC_SYSRQ_SERIAL=y
и текущие флаги прерываний:
cat /
176
Включим на таргете все функции «магических» прерываний системы:
echo "1" > /proc/sys/kernel/sysrq
echo "kernel.sysrq = 1" >> /etc/sysctl.d/99-sysctl.conf
Подробнее о них можно почитать в документации.
Теперь, если ты введешь echo
, система зависнет в ожидании подключения отладчика.
Осталось связать хост и таргет между собой. Для этого необходимо включить в настройках обеих ВМ Serial Port. На таргете это выглядит так.
А на хосте — так.
Обрати внимание, что на хосте установлена галочка Connect to existing pipe/socket! Поэтому сначала мы загружаем ВМ таргета и только потом ВМ хоста.
Теперь все готово для отладки, проверяем.
KGDB также можно активировать «магической» комбинацией клавиш в VirtualBox: Alt-PrintScr-g.
Закидываем в таргет модуль ralloc.ko и загружаем его командой insmod
. Основные команды для работы с модулями ядра:
-
depmod
— вывод списка зависимостей и связанных map-файлов для модулей ядра; -
insmod
— загрузка модуля в ядро; -
lsmod
— вывод текущего статуса модулей ядра; -
modinfo
— вывод информации о модуле ядра; -
rmmod
— удаление модуля из ядра; -
uname
— вывод информации о системе.
После загрузки модуля можем посмотреть его карту адресов командой grep
. Запомни ее — эта команда еще не раз нам пригодится.
Для отладки нам понадобятся адреса областей .text, .data и .bss:
root@target:
0xffffffffc03fb000
0xffffffffc03fd000
0xffffffffc03fd4c0
Посмотрим, какие защитные механизмы включены в ядре.
r4j@rope2:
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2
syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid
pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic
cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx clflushopt
sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
Видим, что SMEP включен, а SMAP — нет. В этом можно убедиться следующим образом:
r4j@rope2:/
BOOT_IMAGE=/boot/vmlinuz-5.0.0-38-generic root=UUID=8e0d770e-1647-4f8e-9d30-765ce380f9b7 ro maybe-ubiquity nosmap
Supervisor mode execution protection (SMEP) и supervisor mode access prevention (SMAP) — функции безопасности, которые используются в последних поколениях CPU. SMEP предотвращает исполнение кода из режима ядра в адресном пространстве пользователя, SMAP — непреднамеренный доступ из режима ядра в адресное пространство пользователя. Эти опции контролируются включением определенных битов в регистре CR4. Подробнее об этом можно почитать в документации Intel (PDF).
Также включен KASLR — это умолчательный вариант в новых версиях ядра Linux.
Пишем эксплоит
Итак, какая же уязвимость присутствует в ralloc? Разгадка кроется в строке arr[
, это значит, что мы можем читать и писать на 32 байта больше реального объема выделенной памяти. Неплохо! Но как мы можем это использовать?
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»