Я клянусь своей честью, что я не оказывал и не получал никакой несанкционированной помощи в выполнении этого задания. — Ян Далтон
Часть 1
Файл: onebyone
Я начал свой анализ этой функции с запуска строк с помощью grep на выходе для поиска флага с заданным форматом. Я не нашел ничего, кроме строки с ошибкой использования «Usage: onebyone ‹flag›».
Я перешел к дизассемблированию файла с objdump
. Мне удалось собрать некоторую информацию, но она выглядела не очень хорошо, и я знаю, что есть лучшие инструменты, которые я могу использовать, чтобы лучше отображать путь выполнения программы и делать скриншоты более крутыми. Я скачал бесплатную версию Binary Ninja, которая позволяет удобно дизассемблировать 32-битные исполняемые файлы x86.
Как только я открыл onebyone в Binary Ninja, я смог увидеть множество сравнений в регистре al
с переходами сразу после этого в раздел кода, который печатает «нет, извините». Принимая это во внимание вместе со строкой ошибки использования, которую я нашел ранее, я понял, что функция проверяла каждый байт параметра на соответствие байтам флага, жестко запрограммированным в исполняемом файле.
Я записал все жестко закодированные шестнадцатеричные значения, которые используются в cmp
инструкциях. Я распознал символы ASCII и перечислил их ниже в том порядке, в котором выполняются cmp
instructions:
hex: 0x30,0x65,0x63,0x33,0x7d,0x2d,0x39,0x6e,0x69,0x52,0x7b ASCII: 0ec3}-9niR{
Буквы кажутся не по порядку. Я еще раз взглянул на дизассемблирование и увидел, что перед инструкциями cmp
исполняемый файл добавляет непосредственное значение в регистр eax
. Я предположил, что это индекс в строке. Я подтвердил это, найдя одну инструкцию cmp
, в которой не было добавлено смещение, и инструкцию со смещением, равным 1. Соответствующие шестнадцатеричные значения, используемые в инструкции cmp
, соответствовали символам «3» и «8», первым двум символам в формате флага.
используя эти знания, я переставил символы на основе индекса, предоставленного смещением, добавленным в сборку, чтобы получить следующий флаг:
389R-{n0ice}
Затем я запустил программу, чтобы проверить свою работу:
Файл: stackexchange
Чтобы запустить этот файл, я загрузил его в Binary Ninja. На первый взгляд я не увидел никаких функций вывода и не увидел никаких инструкций, в которых команда сравнивала значения. Я пришел к выводу, что программа не принимала/использовала какой-либо внешний ввод, как в предыдущем файле. Приглядевшись повнимательнее, я заметил, что функция много манипулирует данными, выделяя место для локальной переменной, устанавливая ее в ноль и загружая значения в локальную переменную.
Я подумал, что будет проще просто открыть программу в отладчике, дать ей запуститься и проверить стек и локальные переменные после завершения основной функции, но до ее выхода.
Как только я открыл программу в gdb (и нашел шпаргалку), я набрал start
, чтобы начать выполнение, а затем s
, чтобы пройти через функцию. Поскольку функция не была написана на C, команда s выполнила все основные, а последующая команда s
завершила бы программу, поэтому я начал печатать значения из стека с помощью таких команд:
x/100xw $esp-50 info registers info locals backtrace
Я не нашел ничего ценного, поэтому я еще раз посмотрел на инструкции. Я решил, что мне нужно пошагово выполнять инструкцию за инструкцией с помощью команды si
и чаще смотреть на кадр стека, чтобы лучше понять, что делает программа.
Я сделал скриншот своего рабочего процесса, чтобы продемонстрировать, что я делаю.
Я знал, что локальная переменная будет сразу после esp
, поэтому после каждой инструкции я печатал десять 32-битных слов, начиная с регистра esp
с помощью команды x/10 $esp
gdb.
Все это время я ищу значения ASCII, соответствующие формату флага, описанному в вызове, в частности, 0x33
, 0x38
и 0x39
, которые соответствуют «389».
Пройдясь по всем инструкциям, остановившись на инструкции leave
, я увидел во фрейме стека шестнадцатеричные значения, которые соответствовали флагу:
Значения стека имеют прямой порядок следования байтов, поэтому для получения ASCII требуется небольшая перестановка.
0x33000000 -> "3" 0x2d523938 -> "89R-" 0x7d65797b -> "{ye}"
Сдача флага:
389R-{ye}
Примечание:
Я хотел упомянуть, что Binary Ninja выполняет операцию xor
за вас. Если бы я навел курсор на «var_#» на снимке экрана Binary Ninja, я бы увидел результирующее шестнадцатеричное значение и таким образом смог бы обнаружить строку. Однако я не осознавал этого до тех пор, пока не нашел строку с gdb (и не сделал запись).
Часть 2
Цель этой задачи — изменить значение логической переменной valid
в программе outofbounds.c
, не зная пароля. Мы можем добиться этого, используя уязвимость переполнения буфера, которая есть в программе.
Программа outofbounds.c
имеет символьный буфер buffer[BUFFER_SIZE]
размером 21 байт, однако она имеет вызов чтения, который считывает 22 байта из STDIN в buffer
, что на один байт больше выделенного размера.
Дополнительный байт, который считывается, затирает первый байт в стеке после buffer
. Для удобства другой локальной переменной в функции main
является valid
char длиной один байт. Из-за того, как будет настроен стек, этот лишний байт будет затирать значение valid
в стеке.
Вот примерный рисунок, показывающий расположение стека при выполнении команды чтения:
Оператор «if», который позже проверяет valid
char для «Аутентификации», не проверяет, является ли char определенным значением, а только проверяет, не равно ли это значение нулю. Таким образом, пока значение не перезаписывается нулем, мы должны иметь возможность заставить программу печатать флаг, переполняя буфер избыточным ненулевым вводом. Я делаю это из терминала Kali ниже, используя только кучу «1».
Бинго, распечатанный флаг:
CMSC389R-{wat_r_u_doing}