How2Heap на русском

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
6,642
Реакции
23
Сегодня я бы хотел поделиться с вами некоторыми знаниями в области эксплуатации кучи на современных linux системах.
Под словом "современные" подразумевается аллокатор ptmalloc - в котором присутствует кеширование чанков. Этот стандарт по умолчанию присутствует в большинстве дебиан-подобных дистрибутивах, так что его мы сегодня и рассмотрим.


Цель данного материала - простыми словами и на понятных примерах раскрыть суть довольно интересных вещей, которые с первого взгляда могут показаться чем-то сложным.
На форуме есть масса материалов по Win эксплуатации, но значительно меньше по Linux. Данная работа рассчитана в первую очередь на новичков в данном направлении и в ней рассмотрены те скользкие моменты над которыми я лично ломал голову ночами. Тут не будет ничего всех-естественного, но я постараюсь объяснить все максимально просто и понятно. Поехали.


В качестве стенда будем использовать debian-10 + libc-2.30 на котором будет развёрнут таск с HTB - chapter1.



  • Анализируем подопытного
checksec --file ./chapter1
11200

Отлично, у нас есть статичный сегмент самого бинарного файла, который не подвержен рандомизации. об этом свидетельствует надпись "No PIE".
А это означает, что сегмент .BSS (о нём чуть позже в деталях) так же будет статичен. И мы сможем эксплуатировать табличку GOT.
Кроме этого, мы можем наблюдать перечень включенных защит - а именно:
частичное RELRO - то бишь защита PLT семента.
NX бит - то бишь не исполняемый стек.
Сanary - канарейка, то бишь рандомная велечина перед указателем дна стека, при нарушении которой программа аварийно завершится.
Ну и, само собой, на практически любой машине будет ASLR на уровне системы. То бишь, все кроме нашего бинарника, будет в рандоме.
Подробнее о том, что является нашим бинарником, а что нет - рассмотрим ниже.
Более подробно про митигейшены можно прочесть тут: https://www.opennet.ru/opennews/art.shtml?num=27938


. Давайте взглянем на приложение в рантайме.
11201

Сразу бросается в глаза то, что мы не можем читать из чанка. Можем только создавать чанк, изменять его содержимое, и удалять чанк.



  • Вскрытие покажет
Ну что, нужно немного ресерча. Для этого на помощь приходит айда-про.
11202

Несколько коряво распознана логика меню, но становится понятно, что тут в uInput считывается ввод от юзера, дальше он попадает в atoi() и исходя из этого мы переходим в какую-ллибо из ф-й по работе с памятью.


11244

Хм, как видно в начале ф-и - программа перебирает циклом for некоторый массив указателей (исходя из названия ptr, pointer) и, если находит такую ячейку массива, которая возвращает false - то бишь свободную - то выходит их цикла и использует дальше уже ее. Если же циклу не узаётся найти свободного поля для указателя на чанк в диапазоне от 0 до 15 включительно - нас выбрасывает из функции с ошибкой puts("Too many notes!");


далее, нас просят ввести размер чанка и запрашивают у аллокатора память данного размера. Заглушка на случай не удачной аллокации - признак хорошего тона. После чего вызывается ф-я reader(). Ее и посмотрим.
11205

снова заглушка. и проброс пользовательского ввода в штатную ф-ю read(). Взглянем на неё по ближе:
11206

считать из файлового дескриптора кол-во байт указанное в переменной count и записать их по указателю в buf. А это говорит о чем? правильно, о том, что мы вообще не ограничены bad char-ами. И можем на аллокации чанка заполнять его абсолютно чем угодно. На счёт файловых дескрипторов более прдробно можно прочесть, например, тут: https://xss.is/threads/37092/


С этим разобрались идём далее:
11207

В начале используется та же конструкция, что и в меню. после трансформации импута в интовое число программа проверяет, попадает ли оно в наш допуск (от 0 до 15 включительно), если нет - мы выходим из игры, а если да - то тогда самое интересное.
сперва вычисляется chunk_size. вычисляется он с помощью ф-и strlen().


Давайте обратимся к man strlen что бы уточнить спецификацию.
11208

Другими словами ф-я принимает указатель на память и начинает по этой памяти ехать, словно комбайн, до тех пор, пока не упрётся в нульбайт. После чего немедленно глушит мотор и возвращает кол-во байт от начального включительно до нуль-байта не включительно.


