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

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

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

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

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

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

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

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

-
push 19 — пушит (сохраняет) в стек значение 19. И тут я при написании генератора ключа попался, я забыл, что 19 — это значение в шестнадцатеричной системе счисления, так что не совершайте моих ошибок и не тратьте пол часа на выяснение, а почему оно генерирует неправильный ключ.
-
А тут начинается деление, никогда не любил его в ассемблере. Связка cdq — idiv используется для знакового 32-ух битного деления. Команда cdq подготавливает регистры для idiv. Idiv всегда делит 64-х битное число на 32-х битное, но один регистр вмещает только 32 бита, поэтому надо использовать два регистра. И исторически, 64-х битное число состоит из регистра eax и edx, нам не всегда нужен второй регистр, поэтому зачастую он забит мусором, и команда cdq очищает старший регистр(edx), чтобы при делении не получился мусор. А дальше, как на математике в 6-м классе: в eax кладется частное, а в edx остаток от деления.
-
mov eax, esi — после деления, в регистре eax осталось частное от деления, если оно нам не надо, то мы можем просто заменить его значение на другое, как тут и делается
-
pop ebx — значение из верхушки стека перемещается в указанный регистр
-
lea ecx,dword ptr ds:[edi+edx] — берет адрес, записанный в edi(сейчас там находится строка «_r <()<1-Z2[l5,^», добавляет к ней edx и возвращает полученный адрес. В edi лежит адрес начала строки, добавляя к нему числа 1,2,3… компьютер понимает, что надо взять начало какой‑то структуры данных, и от него посчитать смещение на какое‑то число. +1 добавит к смещению 1 байт, и так как это ascii‑строка, то это всё равно, что отрезать от строки первый символ и вернуть строку без него.
-
Далее идет деление на адрес 406 B98, у меня в дампе памяти по этому адресу лежит число 6, полагаю, это длина введенного логина, чтобы гонять его по кругу, если он короче магического слова.

Кстати про магическое слово. Прогоняя цикл пару раз, можно заметить, что со временем символы в этой магической и непонятной строке «_r <()<1-Z2[l5,^» превращаются в более осмысленные и похожие на ключ. То есть дальше по коду наш логин посимвольно выполняет какую‑то операцию с этим магическим числом и получается пароль.
-
Следущими двумя строчками lea eax,dword ptr ds:[406384] movzx eax,byte ptr ds:[eax+edx] в регистр eax берется n‑ая по счету буква логина, видимо, мы уже очень близки к основной операции этого алгоритма.
-
movzx edx,byte ptr ds:[ecx] берет байт, или же символ, из «магического числа»
-
И вот оно, командой xor eax, edx, они, как не сложно угадать, ксорятся между собой. Кто не знает, xor — операция исключающего ИЛИ

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

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

-
Далее идет деление на ebx, в котором, спойлер, постоянно лежит 0×19, и мы опять берем только остаток от деления.
-
В самом конце добавляем в 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 готов, осталось его проверить.


Вот и всё, задача решена.
Фух. Надеюсь, это не было слишком нудно (я старался, правда, даже где‑то опустил моменты углубления в теорию), и в будущем поможет какому‑нибудь новичку, как я, начать свой путь в сей прекрасный мир реверса.
ссылка на оригинал статьи https://habr.com/ru/articles/1097676/