Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье будем разбирать одну из самых сложных тем в сфере PWN — эксплуатацию ядра Linux. Ты узнаешь, какие инструменты применяются для отладки ядра, что такое LKM, KGDB, IOCTL, TTY, и много других интересных вещей!
В статьях «Разбираем 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) — объектный файл, содержащий код, который расширяет возможности ядра операционной системы. В нем реализованы всего четыре функции:
ioctl 0x1000
;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). Связь между виртуальными машинами устанавливается либо по последовательному порту, либо по локальной сети. Схематично это выглядит так.
Схема отладки ядра Linux
Существует несколько сред виртуализации, на которых можно развернуть стенд: VirtualBox, QEMU (самый простой вариант) или VMware. Я выбрал первый вариант. Если захочешь попрактиковаться с QEMU, то на GitHub есть руководство.
Также я нашел неплохое видео, которое подробно показывает настройку VirtualBox для отладки ядра.
Остановимся на главных моментах. Первым делом посмотрим, какая версия ядра используется в RopeTwo:
r4j@rope2:~$ lsb_release -r && uname -r
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.0.0-38-generic-dbgsym_5.0.0-38.41_amd64.ddeb
(838,2 Мибайт).
На таргете нужно включить режим отладки ядра (KGDB). Для этого сначала настроим загрузчик, изменим в файле /etc/default/grub
следующие строчки:
GRUB_CMDLINE_LINUX="kgdboc=ttyS0,115200"GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=0 nokaslr"
Тем самым мы даем KGDB команду слушать подключения отладчика на порте ttyS0, а также отключаем KASLR (kernel address space layout randomization) и очистку консоли.
Выполняем команду update-grub
, чтобы записать параметры в загрузчик. После этого можно удостовериться, что значения попали в конфиг GRUB, — ищи их в файле /boot/grub/grub.cfg
.
Если бы мы хотели отлаживать само ядро, было бы необходимо добавить параметр kgdbwait
, чтобы загрузчик остановился перед загрузкой ядра и ждал подключения GDB с хоста. Но так как нас интересует не само ядро, а LKM, то это не требуется.
Далее проверим, что у нас в системе включены прерывания отладки:
root@target:/boot# grep -i CONFIG_MAGIC_SYSRQ config-5.0.0-38-generic
CONFIG_MAGIC_SYSRQ=y
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x01b6
CONFIG_MAGIC_SYSRQ_SERIAL=y
и текущие флаги прерываний:
cat /proc/sys/kernel/sysrq
176
Включим на таргете все функции «магических» прерываний системы:
echo "1" > /proc/sys/kernel/sysrq
echo "kernel.sysrq = 1" >> /etc/sysctl.d/99-sysctl.conf
Подробнее о них можно почитать в документации.
Теперь, если ты введешь echo g > /proc/sysrq-trigger
, система зависнет в ожидании подключения отладчика.
Осталось связать хост и таргет между собой. Для этого необходимо включить в настройках обеих ВМ Serial Port. На таргете это выглядит так.
Настройки Serial port на таргете
А на хосте — так.
Настройки Serial port на хост
Обрати внимание, что на хосте установлена галочка Connect to existing pipe/socket! Поэтому сначала мы загружаем ВМ таргета и только потом ВМ хоста.
Теперь вся готово для отладки, проверяем.
Проверка работы KGDB
KGDB также можно активировать «магической» комбинацией клавиш в VirtualBox: Alt-PrintScr-g.
Закидываем в таргет модуль ralloc.ko и загружаем его командой insmod ralloc.ko
. Основные команды для работы с модулями ядра:
depmod
— вывод списка зависимостей и связанных map-файлов для модулей ядра;insmod
— загрузка модуля в ядро;lsmod
— вывод текущего статуса модулей ядра;modinfo
— вывод информации о модуле ядра;rmmod
— удаление модуля из ядра;uname
— вывод информации о системе.После загрузки модуля можем посмотреть его карту адресов командой grep ralloc /proc/kallsyms
. Запомни ее — эта команда еще не раз нам пригодится.
Карта адресов модуля ralloc
Для отладки нам понадобятся адреса областей .text, .data и .bss:
root@target:~# cd /sys/module/ralloc/sections && cat .text .data .bss
0xffffffffc03fb000
0xffffffffc03fd000
0xffffffffc03fd4c0
Посмотрим, какие защитные механизмы включены в ядре.
r4j@rope2:~$ cat /proc/cpuinfo | grep flags
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:/tmp$ cat /proc/cmdline
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[idx].size = size_alloc + 0x20;
, это значит, что мы можем читать и писать на 32 байта больше реального объема выделенной памяти. Неплохо! Но как мы можем это использовать?
Источник: xakep.ru