В этой статье я рас­ска­жу об алго­рит­мах управле­ния памятью в 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 зас­тавля­ет сод­рогнуть­ся — вклю­чены все защит­ные механиз­мы! Но мы при­нима­ем вызов!

  1. Full RELRO. Гло­баль­ная таб­лица сме­щений (GOT) дос­тупна толь­ко для чте­ния. Это озна­чает, что мы не можем переза­писать в ней ука­затель фун­кции, что­бы изме­нить ход выпол­нения прог­раммы.
  2. Canary found. В сте­ке раз­меща­ется «канарей­ка» (опре­делен­ное зна­чение, переза­пись которо­го озна­чает, что стек был изме­нен), поэто­му перепол­нение сте­ка нам не све­тит, если толь­ко мы не смо­жем заполу­чить кон­троль над «канарей­кой».
  3. NX enabled. Озна­чает отсутс­твие областей в памяти, поз­воля­ющих одновре­мен­ную запись и исполне­ние (RWX). Поэто­му мы не можем помес­тить в адресное прос­транс­тво шелл‑код и запус­тить его.
  4. 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 (появил­ся в glibc 2.26) для любых чан­ков, которые мень­ше или рав­ны 0x410 байт. Все­го 64 односвяз­ных спис­ка на каж­дую аре­ну. Каж­дый tcache bin хра­нит чан­ки оди­нако­вого раз­мера. Каж­дый tcache bin может хра­нить мак­симум семь осво­бож­денных чан­ков.
  2. Fast для любых чан­ков, которые мень­ше или рав­ны 0x0b байт. Все­го десять односвяз­ных спис­ков на каж­дую аре­ну c раз­мером от 0x20 до 0xb0.
  3. Unsorted. Двус­вязный спи­сок, который может содер­жать чан­ки любого раз­мера. Каж­дая аре­на содер­жит толь­ко один такой спи­сок. При выделе­нии области памяти перед unsorted bin сво­бод­ные чан­ки ищут­ся сна­чала в бинах tcache, fast и small. Large bin опра­шива­ется пос­ледним.
  4. Small — кол­лекция из 62 двус­вязных спис­ков на каж­дую аре­ну раз­мером от 0x20 до 0x3f0 (перек­рыва­ются по раз­мерам с fast bins).
  5. Large — кол­лекция из 63 двус­вязных спис­ков на каж­дую аре­ну, из них каж­дый содер­жит чан­ки, раз­мер которых лежит в опре­делен­ном диапа­зоне (нап­ример, largebin 0x400 содер­жит осво­бож­денные чан­ки раз­меров 0x400–0x430).

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

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

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

Tcache bin в отладчике
Tcache bin в отладчи­ке

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

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

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

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

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

 

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

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

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

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

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

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


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

  1. Аватар

    fr0ster

    21.01.2021 в 18:29

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

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