Как я простенький keygen писал

в 7:04, , рубрики: c++, crackme, crackmes.de, hacking, ассемблер, Динамический Анализ, крякми, Отладчик

Всем привет, это моя первая статья подобного рода(да и в целом любого рода), в которой я покажу как писал keygen для простенького приложения. Я не уверен, что эти статьи найдут отклик у кого‑нибудь, но, энивей, попробовать стоит. Если зайдет — это может превратиться в серию статей с повышающимся уровнем сложности реверса, но это в мечтах, а пока к сути.

Крякми абсолютно рандомная со всеми любимого сайта crackmes.one. Сложность 1.7 — самое то для начала.

Как я простенький keygen писал - 1

Итак, при распаковке архива в глаза сразу бросается, что тут 3 файла, вместо привычной одной exe'шки. Расширение на файле DIZ значит, что это просто текстовый файл с кратким описанием zip'ки. В нем не было ничего интересного, в отличие от третьего файла ICU.nfo, в котором было... Чудо‑юдо с заставки и информация о создателях, которая заставила меня лазать по форумам и узнавать, как всё это было устроено в далеком 2005–2006-м годах... Я аж прослезился(нет, серьезно), когда находишь один хакерский форум, с него ссылки на множество других, и по какой ты ни переходи, все они находятся в архивах истории интернета...

Как я простенький keygen писал - 2

Энивей, сразу предполагаю, что тут будет что‑то интереснее обычного консольного приложения, и оказываюсь прав, вдобавок к этому получаю привет из нулевых с самой ужасной музыкой, что я когда‑либо слышал, так что поспешно выключаю приложению звук в микшере громкости винды.

Как я простенький keygen писал - 3

