Разбор заданий по реверсу с DC78422 CTF0 (часть 2)
Данное описание подготовлено и публикуется со слов участника CTF, занявшего почетное второе место – Сементинова Сергея ( @cema_rus ). Мы разбили прохождение реверста на несколько статей потому что они достаточно обширны и подробны, это уже вторая часть, первая часть разбором с заданиями Task1.exe, Task2.exe, Task3.exe, taskSyzran.exe уже доступна, как и третья часть разбора с финальным заланием Final.exe. Дальше текст передается as-is.
Deep.exe
Предпоследнее задание. Уже больше похожее на настоящий реверс, со всем плюшками в виде: разобрать код, понять алгоритм и сделать генератор ключей. Ну что) В добрый путь…
Сначала планировалось выполнять это задание с применением все тех же IDR и IDA, но в IDA при декомпиляции вылазила ошибка и, честно сказать, было лень с ней разбираться - было решено использовать “новый для себя” инструмент - Ghidra (https://github.com/NationalSecurityAgency/ghidra/releases).
Качаем, запускаем, создаем проект, добавляем приложение:
По двойному щелчку на приложении запускается окно CodeBrowser и, при первом запуске, предлагается провести анализ кода, соглашаемся на всё, жмём на кнопку Analyze и дожидаемся окончания анализа.
Теперь имеем код и давайте его разбирать, попутно переименовывая различные переменные и функции для удобного понимания:
Как обычно, начинаем с условия, когда флаг считается правильным. Переменная DAT_0040a800
должна, после всех вычислений, иметь значение равное 100. В самой функции не видно объявления этой переменной, только начальная инициализация. Скорее всего, эта переменная глобальная (в чём мы убедимся позже). Переименовываем и идём дальше.
При дальнейшем изучении понимаем, что функция FUN_00403eb4 возвращает длину строки, а переменная DAT_0040a798
- это введенный нами влаг. На и тело условия, при помощи логики и фантазии, приводим примерно к такому виду:
На этом этапе, уже у многих будет общее понимание алгоритма проверки. А за деталями пойдём по очереди в fInputCharLeft
, fInputCharRight
, fInputCharUp
и fInputCharDown
.
Все 4 функции выглядят примерно одинаково, они отличается битами, наличие которых проверяется в условии и значением, которое суммируется с нашим счетчиком.
Непонятным остаётся только один момент: Что это за условие такое в функциях?! Какое отношение оно имеет к введенному нами флагу?! Ерунда какая-то, которая заставила меня изрядно поломать голову… И, самые внимательные, уже обратили внимание на какую-то странную + 3
в условии.
Продолжаем смотреть другие функции, в надежде что придёт озарение и… Натыкаемся на это:
Некая функция с инициализацией некоего массива и, при детальном рассмотрении, видим, что наш массив сидит по адресу 0x0040a79c
, а указатель на введенную нами строку по адресу 0x0040a798
:
Немного поразмыслив, понимаем, что конструкция вида:
*(byte *)((int)&sInputFlag + iCheckingCounter + 3)
означает не обращение к нашей строке, а обращение к этому массиву, т.к. iCheckCounter
не должен быть меньше 1, а общая сумма (0x0040a798 + 1 + 3 == 0x0040a79c)
равна адресу нашего массива. В общем… Декомпилятор немного неправильно распознал конструкцию, но оттого стало только интереснее)
Что получаем в итоге? Алгоритм проверки флага:
При запуске iCheckCounter = 1
;
- Проверяем все входные условия, в виде фигурных скобок, длины и прочего;
- Если символ из набора “направлений движения”, то проверяем содержимое элемента массива
someArray[iCheckCounter - 1]
(т.к. у нас индексация начинается с 0) и если установлен бит, соответствующий направлению, идём дальше. - Так двигаемся до тех пор, пока
iCheckCounter
не достигнет значения 100;
Алгоритм получили, давайте “накидаем” генератор флагов:
Ограничение на длину флага установил в 20 символов, мне показалось, что этого будет достаточно. Запускаем, смотрим результат:
Выбираем самый короткий, проверяем в программе и на сайте - Correct!
Файлы заданий