Что мы должны отметить для себя на этом моменте?
Что мы можем запросить у аллокатора до 16 чанков, указать им не отрицательное значение размера, и после аллокации заполнить указанное кол-во байт любыми значениями.
После чего мы может запросить у программы изменение содержимого в чанке, и изменить столько байт, сколько будет находиться в чанке от начала пользовательских данных до первого встречного нульбайта.
Забегая вперёд я скажу что эта уязвимость называется one-byte-overflow. Но мы ещё это посмотрим на практике.


Сперва давайте добьем скучное, но нужное, и после перейдём к интересному
11209

Всё тоже самое. если указатель в диапазоне 0-15 включительно, то вызываем free(), чем отдаём чанк во владения аллокатора.


Ну и крайний пункт в меню обозначен прямо в main()
11210

Это может пригодиться нам в дальнейшем, т.к. взаимодействие с данным пунктом происходит в главном стековом фрейме.
Вообще мне понравилось как вот тут https://m.habr.com/ru/post/480454/ описана работа с декомпилятором.


Код:
/*
на ассемблере нету функций. зато есть стек-фреймы или кадры стека.
и, в большинстве случаев, вхождение в какую либо функцию обозначено мнемоникой call
а выход из этой функции обозначен мнемониками leave; ret;
после  выполнения call (входа в функцию) следует пролог на ассемблере, в  котором стековый кадр сдвигается вверх, к младшим байтам.
а  на выходе из этого стекового кадра, в эпилоге ассемблера, программа  восстанавливает стековый кадр из указателя дна стека ($rbp).
именно по этому канарейка расположена перед $rbp
*/



  • От теории к практике
Вообще в PWN опыт решает. Когда у тебя появляется опыт - ты уже видешь о чем говорит тот или иной краш. Ты уже держишь в голове несколько потенциальных векторов и ты уже понимаешь куда бить.
Когда от CTF ты переходишь к эксплуатации реального софта - ты снова чувствуешь себя ничего не понимающим нью-байтом, хотя до этого ты на раз-два раскидывал сложные таски CTF.
И снова опыт. И так до бесконечности.


Первая команда после открытия софта под дебагером должна быть i fu
11211

Потому, что после запуска программы в списке функций появится уже куча других функций из libc и других библиотек. А так мы можем оценить качество нашего ресёрча. и проверить ничего ли мы не упустили.
Вроде как нет. Сразу же стоит отметить что у нас есть 2 печатающие функции - это puts и printf. Но нету возможности печати из чанка. Все вызовы данных печатающих функций происходят с захардкоженными в бинарник строками.
Мы ни как не можем повлиять на то, что передаётся им в аргументы.


Окей, едем далее: скомандуем start что бы проинициализироваться. А после got и следом plt
11213

Перед тобой структура таблички GOT - Глобал Оффсет Тейбл.
Что это такое и с чем его едят ты спокойно найдёшь в интернете.
Но мы с тобой обратим внимание на следующие вещи:
1 - табличка GOT непосредственно связана с табличкой PLT.
2 - до тех пор, пока функция не вызывалась не разу ее динамический адрес не известен. Об этом свидетельствует 2 признака:

  • в сегменте GOT лежит указатель -> на статичный адрес самого бинарника
  • этот статичный адрес имеет оффсет +6
Взглянем на доступные сегменты памяти. Учти, что запуск программы из под дебагера по умолчанию выключает ASLR. Так что все сегменты у нас представлены без рандомизации.
Что бы включить его - можно использовать команду aslr on и перезапустить программу.
11214

Как видешь, под понятие PIE попадает три сегмента. С бинарными командами - имеет бит X. Со статичными данными (строками) - имеет бит R. И сегмент BSS - имеет бит W.
Биты привилегий аналогичны стандартам линукса. выполнение, чтение, запись. Подробнее про это можно прочесть в той же статье, что и про файловые дескрипторы.


Почему +6? давай посмотрим
11215

что мы тут видим? мы видим пуш ноль, после безусловный переход, который напоминает сальто назад. потом пуш по динамическому адресу. и потом ещё один джамп (безусловный переход) по тому же динамическому адресу.
Красиво, не так ли?


Теперь давай разберёмся с .BSS сегментом. В этот сегмент попадают значения таблички GOT, а так же все глобальные и статичные данные объявленные программистом. Почему это происходит?
Потому, что компилятор знает заранее сколько места понадобится в памяти, что бы разместить там объекты, которые объявлены заранее. Например указатели. Он знает, что у нас может быть всего 16 чанков. Он знает что 1 указатель занимает в памяти 1 поле - 8 байт. Он размещает этот массив указателей именно в секции .BSS


Окей. Давай взглянем чуть под другим углом. команда tele aka telescope позволяет посмотреть что есть в памяти и сама переходит по всем указателям.
11216

Однако учти, что если она встречает несколько полей подряд с одинаковыми значениями, то она сокращает вывод. Так что можно чего-то не заметить.


Что бросается в глаза? Отсутствие сегмента оперативной памяти. Heap. Почему? Потому, что он ещё не инициализирован. Потому, что ещё не было не одного обращения к аллокатору.
Даваай-ка это исправим.


продолжаем исполнение программы с помощью c
11218

аллоцируем чанк. И прерываем исполнение программы с помощью sigint - либо командой Ctrl+C
смотрим что измменилось:
11219

1 - появился сегмент heap.
2 - в табличке GOT появились динамические адреса на libc.


давай посмотрим ещё раз на сегмент .BSS
11221

Указатель на наш чанк размером в 1 байт и контентом "1" появился в секции .BSS. tele любезно перешёл по указателю и посмотрел что там находится.


Окей, теперь давай разбираться с heap.
Для начала нужно знать следующее:
Код:
/*
логика менеджера памяти напрямую зависит от версии libc.
в каждой версии libc есть свои отличия от других версий.
есть глобальные отличия, есть не значиельные. Но все это нужно учитвать.
глобальным отличием является введение ptmalloc с версий libc 2.26, если я не ошибаюсь
почитать по поводу эксплуатации ткеша можно на этих слайдах
https://github.com/bash-c/slides/blob/master/pwn_heap/tcache_exploitation.pdf
*/
ну а мы же с тобой рассмотрим на конкретном примере что к чему и как оно все работает.


в pwndbg (кто не знает - это такой "обвес" для GDB) есть замечательная команда vis и heap
но, для их работы необходимы отладочные символы. По поводу отладочных символов написано вот тут: https://xss.is/threads/37704/
11222




  • Забуриваемся ещё глубже.
Для начала нужно понять что такое чанк, из чего он состоит и какой он вообще бывает.
11223

Чанк бывает аллоцированный и свободный.
вот тебе скриншот из моего конспекта, который я писал, когда проводил ночи на пролёт под дебаггером в попытках понять что тут к чему:
11226

В исторических целях вот ещё скриншот про unlink, и в общеобразовательных целях - по UAF. В случае с unlink - это уже не работает в своем первозданном виде на tcache. Но есть другой подход. Об этом позже.
11227

*прим. автора: некоторые данные на скриншотах выше могут быть не актуальны, однако, на тот момент когда я это писал - они очень хорошо помогли мне разобраться в чём суть и как оно работает.




Вернемся к нашим баранам - когда чанк особождается явно начинает проявляться его класс. Он может быть класса tcache, fastbins, класса small, класса unsorted и large.
Но в данном чтиве мы рассмотрим unsorted и tcache чанки. Я думаю этого с головой хватит, что бы заполнить тарелку для твоего мозга пищей
yH5BAEAAAAALAAAAAABAAEAAAIBRAA7



По умолчанию ткеш имеет 7 слотов для освобождённых чанков. этот параметр задаётся при компиляции libc. Так что сисадминам-гентушникам в этом плане повезло, наверное)))
Так вот. Класс tcache делится в свою очереь на динамические группы - согласно размеру. То есть у нас может быть 7 ткеш чанков одного размера. если мы освобождаем 8 чанк такого-же размера - он попадает в unsorted.
если после этого мы освобождаем 9й чанк другого размера - он снова попадает в ткеш, но уже своего размера. если после этого мы освобождаем 10й чанк такого же размера, как первые 8 - он попадает опять в unsorted.
11228

освобождать чанки я начал с выделенного чанка. таким образом список закольцевался именно на нём. как видешь я заполнил список ткеш чанков типа 0х20 семью чанками. после этого я освободил чанк на 0х50 - и он снова попал в ткеш. но уже своего класса. Обрати внимание, что prev_in_use бит игнорируется. а в указателях FD и BK лежат адреса на форвард и беквард чанки соответственно. Тут стоит обратить внимание на то, что FD и BK могут смотреть как вверх так и вниз, в зависимости от последовательности освобождения памяти. Попробуй сам, и разберись с этим.


Так, а какой индекс будет иметь следующий аллоцированный чанк?
11229



Помнишь мы выше смотрели указатель на первый аллоцированный чанк? Он лежал по адресу 0x6020c0
У нас массив чанков на 16 указателей. Не сложно посчитать что следующий не false указатель под индексом 7, если считать c нуля.
Освобождаем чанк с индексом 7 и смотрим в кучу:
11230

Но мы ожидали, что чанк попадет в unsorted? почему-же он в fastbins?
11231



Подумали чистатели.


давай попробуем чуть иначе. Мы не будем вводить все это дело в ручную, а напишем на питоне некоторый скрипт, который нам поможет во всём разобраться.
Но сперва, у нас остался ещё 1 маааленький момент. Мы же нашли уязвимость, во всяком случае мы думаем что мы ее нашли, и классифицировали ее как one-byte-overflow.
В чем же тут суть? Суть в том, что когда мы аллоцируем чанк такого размера, для которого истенно тверждение что: (size(data_segment)+size(size_segment))%16 == 0
то есть размер чанка (размер дата + размер поля size) кратно 16 -- тогда поле data контактирует с полем size следующего чанка. Соответственно, если мы проедемся функцией strlen() по этой грядке - то он вернёт больший размер, чем был аллоцирован. И перезапишет больше байт. Главным условием для этого является то, что не должно быть нигде нульбайтов по дороге до поля size следующего чанка.
11234

данные намазываются на поля, как масло на хлеб. берется поле, и записывается с права на лево, (и берется следующий кусочек хлеба) и берется средующее поле с лева на право.


перезаписываем чанк символами "B" - и наблюдаем, что размер чанка с индексом 2 из 0х21 превратился в 0x42
11235

Если попытаться теперь освободить чанк с индексом 2 то мы получим следующий краш:
11236

Запомни визуально выделенную инструкцию. Она железобетонно гарантирует что размер чанка кораптед :3

  • P1w-P1w


11232

По центру - эксплойт. Вернее, среда его разработки. Справа - среда его тестирования - python3 в рантайме. И слева - отладчик.
Поработал справа, достиг нужного эффекта слева - сохранил изменения по центру. Очень удобно.


Для начала определяем функции для созддания удаления и изменения чанков соответственно.
Потом запускаем процесс. получаем его PID. Слева этот PID аттачим командой attach #PID
Как видешь - адреса уже в ASLR рандомизации.


Давай теперь составим с тобой план действий. Что нам нужно? Что мы можем? Как мы поступим?
1 - нам нужно RCE
2 - мы можем изменить размер любого чанка, кроме нулевого (с индексом 0). Так же, мы можем использовать гаджеты из бинарника и секцию .BSS так как PIE off.
3 - это уже вопрос более глубойкий. Есть, на вскидку, как минимум 4 варианта как выполнить свой код.

  • перезаписать адрес возврата сделав стек-пйвотинг - но для этого нужно знать адрес стека
  • вызвать ван-гаджет - но для этого нужно знать адрес либц
  • перезаписать что-нибудь в таблице GOT, затригерить это что-то. Но для этого нужен вангаджет. или стек пайвотинг в один тейк. (и то и другое, кстати, тут есть)
  • зафорсить какую-нибудь формат стринг уязвимость и перезаписать что-нибудь ею. или сликать что-нибудь ею.
Либо?

  • ммм. аллоцировать чанк в секции libc. перезаписать хук. затригеррить хук. для этого не нужно знать вообще не одного адреса. вероятность "попасть" будет примерно 1/4096.
Ну как-то так. Окей. Читать из чанка мы не можем. Адреса, кроме бинарника, мы не знаем. В принципе для первых 4 пунктов нам нужно знать адреса. Как же поступить?
Давай для начала посмотрим вот сюда:
11237

