Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
PWN — одна из наиболее самодостаточных категорий тасков на CTF-соревнованиях. Такие задания быстро готовят к анализу кода в боевых условиях, а райтапы по ним чаще всего описывают каждую деталь, даже если она уже была многократно описана. Мы рассмотрим таск Useless Crap с апрельского TG:HACK 2020. Сам автор оценил его сложность как Hard. Задание очень интересное, во время соревнования я потратил на него около двенадцати часов.
Для начала расскажу об инструментах, которые я использовал.
Я предпочел не выбирать однозначно между IDA и Ghidra и использую один или другой дизассемблер в зависимости от ситуации, но в тасках категории PWN хороший псевдокод чаще выдает IDA.
«Ванильный» GDB невозможно использовать без очень серьезной психологической подготовки, так что чаще всего его юзают в сочетании с одним из плагинов: PEDA, GEF или pwndbg. Из них PEDA — самый старый (классический!) вариант, но я до сих пор не переехал на один из новых, так что использую его.
Также, пока весь мир полностью переезжает на Python 3, разработчики эксплоитов и не думают о том, чтобы покидать любимый Python 2. Дело в очень неприятной обработке raw bytes в третьей ветке Python, приходится постоянно держать в голове ее особенности и тратить лишнее время на исправление возникающих багов.
Полезные дополнительные инструменты:
Итак, организаторы дали нам бинарник и файлы серверной libc и линковщика. Также точно указан путь до флага; опытные игроки в CTF сразу могут предположить, что придется писать свой шелл-код.
Очевидно, самое первое, что нужно сделать, — это просто выполнить бинарник и примерно оценить сложность, быстренько просмотрев security mitigations в checksec.
Чтобы исполняемый файл использовал нужную libc, пропатчим в нем путь до линкера и укажем ее в переменной окружения LD_PRELOAD
.
patchelf --set-interpreter ld-2.31.so ./crap
LD_PRELOAD=./libc-2.31.so ./crap
Нас встречает незамысловатая менюшка, появляется надежда на быстрое и простое решение. Живет эта надежда, правда, недолго, примерно до открытия checksec.
У нас включены на максимум все защитные механизмы. Вот их краткое описание.
NX — делает стек неисполняемым. Около двадцати лет назад большинство уязвимостей переполнения буфера эксплуатировали запись шелл-кода на стек с последующим прыжком на него. NX делает такую технику невозможной, однако сейчас она еще жива в мире IoT.
Stack canary — определенное секретное значение на стеке, записанное перед RBP и return pointer и, таким образом, защищающее их от перезаписи через уязвимость переполнения буфера.
Full RELRO — делает сегмент GOT доступным только для чтения и размещает его перед сегментом BSS. Техники эксплуатации через перезапись GOT не сложны, но выходят за рамки этой статьи, так что предлагаю читателю самому разобраться с ними. О том, что такое Global Offset Table, можно прочитать, например, в Википедии.
ASLR — это защитный механизм, который значительно усложняет эксплуатацию. Его основная задача — рандомизация базовых адресов всех регионов памяти, кроме секций, принадлежащих самому бинарнику.
По сути, ASLR работает следующим образом. В диапазоне адресов, который на несколько порядков превышает размер рандомизируемого региона памяти, выбирается начальная точка отсчета, базовый адрес. К нему есть два требования:
Главная проблема атакующего в том, что это действительно работает. Можно взять конкретный пример: нужно найти адрес функции system
в libc, при этом никакой информации о нем не известно. Давай примерно представим, сколько времени на это понадобится. Первый байт любого адреса библиотеки почти обязан быть равен 0x7f. Последние три ниббла мы знаем, так как независимо от выбора базового адреса они остаются теми же при каждом запуске программы. Достаточно несложная школьная задачка по комбинаторике:
2^8 * 2^8 * 2^8 * 2^4 = 2^28 = 268435456
Это примерная оценка, так как не учитывается определенное количество адресов вверху диапазона, которые брать нельзя, иначе остальной регион памяти тогда не уместится; тем не менее она достаточно точная. Допустим, на каждый запуск эксплоита в среднем мы тратим три секунды. Тогда полный перебор займет примерно 25 лет, что нас явно не устраивает, ведь CTF идет всего 48 часов.
Ну и наконец, PIE — это, по сути, ASLR для сегментов памяти самого исполняемого файла. В отличие от базового ASLR, который работает на уровне ОС, PIE — это опциональный защитный механизм, он может и не присутствовать в бинарнике.
Есть легенда, что если реверсера разбудить среди ночи и дать ноутбук, то он первым делом откроет IDA и нажмет F5. Не знаю, насколько это правда, но всегда поступаю именно так, когда хочу разобраться, как работает неизвестный исполняемый файл.
Нам повезло, декомпилированный псевдокод выглядит приятно и легко читается, так что больших проблем с пониманием механизмов исполняемого файла быть не должно.
Рассмотрим функции по порядку.
Здесь нет ничего по-настоящему интересного, просто отключается буферизация ввода и вывода и устанавливается время работы программы (после 0x3c секунд произойдет прыжок на handler
, функцию, которая состоит из одной строки: exit(0);
).
Эта функция намного интереснее предыдущей: оказывается, в программе достаточно жестко настроен seccomp. Давай разберемся, какие системные вызовы разрешены. Найти таблицу соответствий названий сисколлов с их номерами не представляет труда.
Итак, абсолютно точно разрешены exit
, mprotect
, open
и close
. Немного, но уже становится понятен финальный этап эксплуатации: нужно будет сделать один из регионов памяти доступным для чтения, записи и исполнения, записать туда шелл-код на чтение файла с флагом и прыгнуть на него.
Также доступны системные вызовы read
и write
, но не полностью. IDA не показывает аргументы seccomp_rule_add
после четвертого, а ведь основные правила настройки для заданных сисколлов именно там. Нажав правой кнопкой мыши на название функции, можно выбрать опцию Set call type и, таким образом, дописать еще несколько __int64
, чтобы увидеть больше аргументов.
По опыту работы с seccomp могу сказать, что IDA не совсем правильно определила седьмой аргумент, который равен SCMP_CMP_EQ
(это 4), но становится ясно, что программа может читать только из нулевого дескриптора (stdin
), а писать только в первый дескриптор (stdout
). Пока что не совсем понятно, как тогда написать шелл-код, ведь читать нужно в любом случае из дескриптора открытого файла, который точно не равен нулю. Но об этом позже.
Это меню, которое выводится каждую итерацию цикла в main.
Получение номера выбранной функции происходит безопасно, здесь нет ничего интересного для нас.
Автор таска предоставляет нам чистый arbitrary read. Не могу сказать, что это очень редкое и уникальное решение, но такие задания чаще всего крайне интересны. Мы можем прочитать что угодно откуда угодно не более двух раз, по крайней мере так будет считать программа. Переменная read_count
глобальная, а значит, хранится не на стеке, а в BSS. В дальнейшем понимание этого может облегчить эксплуатацию.
Похожим образом работает do_write
. У нас появляется возможность записывать что угодно куда угодно, пока write_count
меньше единицы или равен ей. Сразу можно придумать обход механизма проверки: после каждой записи через следующий do_write
присваивать write_count
значение -1
, таким образом получить полный, ничем не ограниченный arbitrary write и схожим образом arbitrary read.
Мы еще не успели прочитать весь код, а уже имеем серьезный контроль над потоком выполнения программы.
При выборе третьей опции меню вместо незамедлительного выхода программа попросит оставить обратную связь. Это происходит следующим образом:
Пока что эта ошибка некритична, но в дальнейшем может быть очень полезна.
Чтобы вызвать эту функцию, нужно ввести цифру 4, упоминания о которой нет в меню. Функция view_feedback
выводит то, что находится по указателю feedback
, не проверяя состояние чанка, который может быть освобожден. Такой тип уязвимостей называется Use-After-Free. Подразумевается, что по адресу указателя должен лежать пользовательский ввод, но чуть позже мы увидим, что для освобожденных чанков это не всегда так.
Источник: xakep.ru