+ Reply to Thread
Results 1 to 1 of 1

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

  1. #1

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

    1. Описание

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

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

    * IDA Pro (64-bit)

    2. Разбор

    Первым делом узнаем, с чем мы имеем дело.
    Code:
    [~/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.
    Code:
    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.
    Получаем
    Code:
    scanf("%10s", input_str);
    Далее мы видим использование механизмов VEH и SEH. Рассмотрим вызов AddVectoredExceptionHandler (см также VEH, SEH).

    Code:
    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'

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



    P.s. В непропатченном исполняемом файле из-за множества вызовов Sleep придется ждать, чтобы увидеть результат.
    Attached Files
    Last edited by mashtyx; 15-12-2017 at 20:28.

  2. 6 пользователя(ей) сказали cпасибо:
    Dark Koder (16-12-2017) Darwin (16-12-2017) Fa1RLiGHT (16-12-2017) FlatL1ne (21-12-2017) petrucho (16-12-2017) ximera (16-12-2017)
+ Reply to Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 14:46
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org