R0 CREW

Разбор задачи от petrucho

1. Описание

Задача от petrucho.
Дан файл task_3.elf (см. приложенный файл снизу). Требуется получить такую строку, что при вводе её программа напечатает “Done!”.

Используемое ПО:

  • IDA Pro (64-bit)

2. Разбор

Первым делом узнаем, с чем мы имеем дело.

[~/task_3] > file task_3.elf
task_3.elf: PE32+ executable (console) x86-64, for MS Windows

На самом деле это исполныемый файл для Windows! Меняем расширение на .exe.

Открываем подопытный файл в IDA Pro. Попадаем в функцию main. Заметим, что main не является точкой входа в приложение.

Декомпилируем функцию main и приступип к анализу.

Начнем с вызова функции scanf.

scanf("%10s", &v6, envp);

Мы видим, что ожидается ввод 10 символов. Предполагаем, что длина ключа должна тоже быть 10 символов. Переименуем переменную v6 в input_str и изменим тип на массив из 10 символов. ПКМ по input_str->Set lvar type->char input_str[10]
Возникает вопрос, для чего нужен 3-й аргумент envp в вызове scanf?
На самом деле envp не передается в scanf. То, что мы видим, связано с соглашением о вызове (Microsoft для 64-битных приложений). Первые 4 аргумента передаются через регистры RCX, RDX, R8, R9. Из ассемблерного листинга мы видим, что RDX = &v6, RCX = “%10s”.

А теперь мы посмотрим, где был вызов функции main (жмем x и смотрим xrefs на main)?

Видим, что R8 = envp перед вызовом main, а внутри main регистр R8 нигде не менялся.
Поэтому это было декомпилировано таким образом. ПКМ по scanf->Delete variadic argument.
Получаем

scanf("%10s", input_str);

Далее мы видим использование механизмов VEH и SEH. Рассмотрим вызов AddVectoredExceptionHandler (см также VEH, SEH).

Handle = AddVectoredExceptionHandler(1u, Handler);

В качестве 2-го аргумента передается хэндлер для исключения. Рассмотрим его.

Видим, что по некоторому адресу записывается символ ‘R’. Что это? Часть ключа? Посмотрим xrefs на этот byte_1400F3C2 (жмем x). Предполагаем, что на 5-ом месте (отсчет с 1) в ключе должен быть символ ‘R’. Хэндлер возвращает EXCEPTION_CONTINUE_SEARCH (#define EXCEPTION_CONTINUE_SEARCH 0 - продолжить поиск хэндлера).

Цикл for обернут в __try. Рассмотрим фильтр исключений в блоке __except, который вычисляется при возникновении исключения.

Функция записывает ‘X’ в byte_14000F3C1 - 3-й элемент ключа (отсчет с 1) и возвращает 1, что означает, что исключение распознано и будет выполнен код в блоке __except при возникновении исключения.

Смотрим на подозрительный цикл. Открываем ассемблерный листинг и видим xor ecx, ecx, а следом idiv ecx.

Деление на ноль внутри __try. Сработает исключение (как мы выяснили выше, выполнится код в __except). Заметим, что отладчик может “съедать” исключения и вызов хэндлера может не произойти. Перед нами один из антиотладочных приемов.

Рассмотрим sub_1400014C0. Мы видим еще один прием - проверка отладочных регистров. В byte_14000F3C0 записывается ‘4’. Это 1-ый символ ключа (отсчет с 1). Переименуем фукцию в has_debugger1.

А v8 из main в has_debugger1_v. Аналогично видим другие противоотладочные техники в sub_140001440 и sub_1400013C0. По аналогии выясняем, что byte_14000F3C4 == ‘_’ - 9-ый символ ключа, а byte_14000F3C5 == ‘4’ - 10-ый символ ключа. (отсчет с 1). Переименуем соответственно в has_debugger2 и has_debugger3.

Перейдем к sub_140001000.
В качестве первого аргумента передается адрес main. Изменим тип и имя a1 на char *ptr, а a2 на n.

Происходит xor первых n байт по адресу ptr с числом 0x55. xor(0x55, 0x99) = 0xCC. Но 0xCC - это опкод int3. Эта функция проверяет, установлены ли софтварные точки останова. byte_14000F3C3 = ‘k’ - это 7-ой символ ключа (отсчет с 1).

Проанализируем функцию sub_140001080. Заменяем вызов Sleep нопами.

Он тут ни к чему. Декомпилируем снова. Видим, что выход из цикла for произойдет тогда, когда i = a2. Т.е. Функция вернет a1 - a2. Это очень похоже на сравнение аргументов. Переименуем функцию в not_equals.

Рассмотрим цепочку условий.

3. Заключение

Очевидно, что нам нужно дойти до конца main’а.
Таким образом, чтобы получить валидный ключ, необходимо не иметь софтварных точек останова и присоединенного отладчика.

(отсчет с 1)
2-ой символ ключа должен быть byte_14000E000 == ‘@’
4-й - byte_14000E001 == ‘3’
6-й - byte_14000E002 == ‘4’
8-ой - byte_14000E003 == ‘h’

Из анализа предшествующих функций, мы узнали, что:

1-ый символ - ‘4’
3-ий символ - ‘X’
5-ый символ - ‘R’
7-ой символ - ‘k’
9-ый символ - ‘_’
10-ый символ- ‘4’

Объединяя, получаем ключ:

Hide

4@X3R4kh_4

P.s. В непропатченном исполняемом файле из-за множества вызовов Sleep придется ждать, чтобы увидеть результат.