Брутим crackme#03 от korsader

от автора

Вступление

У меня нет опыта в реверсе, есть некие базовые знания ассемблера, позволяющие сделать битхак условных переходов, чтобы данное приложение всегда выходило на success-path независимо от введенного значения. Но так как это слишком банально для того, чтобы быть опубликованным здесь, а до полного воспроизведения алгоритма проверки введенного ключа я еще не дорос, я решил попробовать метод брута, т.е. последовательного подбора ключей. Конечно же, я не могу сказать, что и этот способ отличается своей крутизной, но по крайней мере подробное описание этого способа не так часто встречается среди статей по реверсу.

image

Источник

Ссылка на оригинальный пост crackme#03.

В ветке, где опубликован этот крякми, пользователь ARCHANGEL опубликовал метод брута на C++, который действительно выдает правильный пароль для этого крякми. Он мог позволить себе написать брут на языке высокого уровня, так как смог воспроизвести алгоритм и нашел значение 0x23a06032, с которым сравнивается полученное crc.

Я же пока не научился так глубоко анализировать алгоритмы на asm-е, поэтому буду работать не отходя от кассы, используя алгоритм проверки в самом крякми как черный ящик.

Анализ условных переходов

Запускаем крякми и выключаем звук на компьютере (видимо именно этого добивался автор, добавив звуковое сопровождение).

image

Запускаю OllyDbg 2.01 c плагином OllyExt, в котором выставлены приведенные ниже настройки:

image

Этот плагин поможет избавиться от некоторых потенциальных приемов анти-отладки.

Аттачимся из Ольги к выполняемому процессу crackme#03.exe: File -> Attach…

image

Далее пробуем ввести любое значение в поле ввода и без труда находим код, в котором оно обрабатывается. Сделать это можно выставив breakpoint-ы на потенциальные функции получения текста (в частности GetDlgItemTextA) или пролистать код исходного модуля — благо здесь он небольшой. Нажимаем кнопку Check.

image

Срабатывает breakpoint, жмем один раз F8 и смотрим листинг. Видим, что введенная нами строка хранится по адресу 004095BC. Также смотрим на инструкцию по адресу 0040101F. Она сравнивает длину введенной строки со значением 12 и в случае неравенства выбрасывает нас на 004010AF.
Далее, обратите внимание на инструкцию по адресу 00401028, которая сравнивает 12-ый байт введенного значения со значением 72, а это буква r в ASCII-кодировке. В случае неравенства снова выбрасывает нас на 004010AF. Что же это за адрес такой? Об этом чуть позже.
Теперь обратим внимание на инструкции в диапазоне 004010310040104C. Покурив справочник команд ассемблера по командам repne и scas, а также потрассировав выделенную область кода, приходим к выводу, что aehnprwy — это допустимый алфавит требуемого ключа.

image

Теперь посмотрим, что же у нас расположено по адресу 004010AF. Там вывод сообщения о неудачном вводе пароля.

image

Итак, подведем итоги первичного анализа:
1) Ключ должен состоять из 12 символов;
2) Последний символ ключа должен быть r;
3) Символы ключа должны принадлежать алфавиту aehnprwy.

Брутфорс

Приступим к реализации брут-метода. Реализуем его сначала на концептуальном уровне. Можно на блок-схеме, можно на языке высокого уровня. Я реализовал его прототип на C++. Для понимания дальнейшего материала необходимо переварить код под спойлером.

Скрытый текст

#include <stdio.h> #include <string.h>     const char *Alphabet = "aeyhpnwr"; int         Password_len = 12;  int CheckPassword(char pass[]) // функция проверки введенного пароля. {     if (strcmp(pass, "aaaaaaahaahr") == 0)  // проверка на заглушку         return 0;          return 1;     }  int main() {     int Alphabet_len    = strlen(Alphabet);         char* CodeArray     = new char[Password_len + 1];     char* Password      = new char[Password_len + 1];              int n;      // Инициализируем пароль, чтобы все символы были равны первому (т.е. нулевому) символу алфавита     Password[Password_len] = 0;     for (int i = 0; i < Password_len; i++)     {         CodeArray[i] = 0;         Password[i]  = Alphabet[CodeArray[i]];     }          // последний символ всегда r     Password[Password_len - 1] = 'r';          while (true)     {         //printf("\nCurrent pass = %s", Password);          if (CheckPassword(Password) == 0)         {             printf("\nRight Password = %s\n", Password);             break;         }          n = Password_len - 2;         while (n >= 0)         {             CodeArray[n]++;             if (CodeArray[n] >= Alphabet_len)             {                 CodeArray[n] = 0;                 Password[n] = Alphabet[0];                 n--;                 continue;             }             Password[n] = Alphabet[CodeArray[n]];             break;         }     }      delete[] CodeArray;     delete[] Password; }

Основные этапы:
1) Инициализация ключа (пароля) начальным значением.
2) Проверка ключа.
3) Если ключ неверен, то генерим следующий ключ и переходим к шагу 2. Если ключ верный, переходим к шагу 4. Если мы перебрали все ключи и ни один из них не подошел, переходим к шагу 5.
4) Crackme взломан.
5) Crackme не взломан. Где-то мы ошиблись.

Инициализация ключа

Программа хранит и обращается к ключу по адресу 004095BC — там и будем его инициализировать. Алфавит расположен по адресу 00409071.
004095CC — это адрес памяти, где мы будем хранить наш массив CodeArray.

