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

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


7-02-2021, 00:00. Автор: Харитон
Раз­бира­ем 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 для отладки ядра.


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

Ос­тановим­ся на глав­ных момен­тах. Пер­вым делом пос­мотрим, какая вер­сия ядра исполь­зует­ся в 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 бай­та боль­ше реаль­ного объ­ема выделен­ной памяти. Неп­лохо! Но как мы можем это исполь­зовать?



Перейти обратно к новости