В этой статье я рас­ска­жу об алго­рит­мах управле­ния памятью в Linux, тех­никах heap exploitation и методах экс­плу­ата­ции уяз­вимос­ти Use-After-Free со все­ми вклю­чен­ными механиз­мами защиты. А поможет мне в этом RopeTwo — одна из самых слож­ных машин с Hack The Box.

В статье «Раз­бира­ем V8» я опи­сывал спо­соб экс­плу­ата­ции намерен­ной уяз­вимос­ти в движ­ке V8. Она поз­волила получить шелл, но, что­бы прод­винуть­ся даль­ше и получить флаг юзе­ра, тре­бует­ся решить новую слож­ную задачу — про­экс­плу­ати­ровать уяз­вимос­ти работы с памятью.

 

Разведка

Пер­вым делом под­клю­чаем­ся к тач­ке по SSH и запус­каем скрипт поис­ка уяз­вимос­тей для эска­лации при­виле­гий. Лич­но я пред­почитаю исполь­зовать LinPEAS.

artex@kali:/home/artex/HTB/RopeTwo# ssh -i key chromeuser@10.10.10.196
artex@kali:/home/artex/HTB/RopeTwo# scp -i ssh/key linpeas.sh chromeuser@10.10.10.196:/tmp

chromeuser@rope2:/tmp$ chmod +x linpeas.sh
chromeuser@rope2:/tmp$ ./linpeas.sh > linpeas.txt

artex@kali:/home/artex/HTB/RopeTwo# scp -i ssh/key chromeuser@10.10.10.196:/tmp/linpeas.txt linpeas.txt

Смот­рим вни­матель­но отчет, ана­лизи­руя каж­дую строч­ку. В раз­деле «Interesting Files — SUID» видим инте­рес­ный файл — rshell.

Файлы с включенным битом SUID
Фай­лы с вклю­чен­ным битом SUID

Пос­мотрим вни­матель­нее, что это.

Запускаем rshell
За­пус­каем rshell

По­хоже на restricted shell с вклю­чен­ным битом SUID, это явно наш паци­ент! Ска­чива­ем и нат­равли­ваем на него «Гид­ру». Не буду при­водить здесь весь лис­тинг дизас­сем­бли­рован­ного кода, я вмес­то это­го сде­лал уп­рощен­ную диаг­рамму с основной логикой фун­кций rshell.

Диаграмма основных дизассемблированных функций
Ди­аграмма основных дизас­сем­бли­рован­ных фун­кций

Как ока­залось, это вов­се не restricted shell, а лишь его эму­ляция. Нам дос­тупно все­го нес­коль­ко команд: add, edit, rm, ls, echo, id и whoami. Самые инте­рес­ные из них — пер­вые четыре (поз­же выяс­нится, что три). Они поз­воля­ют соз­давать, изме­нять, уда­лять и отоб­ражать объ­екты («фай­лы») и выделять соот­ветс­тву­ющие им учас­тки в памяти с опре­делен­ным раз­мером (не более 112 байт) и кон­тентом. При­чем коман­да ls выводит толь­ко име­на фай­лов, без содер­жания. Что ж, все ука­зыва­ет на то, что впе­реди нас ждет heap exploitation.

В интерне­те мно­го информа­ции на эту тему, начать свое пог­ружение в нее мож­но, нап­ример, с сай­та Дха­вала Капила. Пер­вое, что нам нуж­но, — най­ти уяз­вимос­ти в коде, которые мож­но про­экс­плу­ати­ровать.

На пер­вый взгляд, никаких явных уяз­вимос­тей в коде нет. Вез­де исполь­зуют­ся либо безопас­ные фун­кции, либо про­вер­ки раз­мернос­ти, а ввод тер­миниру­ется нулем. Я пот­ратил мно­го вре­мени, преж­де чем нашел уяз­вимость под наз­вани­ем use after free (UAF).

Под­робнее о том, что такое UAF, мож­но почитать, нап­ример, в бло­ге Orange Cyberdefense.

 

Настраиваем окружение

Наш экс­пло­ит мы будем писать с помощью pwntools — незаме­нимой питонов­ской биб­лиоте­ки для соз­дания экс­пло­итов. Но пер­вым делом нам нуж­но нас­тро­ить окру­жение. Для это­го, помимо самого rshell, необ­ходимо ска­чать с машины RopeTwo биб­лиоте­ки glibc (стан­дар­тная биб­лиоте­ка C, реали­зующая сис­темные вызовы и основные фун­кции, такие как malloc, open, printf) и ld (биб­лиоте­ка динами­чес­кой лин­ковки). Это необ­ходимо для пол­ной сов­мести­мос­ти вер­сий. Во‑пер­вых, новые релизы glibc час­то содер­жат изме­нения, устра­няющие те или иные уяз­вимос­ти, а во‑вто­рых, нам важ­но, что­бы сме­щения всех фун­кций сов­падали. Ниже при­веде­на таб­лица вер­сий glibc и воз­можнос­ти исполь­зования раз­личных вари­антов heap exploitation.

Популярные техники heap exploitation
По­пуляр­ные тех­ники heap exploitation

Ви­дим, что в нашем слу­чае мы будем исполь­зовать вер­сию libc 2.29.

Версия libc на сервере
Вер­сия libc на сер­вере

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

Те­перь надо про­пат­чить наш rshell коман­дой patchelf --set-interpreter ./ld-2.29.so rshell, что­бы он стал исполь­зовать лин­кер нуж­ной вер­сии.

Те­перь, если ты хочешь запус­тить rshell c нуж­ной вер­сией libc, исполь­зуй коман­ду

LD_PRELOAD='libc-2.29.so' rshell
Вывод checksec
Вы­вод checksec

Вы­вод checksec зас­тавля­ет сод­рогнуть­ся — вклю­чены все защит­ные механиз­мы! Но мы при­нима­ем вызов!

Full RELRO. Гло­баль­ная таб­лица сме­щений (GOT) дос­тупна толь­ко для чте­ния. Это озна­чает, что мы не можем переза­писать в ней ука­затель фун­кции, что­бы изме­нить ход выпол­нения прог­раммы.
Canary found. В сте­ке раз­меща­ется «канарей­ка» (опре­делен­ное зна­чение, переза­пись которо­го озна­чает, что стек был изме­нен), поэто­му перепол­нение сте­ка нам не све­тит, если толь­ко мы не смо­жем каким‑либо обра­зом заполу­чить кон­троль над «канарей­кой».
NX enabled. Озна­чает отсутс­твие областей в памяти, поз­воля­ющих одновре­мен­ную запись и исполне­ние (RWX). Поэто­му мы не можем раз­местить в адресном прос­транс­тве шелл‑код и запус­тить его.
PIE enabled. PIE — это аббре­виату­ра от Position Independent Executable. Озна­чает, что базовый адрес исполня­емо­го фай­ла меня­ется при каж­дом запус­ке, поэто­му без утеч­ки исполь­зовать ROP и ret2libc не получит­ся.

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

def add(name, size, content="A"):
io.sendlineafter('$ ', 'add '+str(name))
io.sendlineafter('size: ', str(int(size)))
io.recvuntil("content: ")
io.sendline(content)
def edit(name, size, content="A"):
io.sendlineafter('$ ', 'edit '+str(name))
io.sendlineafter('size: ', str(int(size)))
if int(size) != 0:
io.recvuntil("content: ")
io.send(content)
def rm(name):
io.sendlineafter('$ ', 'rm '+str(name))

Тут сра­зу важ­но отме­тить, что в фун­кции add мы не можем исполь­зовать io.send(content), как в фун­кции edit, пос­коль­ку для записи кон­тента add исполь­зует fgets, а edit — read. Поэто­му вос­поль­зуем­ся методом io.sendline(content), который добав­ляет в кон­це перенос стро­ки и голов­ной боли нам (об этом даль­ше).

 

Куча, бины и чанки

Бин — это кор­зина, в которую попада­ют осво­бож­денные учас­тки памяти (чан­ки). Пред­став­ляет собой спи­сок осво­бож­денных чан­ков, который быва­ет односвяз­ный и двус­вязный. Основное его наз­начение — быс­трое выделе­ние учас­тка памяти (по ста­тис­тике, в прог­раммах час­то выделя­ются и осво­бож­дают­ся учас­тки памяти оди­нако­вых раз­меров). Вид связ­ности спис­ка зависит от того, в какой бин попал осво­бож­даемый чанк, что, в свою оче­редь, зависит от его раз­мера. Раз­мер чан­ка уве­личи­вает­ся крат­но 16 бай­там (0x20 → 0x30 → 0x40...). Это зна­чит, что млад­шие 4 бита поля раз­мера чан­ка не исполь­зуют­ся. Вмес­то это­го они содер­жат фла­ги сос­тояния чан­ка:

  • PREV_INUSE — уста­нов­ленный, озна­чает, что пре­дыду­щий чанк исполь­зует­ся, и наобо­рот;
  • IS_MMAPPED — озна­чает, что этот чанк был выделен mmap();
  • NON_MAIN_ARENA — озна­чает, что чанк не при­над­лежит main arena.

Аре­на — это струк­тура фун­кции malloc, содер­жащая бины для осво­бож­денных из кучи чан­ков.

Мак­сималь­ное количес­тво арен для про­цес­са огра­ниче­но количес­твом ядер про­цес­сора, которое дос­тупно это­му про­цес­су.

На дан­ный момент сущес­тву­ет пять типов бинов (циф­ры при­веде­ны для 64-бит­ных при­ложе­ний):

  1. Tcache bin (появил­ся в glibc 2.26) для любых чан­ков, которые мень­ше или рав­ны 0x410 байт. Все­го 64 односвяз­ных спис­ка на каж­дую аре­ну. Каж­дый Tcache bin хра­нит чан­ки оди­нако­вого раз­мера. Каж­дый Tcache bin может хра­нить мак­симум семь осво­бож­денных чан­ков.
  2. Fast bin для любых чан­ков, которые мень­ше или рав­ны 0x0b байт. Все­го десять односвяз­ных спис­ков на каж­дую аре­ну c раз­мером от 0x20 до 0xb0.
  3. Unsorted bin. Двус­вязный спи­сок, который может содер­жать чан­ки любого раз­мера. Каж­дая аре­на содер­жит толь­ко один такой спи­сок. При выделе­нии области памяти перед Unsorted bin сво­бод­ные чан­ки ищут­ся сна­чала в бинах tcache, fastbin и smallbin. Large bin опра­шива­ется пос­ледним.
  4. Small bin — кол­лекция из 62 двус­вязных спис­ков на каж­дую аре­ну раз­мером от 0x20 до 0x3f0 (перек­рыва­ются по раз­мерам с fastbins).
  5. Large bin — кол­лекция из 63 двус­вязных спис­ков на каж­дую аре­ну, из них каж­дый содер­жит чан­ки, раз­мер которых лежит в опре­делен­ном диапа­зоне (нап­ример, 0x400 largebin содер­жит осво­бож­денные чан­ки раз­меров 0x400–0x430).

Для наг­ляднос­ти выпол­ним прос­той код, который соз­дает и очи­щает два чан­ка по 0x30 байт каж­дый:

add(0, 0x28)
add(1, 0x28)
rm(0)
rm(1)

Пос­мотрим, как это выг­лядит в GDB. Мы видим, что оба чан­ка попали в tcachebin 0x30. Струк­тура tcachebin пред­став­лена на скрин­шоте.

Tcachebin в отладчике
Tcachebin в отладчи­ке

Од­носвяз­ный спи­сок мож­но пред­ста­вить так.

Односвязный список бина
Од­носвяз­ный спи­сок бина

А двус­вязный — так (для small bins Size будет оди­нако­вый, а для large bin еще добавят­ся ука­зате­ли fd_nextsize и bk_nextsize).

Двусвязный список бина
Двус­вязный спи­сок бина

fd ука­зыва­ет на сле­дующий чанк в спис­ке, а bk — на пре­дыду­щий.

 

Пишем эксплоит

С теорией покон­чено, теперь поп­робу­ем написать при­митив про­изволь­ной записи, исполь­зуя UAF. Мы не можем исполь­зовать тех­нику Tcache Dup (Double Free), так как в glibc 2.29 появи­лась защита от нее.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

1 комментарий

  1. Аватар

    fr0ster

    21.01.2021 в 18:29

    Я не стал запиливать удаленный експлойт, я выполнил его на целевой машине, зато выполнялся он явно быстрее нескольких минут 🙂
    Я мультипроцессинг запилил

Оставить мнение