Приложение запрашивает логин и какой‑то регистрационный код, что ж, почему бы ей их не дать. Ввожу логин qwerty и пароль 123 456 и получаю сообщение об ошибке, видимо, так легко не получится:(

Как я простенький keygen писал - 4

Судя по давности создания крякми(аж 2 006 год), можно смело открывать программу через дебаггер x32 и сразу пролистать все инициализации, запуск музыки и тд до упора.

Как я простенький keygen писал - 5

Жмем пкм в любое место главного окна → поиск в → все модули → ссылки на строки и сразу натыкаемся на все уже знакомые нам строки.

Как я простенький keygen писал - 6

Можно предположить, что генерация пароля происходит после проверки на длину никнейма. Перейдя по адресу строки «Usename must have at least 4 chars», замечаю ниже по коду интересный прыжок обратно, что свидетельствует о цикле(for или while, неважно, ведь в ассемблере они все выглядят одинаково), если вкратце, то код не знает, что он цикл, он просто идет‑идет, доходит до проверки, в данном случае jl (jump if less / перейти, если значение меньше), как именно оно сравнивается, поясню чуть позже.

Как я простенький keygen писал - 7

Поставив точку останова (breakpoint) перед началом цикла, ввожу логин qwerty и пароль 123 456 и попадаю в цикл. Теперь можно подробно рассмотреть каждую строчку кода здесь, благо, тут их не так много.

  1. mov eax, esi — копирует значение из регистра esi в регистр eax, далее по коду видно, что в конце цикла регистр esi инкрементируеся, значит, это счетчик.

    Как я простенький keygen писал - 8
  2. push 19 — пушит (сохраняет) в стек значение 19. И тут я при написании генератора ключа попался, я забыл, что 19 — это значение в шестнадцатеричной системе счисления, так что не совершайте моих ошибок и не тратьте пол часа на выяснение, а почему оно генерирует неправильный ключ.

  3. А тут начинается деление, никогда не любил его в ассемблере. Связка cdq — idiv используется для знакового 32-ух битного деления. Команда cdq подготавливает регистры для idiv. Idiv всегда делит 64-х битное число на 32-х битное, но один регистр вмещает только 32 бита, поэтому надо использовать два регистра. И исторически, 64-х битное число состоит из регистра eax и edx, нам не всегда нужен второй регистр, поэтому зачастую он забит мусором, и команда cdq очищает старший регистр(edx), чтобы при делении не получился мусор. А дальше, как на математике в 6-м классе: в eax кладется частное, а в edx остаток от деления.

  4. mov eax, esi — после деления, в регистре eax осталось частное от деления, если оно нам не надо, то мы можем просто заменить его значение на другое, как тут и делается

  5. pop ebx — значение из верхушки стека перемещается в указанный регистр

  6. lea ecx,dword ptr ds:[edi+edx] — берет адрес, записанный в edi(сейчас там находится строка «_r <()<1-Z2[l5,^», добавляет к ней edx и возвращает полученный адрес. В edi лежит адрес начала строки, добавляя к нему числа 1,2,3... компьютер понимает, что надо взять начало какой‑то структуры данных, и от него посчитать смещение на какое‑то число. +1 добавит к смещению 1 байт, и так как это ascii‑строка, то это всё равно, что отрезать от строки первый символ и вернуть строку без него.

  7. Далее идет деление на адрес 406 B98, у меня в дампе памяти по этому адресу лежит число 6, полагаю, это длина введенного логина, чтобы гонять его по кругу, если он короче магического слова.

    Как я простенький keygen писал - 9

    Кстати про магическое слово. Прогоняя цикл пару раз, можно заметить, что со временем символы в этой магической и непонятной строке «_r <()<1-Z2[l5,^» превращаются в более осмысленные и похожие на ключ. То есть дальше по коду наш логин посимвольно выполняет какую‑то операцию с этим магическим числом и получается пароль.

  8. Следущими двумя строчками lea eax,dword ptr ds:[406384] movzx eax,byte ptr ds:[eax+edx] в регистр eax берется n‑ая по счету буква логина, видимо, мы уже очень близки к основной операции этого алгоритма.

  9. movzx edx,byte ptr ds:[ecx] берет байт, или же символ, из «магического числа»

  10. И вот оно, командой xor eax, edx, они, как не сложно угадать, ксорятся между собой. Кто не знает, xor — операция исключающего ИЛИ

    Как я простенький keygen писал - 10
  11. У меня сейчас идет вторая итерация, так что xor происходит между символами 'w' и 'r', но как же мы можем ксорить символы, это же не цифры...

    Наш компьютер понимает только 0 и 1, биты. Так что все символы в нем закодированы с помощью 0 и 1, и поможет понять нам, какой код соответствует какой букве — ASCII‑таблица. В ней строчной букве w соответствует десятичный код 119, а для r — 114. Осталось любым сайтом (или на листочке) перевести всё это в двоичную систему счисления. Для 119 получилось 01110111, а для 114 — 01110010.

    Теперь можно делать xor двух этих чисел, тут, опять же, можно зайти на сайт, либо посчитать в уме, но так как я ленивый, то открываю первый же сайт и считаю на калькуляторе

    Как я простенький keygen писал - 11

    Значит, в eax сейчас должно быть значение 101 в шестнадцатеричной, то есть 5, проверяем

    Как я простенький keygen писал - 12
  12. Далее идет деление на ebx, в котором, спойлер, постоянно лежит 0×19, и мы опять берем только остаток от деления.

  13. В самом конце добавляем в dl 0×41, окончательно получая часть пароля. Для нашей пятерки это будет 0×46, или же символ 'F' в ASCII. Потом увеличиваем счётчик на единицу, делаем cmp и всё по новой.

    Как и обещал, немного инфы про cmp a, b. Сама по себе, команда не влияет ни на регистры, ни на что, кроме одного места — регистра флагов, он представляет из себя... регистр, где каждый бит отвечает за свой флаг. Я не буду рассказывать про все флаги, расскажу только про те, что нас сейчас интересуют. Чтобы понять, что это за два числа, cmp берет и вычитает одно число из другого, и уже от результата операции поднимает или опускает нужные флаги. Команда JL (jump if less, если кто забыл), проверяет сразу два флага: флаг знака(SF, sign flag) и флаг переполнения(OF, overflow flag). Переход произойдет, если OF!= SF, то есть если один из них равен нулю, а второй единице — то переходу быть.

Теперь у нас есть вся нужная информация для написания keygen'а. Я пишу на с++.

#include <iostream>

using namespace std;

int main()
{
    string login;
    string secret = "_r <()<1-Z2[l5,^";
    string answer;
    int hex_salt = 0x41;
    cin >> login;
    //------------------------
    for (int i = 0; i <= secret.length() - 1; i++) {
        if (i != 0 && i % 4 == 0) 
            answer += '-';
      
        char symbol = login[i % login.length()];
        int result =  symbol xor secret[i];
      
        result = result % 0x19;
        result += hex_salt;
        answer += result;
    }
    cout << answer;
}

Код максимально простой, до 11-й строки выделение места под переменные и инициализация некоторых из них. Потом ввод логина и цикл, по длине равный нашей магической строке, if ставит '‑' через каждые 4 символа.

Потом берется один символ и ксорится с соответствующим символом из магической строки, после чего результат делится с остатком на 0×19 и к нему добавляется 0×41. Вау‑ля, keygen готов, осталось его проверить.

Как я простенький keygen писал - 13
Как я простенький keygen писал - 14

Вот и всё, задача решена.

Фух. Надеюсь, это не было слишком нудно (я старался, правда, даже где‑то опустил моменты углубления в теорию), и в будущем поможет какому‑нибудь новичку, как я, начать свой путь в сей прекрасный мир реверса.

Автор: keferr

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js