мм, не густо. тогда давай посмотрим вот сюда: https://github.com/shellphish/how2heap/
Тут есть пару интересных техник. Они воссозданы синтетически, но суть оловить можно. особенно если скомпилировать с ключем -g и смотреть это кино пд отладчиком.


Кароче, нам надо сделать так, что бы мы смогли получить хоть какой-то примитив для начала. Чтение или запись. хоть что-то. Для этого, сперва нужно забить список ткеш:
11238

Сказано - сделано!


что дальше? дальше следующий освобождаемый чанк размера 0x110 попадает в unsorted bin.
11239

Смотрим более детально:
11240

То есть, у нас в чанке уже есть адрес в libc. Если бы в нашей программе была возможность печати из чанка - мы бы с лёгкостью его распечатали. Но такой возможности и нас нету.
Что же нам делать? Ну, наверное нам нужно попробовать оверлапнуть чанки. Больше мы по сути ничего и не можем сделать. Учти, что либц очень трепетно следит за всеми размерами метаданных всех чанков. И если ему что-то покажется странным - сигаборт.


Но, мы будет рассчётливы и аккуратны.
11241

Бэмс! И магия. Тёёёёмная мааагияяяя....
Чуть подробнее рассмотрим это заклинание:
1 - сендлайн(2) - выбор в меню изменения контента
2 - сбрасываем вывод
3 - сендлайн(14) - выбираем чанк, который будем изменять.
4 - сбрасываем вывод
5 - сенд(о*256+9байт) | тут важно то, что sendline на питоне терменирует строку байтом переноса строки \x0a. А нам он там не нужен. По этому send(). |
Выходит, что по адресу 0x1e81280 у нас был последний кусочек чанка с индексом 14. Чанк алллоцирован. Мы смело можем писать в него.
помнишь, как мажется масло на хлебушек? берется кусочек хлебушка (8 байт) и кусочек масла (8 байт). и мажется справа на лево. получается что \x30 попадает в начало поля, далее \x03. далее 6 нульбайтов. далее берется следующее поле и маленький кусочек маселка размером в 1 байтик намазывается на начало поля. получается то, что на скриншоте. То есть, мы правим поле prev_size на размер нашего фейкового чанка, который теперь составляет 0х330 байт включая размер мметаданных. и правим бит prev_in_use сследующего чанка на 0 - говоря о том, что наш фейковый чанк не используется, а свободен.
6 - мы перезаписываем данные чанка с индексом 11 (чанк, который идёт перед 12, логично блин) и во время изменения вылазим за его пределы, чем перезаписываем размер 12 чанка на байтый \x31\x03
почему не \x30\x03 ? потому что это prev_in_use бит. а предыдущий чанк, 11, у нас аллоцирован и используется.


Таким образом наш unsorted bin чанк свободен и 2 чанка внутри него - аллоцированны. Это и есть чанк оверлаппинг.
Повторяем действия ещё раз.
11243

отлично, отлично. теперь у нас есть 2 unsorted bin размером в 0x330 в нутри каждого из них есть по 2 allocated чанка размером в 0x110


А значит мы можем что? Правильно, мы можем выбрать следующий вектор для продолжения атаки. Как ты понял в куче очень много возможных элементов атаки, и комбинируя их, в зависимости от условий, ты можешь достичь желаемого результата.
Я думаю, что следует рассмотреть и применить методику tcache poisoning. Работает она следующим образом:
создаётся свободных tcache чанка одиннакового размера один рядом с другим. и в FD указатель младшего (у которого индекс меньше) записывается тот адрес, по которому мы хотим получить read/write примитив. Важно тут то, что этот адрес должет быть в сегменте, который доступен для записи, иначе мы получим краш.


Давай пробовать:
сперва мы освободим tcache что бы можно его заполнить, но уже нужными (подконтрольными) нам чанками:
*прим автора: тут я нечайно не скопировал одну инструкцию и вылетел с сигабортом, так что дальше адреса будут в рандомизации, но уже отличной от той, что выше. принципиально ничего не меняется.
11246

Вжух. И магия снова работает по полной. Хотя, с первого взгляда может показаться, что первое и второе заклиенание были более эффектны и наглядны - на самом деле это - куда круче.
По подробнее:
аллоцируем 7 чанков на 264 байта (264+8=0х110) - чем освобождаем ткейч.
освобождаем 2 чанка, которые находятся в теле нашего верхнего из двух, уже освобождённого, фейкового, unsorted bin-а.
далее, мы запрашиваем у менеджера памяти чанк разммером 1 байт. наглядно, аллокатор любезно нарезает на чанк на 32 в сумме с метаданными байт с верхушки нижнего (с индексом 0) unsorted bina.
Таки образом в аллоцированном чанке оказывается сразу 2 адреса. На либси и на кучу. почему? потому что они там были изначально, когда освобождался чанк на 0x110 и попадал в unsorted. форвард смотрел в либси, а бэквард - на предыдущий фейковый чанк. отсюда и происхождение адресов. Когда же мы аллоцируем там чанк - аллокатор не тратит процессорное время, что бы его зачистить. а просто отдаёт его нам со всем тем, что в нём. а вот наш unsorted уже не 0х330 байт, а 0x330-0x20=0x310 байт. теперь он уникален (в смысле нету второго такого unsorted) и оба указателя смотрят в либси.
Есть тут один момент. Исходя из логики самой программы я обязан что-то записать в созданный чанк. хотя бы байтик. я пишу туда \xff. это сути не меняет, так как последние 3 тетрады (полтора байта) в статике. то есть их можно смело менять нулями.
теперь важный и кропотливый момент: перезапись unsortedbin своими значениями.


Сперва я готовлю пейлоад. 264 байта - перезаписывают память пренадлежащую к нашему unsorted чанку. сверхну на скриншоте видно, что там знаки "!". Таким образом мы подбираемся к началу метаданных деаллоцированного и активного чанка типа tcache 0x110. перезаписываем его поле size на 0х110 - так как над ним был свободный unsorted. перезаписываем указатель FD на то место, где хотим аллоцировать новый чанк. это может быть любая памят с битом W. будь то стек, или .BSS или libc.
Следующие адреса не принципиальны, но пускай смотрят куда-то, где есть бит W. я поставил их смотреть в BSS но по дальше.
Потом добавляем 248 байтов "@" и снова перезаписываем поля size, FD, BK, но уже нижнего tcache 0x110 чанка.
На данный момент чанки не были перезаписаны. Перезапись происходит следом за аллокацией на 808 байт. а 808 байт == 0х330-8.
Таких движений немного не вывез даже pwndbg и один из чанков, по его мнению, пропал без вести.


Далее - очень важный момент заклинания. Если глянуть на скриншоты в начале статьи то будет ясно что по адресу 0x602018 находится [email protected]
Почему я выбрал это место, что бы аллоцировать там чанк? Потому, что в нее, в функцию free(), передаётся указатель на чанк. Точно так же, как в функцию printf() передаётся указатель на строку. Так что нам мешает заменить функцию free() функцией printf()?
0x400710 - это адрес printf(). 0x400710 - статичен. 0x602018 - тоже статичен. единственное, что нужно учесть, что следующее поле от 0x602018 затрется нулями в виду особенностей эксплуатации. Так как там печатающая функция puts() - мы можем не заморачиваться особо с поиском puts() в plt, а просто перезаписать и её поле значением [email protected].
p4w = p64(0x400710)+p64(0x400710)


11247

Шикарно. У нас есть чанк в BSS сегменте) Единственное, что мы его не можем деаллоцировать. Так как его поле size будет корраптед. Но, мы можем, при необходимости, создать выше ещё один фейковый чанк. Поправить поле size текущего на валидное значение. и спокойненько его деаллоцировать. в результате там появится несколько интереснейших адресов. Но, я оставлю возможность попробовать это заклинание в качестве домашнего задания моему читателю, т.к. у меня лимит в 40 изображений и он подходит к концу. Эх. А я хотел ещё про аллокацию в сегменте libc рассказать.


11249

И так. Пробормотав обратное заклинание я сделал следующее:
1 - попытался удалить чанк под индексом 8 - на скриншоте выше он ровового цвета. На тот момент в GOT в поле free() находилась функция printf(). Как видно справа - мне любезно предоставили адрес в либси.
2 - заменил 3 младших нулями и вычислил базу либси.
3 - взял случайный чанк и записал в него модификаторы %#lx
4 - заэнфорсил уязвимость форматных строк попыткой удалить чанк фенкцией printf(). В результате чего содержимое чанка попало в printf() и распечатало мне адрес со стека.
5 - восстановил значение в GOT поля принадлежащего функции free() на 0х4006d6 - а мы с вами на этом моменте заострили внимание, что оффсет +6 заставляет заново вычислить и записать в GOT реальный адрес функции.
6 - подготовил 2 свободных tcache чанка размера 0х110 для следующей атаки. Чем, попутно, и восстановил реальное значение free().


Ну всё. У нас есть адреса стека. у нас есть адреса либси. у нас есть ван_гаджет. Вопрос только в том, что в libc-2.30 условия для запуска гаджета - это $rsp+0x60 == 0x0
Такую картину нам может дать функция exit(). Так как она отрабатывает из главного карда.


11262



Ещё раз пройдёмся по заклинанию:
1 - обозначаем статичный адрес в .BSS для функции exit
2 - колдуем с оффсетамми для перезаписи нижнего unsorted bin (важно то, что мы отрезали от него 0x20 байт, так что он теперь не 0x330, а 0x310. по этому и оффсеты будут меньше до первого блока метаданных
3 - аллоцируем чанк на 778+8=0х310 байт
4 - перезаписываем метаданные свободных чанков tcache
5 - аллоцируем 2 tcache чанка нужного размера
6 - тригеррим exit() из главного меню.
7 - получаем PROFIT!


11261



Эксплоит отрабатывает где-то секунды 4-5.



  • На десерт
Но, это ещё не всё. Ты наверное обратил внимание на комментарии в коде эксплойта, которые кричат о том, что там автор имел намерения перезаписывать __malloc_hook? Все знают про то, что если перезаписать __malloc_hook на какой-нибудь адрес памяти с привелегиями X - то он выполнится. Точнее, туда будет осуществлён безусловный переход.
Так вот, была такая техника House of Roman - эта техника работала на фастбинах, если я не ошибаюсь. Она позволяла с вероятностью 1/16 выполнить код не зная вообще не единого адреса.


Так вот, ещё до того, как я узнал о существовани этого сценария - мне пришла в голову следующая мысль:


11260



Мы делаем чанк оверлаппинг, а потом аллоцируем небольшой кусочек, что бы до начала освобождённого tcache чанка осталось отличное от самого tcache расстояние. И тогда аллоцируем это расстояние.
Таким образом в наш FD указатель попадает адрес непосредственно в libc. Более того, для этого нам не нужно знать никаких адресов, и даже если PIE будет включен - для нас это не будет помехой.


Взглянув на адрес становится ясно, что над ним расположенны как раз таки __malloc_hook


А это означает, что если перезаписать младший байт нульбайтом - то мы сможем аллоцироваться прямо на хуках.
можно было бы сделать аллокацию на 1 байт и записать туда \x00, но мы, зная оффсет до нужного нам хука, сразу аллоцируемся на нём.


11259



Момент истины настал. А я уже малёха подустал
yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
на написание этого материала ушло около 8 часов. подряд.


11258



Но нужно доделать, раз уж взялся.



  • Last Word
И так что мы имеем в итоге?
1 - мы затераем следующее от места аллокации поле. это не страшно в данной ситуации, но нужно учитывать.
2 - мы не имеем возможности положить адрес в __malloc_hook, а изначально там пустота.
3 - самое главное во всём этом то, что у нас гаджет находится по адресу отличному от адреса хука на 6 тетрад. младшие 3 тетрады - статичны. их брутфорсить не нужно. старшие 2 байта - идентичны. А отсюда следует, что шансы попасть по ван-гаджету примерно 16*16*16.
pwndbg> p 16*16*16 $30 = 4096
4 - остаётся только затригеррить. но на реальных программах это не не должно составить большого труда.


Пожалуй, назову эту технику House of Cats :3



  • Gr3373n95:
  • Фаззу за то что когда-то ввел в этот мир
  • ребятам с чата за то, что терпят мои вопли в 3 часа ночи))
  • Владу из Питера, с которым мы хоть и редко общаемся, зато весьма продуктивные идеи рождаются.
  • bilka00 за подсказки по ЯП.
  • https://heapme.f2tc.com/login - тут можно представить в графике происходящее в хипе. и промотать вперед\назад пошагово. интересный проект.
  • И моей жене, за то что была рядом со мной в этом бою
    yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
Автор: swagcat228
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.