Погружение в недра. Разбираем kernel exploitation, чтобы добраться до рута на виртуалке c Hack The Box

Содержание статьи

  • Разведка
  • Статический анализ
  • Разворачиваем стенд
  • Пишем эксплоит

В этой статье будем раз­бирать одну из самых слож­ных тем в сфе­ре 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) — объ­ектный файл, содер­жащий код, который рас­ширя­ет воз­можнос­ти ядра опе­раци­онной сис­темы. В нем реали­зова­ны все­го четыре фун­кции:

  • вы­деле­ние памяти в адресном прос­транс­тве ядра (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). Связь меж­ду вир­туаль­ными машина­ми уста­нав­лива­ется либо по пос­ледова­тель­ному пор­ту, либо по локаль­ной сети. Схе­матич­но это выг­лядит так.

Схе­ма отладки ядра 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

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *