Crackme (как, впрочем, следует из названия) это программы, созданные специально для того, чтобы их взломать (понять алгоритмы, заложенные разработчиком, и используемые для проверки введенных пользователем паролей, ключей, серийных номеров). Подходы и методики, использованные в статье, ни в коем случае не должны применяться к программному обеспечению, производителями которого подобные действия не одобрены явно (например, в лицензионных соглашениях, договорах, и т.д.). Материал опубликован исключительно в образовательных целях, и не предназначен для получения нелегального доступа к возможностям программного обеспечения в обход механизмов защиты, установленных производителем. Исследуемая crackme является оконным приложением, и написана на assembler. Наша задача состоит в том, чтобы понять алгоритм генерации ключа, найти валидный серийный номер (или номера), и написать кейген. Для анализа и проверки гипотез нам понадобится: 
Запустив crackme мы видим одно окно, с полем для ввода серийного номера, и кнопкой, почему-то называющейся "Generate", и очевидно отвечающей за проверку введенного серийного номера. Что бы я ни вводил в поле — статус по прежнему оставался "Unregistered".

Загрузимся в IDA и попробуем найти код, который отвечает за валидацию серийного номера. Сначала ищем строку Unregistered. Для этого идем в меню View — Open Subviews — Strings, находим строку, и переходим в дизассемблированный код. Далее через меню Jump to xref to operand... переходим к коду, который использует эту строку.   Беглый осмотр показывает, что статус Registered проставляется соседней веткой кода, а также осуществляется цепочка проверок, из которой четыре ветки ведут к Unregistered, и только полное прохождение всех проверок ведет к статусу Registered. Давайте разбираться. Прокрутив граф кода вверх, посмотрим на первый блок кода. Очевидно, что код, с помощью функции GetDlgItemTextA получает значение введенного номера, и, если длина строки равна нулю — перебрасывает нас к статусу Unregistered. Следующий блок кода, выполняющийся в случае, если введено хоть что-нибудь: Этот код вызывает функцию atoi, которая вытаскивает число из строки (если оно там, конечно, есть) и возвращает int (в регистр EAX, значение которого кладется в стек, и регистр обнуляется). Затем, следует еще четыре блока кода. Смысл этого блока в получении некой контрольной суммы введенной строки. Сразу после вызова atoi результат сохранялся в стек, и копировался в регистр ECX. Здесь же каждый байт введенного нами серийного номера суммируется с числом 1E240 (123456 в десятичной системе), и эта сумма добавляется к значению в ECX. Таким образом, блок проходит все байты введенного нами серийного номера, и на выходе мы получаем некую сумму. Главный момент этого блока — инструкция repne scasw, которая осуществляет поиск подстроки во введенном серийном номере. Ее задача найти в строке 2 байта (2 bytes = word, поэтому и scasw) — 4D 43, по кодовой таблице ASCII это соответствует строке "CM". Этот блок достает из стека результат работы функции atoi и сравнивает его с числом 7E7 (2023 в десятичной системе). Последний блок проверки вызывает иструкцию div, деля значение в EAX на ECX. Целая часть помещается в EAX, а остаток в EDX. Инструкция test edx, edx проверяет, что деление прошло без остатка. 
 Что мы имеем в итоге: Строка "2023CM" не является валидным серийным номером, а значит нам немного не хватает символов до прохождения проверки. Теперь понадобится немного поразмыслить о том, как найти валидные серийные номера. Фактически нам надо решить уравнение вида: Однако, хотя (x1 +… + xN + 123456 * N) как-то смахивает на арифметическую прогрессию, и наверное можно было бы решить это математическим методом, мы пойдем по пути перебора. Тут-то нам и поможет Python. Мы точно знаем, что: Для начала найдем все варианты контрольных сумма, которые выше контрольной суммы строки "2023CM" + 123456, меньше 0xFFFFFFFF, делящиеся на 2023 без остатка: Теперь, когда мы получили все варианты контрольных сумм, примерно пододящие под заданный диапазон, прокрутим мясорубку в обратную сторону. Нам нужно понять, в какое из чисел, за минусом контрольной суммы строки, влезет равное количество байт из ASCII от кода 88 до 122, т.е. от a до z. Так как мы точно знаем, что в алгоритме контрольной суммы каждый байт суммируется с числом 123456 мы можем: Теперь в l1 у нас остаются только числа, точно подходящие под заданный диапазон ASCII-кодов. Попробуем собрать строку. В результате мы имеем 2 серийных номера: Проверяем, и… да! Серийный номер валиден, статус "Registered"!   Web-developer