Для инициализации ключа находим неиспользуемую область секции кода и вставляем туда наши инструкции. Я выбрал адрес 00404122.
Чтобы собственно эта инициализация выполнилась, идем к тому месту, где вызывается GetDlgItemTextA, аккуратно заменяем ее вызов на безусловный переход к нашему коду по адресу 00404122, а предшествующие ей push-команды заменяем nop-ами. Все делаем аккуратно, чтобы адрес команды CMP EAX, 0C остался прежним: 0040101F.

image

На рисунке ниже представлен сам код инициализации. По ее окончанию переходим к алгоритму валидации ключа в результате безусловного перехода на 0040101F.

image

Скрытый текст

Обнулить массив CodeArray, состоящего из 12 однобайтовых значений, можно в 3 итерации по 4 байта за итерацию.

00404122      B9 03000000   MOV ECX,3 00404127      BA CC954000   MOV EDX,004095CC                   ; адрес массива CodeArray 0040412C      C7448A FC 000 MOV DWORD PTR DS:[ECX*4+EDX-4],0 00404134      E2 F6         LOOP SHORT 0040412C 

Далее выполняем проставление первого (т.е. нулевого) символа алфавита для каждого байта строки по адресу 004095BC

 for (int i = 0; i < Password_len; i++) { 	Password[i]  = Alphabet[CodeArray[i]]; }

00404136      BB BC954000   MOV EBX,004095BC                ; Адрес введенного ключа, к которому обращается crackme                      0040413B      B9 0C000000   MOV ECX,0C 00404140      31C0          XOR EAX,EAX 00404142      8A4411 FF     MOV AL,BYTE PTR DS:[EDX+ECX-1] 00404146      8A80 71904000 MOV AL,BYTE PTR DS:[EAX+409071] ; 409071 - адрес алфавита              0040414C      884419 FF     MOV BYTE PTR DS:[EBX+ECX-1],AL   00404150      E2 F0         LOOP SHORT 00404142 00404152      C643 0B 72    MOV BYTE PTR DS:[EBX+0B],72     ; Последний символ всегда r 00404156      C643 0C 00    MOV BYTE PTR DS:[EBX+0C],0      ; Закрываем строку нулевым символом 0040415A      B8 0C000000   MOV EAX,0C                      ; Обозначаем длину ключа как 12 0040415F    ^ E9 BFCEFFFF   JMP 0040101F                    ; Возврат к коду валидации ключа 

Получение следующего значения ключа

Теперь нам нужно реализовать код получения следующего значения ключа. Выбираем для этого адрес 00404165 (сразу после кода инициализации) и делаем переход на него с того места, где программа обзывает нас ламерами.
Модифицируем инструкции по адресам 004010AF и 004010B1.

image

Ну и по адресу 00404165 реализуем код получения нового ключа.

image

Скрытый текст

00404165    B9 0B000000     MOV ECX,0B                      ; Количество символов ключа, которые подлежат изменению 0040416A    BA CC954000     MOV EDX,004095CC                ; Адрес массива CodeArray 0040416F    BB BC954000     MOV EBX,004095BC                ; Адрес введенного ключа, к которому обращается crackme   00404174    31C0            XOR EAX,EAX                      00404176    FE4411 FF       INC BYTE PTR DS:[EDX+ECX-1]     ; Получаем следующий порядковый номер символа в алфавите 0040417A    807C11 FF 08    CMP BYTE PTR DS:[EDX+ECX-1],8   ; Проверяем, не вышли ли за пределы алфавита 0040417F    7D 15           JGE SHORT 00404196               00404181    8A4411 FF       MOV AL,BYTE PTR DS:[EDX+ECX-1]  ; Если не вышли, грузим номер в AL 00404185    8A80 71904000   MOV AL,BYTE PTR DS:[EAX+409071] ; Получаем символ из алфавита по порядковому номеру. 409071 - адрес алфавита  0040418B    884419 FF       MOV BYTE PTR DS:[EBX+ECX-1],AL  ; Записываем букву в соотв-ий символ ключа 0040418F    B8 0C000000     MOV EAX,0C                      ; Обозначаем длину ключа как 12 00404194  ^ EB C9           JMP SHORT 0040415F              ; Прыгаем на код валидации ключа 00404196    83F9 01         CMP ECX,1                       ; Если вышли за пределы алфавита, смотрим, не первый ли это символ ключа 00404199    7F 02           JG SHORT 0040419D               ;  0040419B    CD 03           INT 3                           ; Перебор закончен, правильный ключ не найден 0040419D    C64411 FF 00    MOV BYTE PTR DS:[EDX+ECX-1],0   ; Если ecx (i) > 1, CodeArray[i] = 0; 004041A2    A0 71904000     MOV AL,BYTE PTR DS:[409071]     ; В AL записываем первый (нулевой) символ алфавита. 409071 - адрес алфавита  004041A7    884419 FF       MOV BYTE PTR DS:[EBX+ECX-1],AL  ; Записываем букву в соотв-ий символ ключа 004041AB    49              DEC ECX                         ; Переходим к след. элементу 004041AC  ^ EB C6           JMP SHORT 00404174              ; Повторяем итерацию для следующего элемента 

Запуск

Жмем кнопку Check и идем пить чай. Черт, и чай-то давно уж вскипел, придется кипятить заново.

image

Через некоторое время получаем:

image

Смотрим, что же у нас по адресу 004095BC

image

happynewyear — это и есть искомый ключ.

P. S. Текущая статья также была опубликована мною (правда под другим ником) на reverse4you.org. Если это каким-то образом нарушает правила текущего портала, то я совершенно нормально отнесусь к тому, что модераторы хабра удалят текущую статью.
ссылка на оригинал статьи https://habrahabr.ru/post/324884/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *