Исследование защиты игры Limbo. Кейген

Исследование защиты игры Limbo. Кейген

Всем привет. Многие знают об этой замечательной игре — LIMBO! Вы даже наверняка покупали ее в Стиме, или качали с торрентов… Я тоже ее купил когда-то (что и вам советую!), и прошел). Но, как всегда, мне было этого мало, и я, из спортивного интереса, решил изучить ее защиту. Так и появился кейген к игре LIMBO. В этой статье я расскажу и покажу вам, как я это делал.

Прежде, чем начинать, помните: все действия вы выполняете на свой страх и риск. Уважайте работу геймдевелоперов. Этап первый: Поверхностный осмотр пациента

Полный инсталлятор игры можно скачать здесь. Установив игру, первым делом, как обычно, выясняем, на чем написан главный исполняемый файл. Я воспользуюсь для этого ExeInfo PE.

Видим: Visual Studio 2008. IDA Pro прекрасно с ней справляется, поэтому туда и отправим. Я буду пользоваться связкой IDA Pro + HexRays, т.е. с декомпилятором — для ускорения работы.

Этап второй: что мы ищем?

Первым делом, дадим Иде проанализировать limbo.exe — главный исполняемый файл игры.

Далее, нужно определить, что именно мы, собственно, хотим найти здесь. Запустим игру:

Видим волшебную надпись "UNLOCK FULL GAME". На нее и нажмем. Далее нас ожидает нежданчик (по крайней мере, я, когда первый раз выбирал этот пункт меню, я ожидал увидеть поле ввода на графическом движке игры, или типа того, а оказалось все гораздо проще. ):

Да, да! Именно обычное окошко! Нам же легче. Попробуем что-нибудь ввести, и нажать Unlock. Как-то так:

Ну что ж, поищем по тексту в IDA, чтобы затем от него отталкиваться, и найти место проверки. И тут меня ожидал облом… По тексту сообщения в окне ошибки Ида мне ничего не нашла! То же самое сказал мне и поиск по содержимому через Total Commander. Возможно, сообщение зашифровано. Можно попробовать найти вызов окна отталкиваясь от вызова MessageBoxA/W. Но, я пошел другим путем, который, почему-то, мало где в статьях описывают.

Этап третий: Нажми меня

Мы поступим следующим образом. Откроем любой удобный Вам редактор ресурсов, затащим в него ехе-шник, найдем окошко диалога ввода ключа, а в нем — кнопку Unlock. Сказано — сделано:

На скрине я выделил ID нашей кнопки. По нему мы и будем искать, где именно обрабатывается нажатие. Откроем Иду, нажмем Alt+I (Search -> immediate value. ), введем число 203 (без 0x, т.к. десятичное), и посмотрим, что найдется. А нашлось вот это:

Видите те строчки, которые Ида пометила как ; nIDDlgItem? С них и начнем. Двойным кликом переходим на первый из таких результатов:

Зеленой стрелкой я обозначил место, на которое указала Ида, а чуть ниже (привычка: прокручивать выше/ниже искомого места) — стрелкой обозначено место вызова одной интересной API-функции: GetDlgItemTextA. Судя по названию по MSDN, эта функция получает текст указанного элемента окна в буфер.

Итак, проследим, куда уходит полученный серийник. Прокручиваем листинг, чтобы видеть место вызова API-функции целиком:

Мой "намыленный" взгляд подсказывает мне, что полученный буфер (Ида обозначила его как var_134) передается прямиком в следующую за вызовом GetDlgItemTextA функцию, которая возвращает в al нулевое, либо ненулевое значение (похоже на результат проверки ключа). Давайте проверим догадку…

Этап четыре: Декомпиляция

Заходим в функцию. Видим там прыжок на еще один адрес — переходим по нему. Видим нормальный код, поэтому смело жмем там F5 (вызываем HexRays Decompiler).

Теперь можно попытаться привести этот код к более адекватному.

Первым делом, замечаем, что входной параметр имеет тип int, что не совсем правда. Обозначим его как "char *". Для этого становимся на имя функции и жмем там клавишу Y (Set item type). Исправляем тип и имя входного параметра (я обозвал его как key).

Далее… Видим строчку:

Т.к. наш входной параметр — строка, давайте в тех местах, где символы ключа сверяются с числами, исправим на сравнение с символами. Для этого на каждом из таких чисел нажмем R (Char). Уже лучше:

Для наглядности дадим v3 имя i, т.к. похоже, что она используется как итератор. Переименовываем нажатием на имени клавиши N (Name).

Замечаем, что в цикле происходит взятие каждого символа из ключа, и передача его в пока неизвестную нам функцию. Предлагаю выяснить, что это за функция. Двойным щелчком переходим в нее. Видим там вызов еще одной функции, переходим туда. И, вот оно — обработка одиночного символа! (Здесь есть куча работы для клавиши R, но я лишь покажу сразу результат обработки).

Прекрасно! Теперь возвращаемся назад клавишей Esc до основной функции. Замечаем, что IDA сама переопределила для нас тип результата возвращаемого функцией обработки символа. Именуем дальше, обозначаем типы, и получаем следующий код цикла:

Если вы заметили, то тут есть одна интересная бага декомпилера. Видим, что переменная, обозначенная у меня как itr, совершенно не инкрементируется. Чтобы выяснить, что на самом деле происходит, жмем ПКМ -> Copy to assembly, и смотрим, где же используется наша itr. Выясняем: она инкрементируется прямо в этом цикле (чего и стоило ожидать), а до цикла — обнуляется. Учтем это при написании кейгена.

Теперь вторая часть функции проверки ключа… У нас осталась одна неисследованная функция, которая, кстати, очень похожа на функцию подсчета CRC32. Результат обработки (пусть и на скорую руку, но читаемый):

Оставшийся кусок (преобразованный):

Этап пять: написание кейгена

Задача: определить, что именно происходило с ключом, чтобы написать обратную функцию. Писать я буду, вопреки здравому смыслу и выдаче HexRays, на Delphi, а Вы можете писать на том языке, который проще именно Вам.

  1. Игре нужен ключ в 32 символа без дефисов (37 — с дефисами).
  2. Берется по четыре символа из ключа (не учитываются дефисы). Каждый из них пропускается через функцию convert_char и суммируется по формуле: sum += new_c << 5 * (3 — itr);
  3. Каждая такая сумма преобразовывается в lower-case хекс-строку (5 символов) и доклеивается до имеющейся (итого 40 символов);
  4. Берется CRC32 от первых 32 символов получившейся строки и сравнивается с оставшимися восемью символами полученной в предыдущем пункте строки;
  5. Если строки не совпали — наш ключ неправильный.
  1. Написать функцию, преобразующую 40-символьный хэш обратно в ключ;
  2. Сгенерировать 32-символьный хэш;
  3. Посчитать от него 8-символьный CRC32;
  4. Склеить строки, полученные на этапах (2) и (3);
  5. Передать в функцию (1) — получим искомый ключ.
  1. Т.к. входной хэш был получен из восьми5-символьных хэш-кусков, будем обрабатывать его так же, по "пятеркам";
  2. Каждая "пятерка" была получена из четырех символов ключа;
  3. Т.к. при каждом вычислении "пятерки", она сдвигалась на 5 бит влево, получается, что на каждый символ ключа приходится 5 бит;
  4. Внимательное рассмотрение кода функции convert_char приводит нас к такой мысли, что набор символов ключа ограничивается лишь символами набора "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
  5. Итого: 32 символа хэша генерятся из "пятерок". 32 % 5 = 24 целых символа и 2 в остатке — т.е. два символа нам придется просто догенерить.

Далее считаем CRC32 от хэша:

Ну и, наконец, результирующее получение лицензионного ключа:

Проверяем, и… Ввод сгенеренного ключа активировал игру, пункт активации исчез!

Итоги

Главное при написании кейгена — уметь обратно думать. Т.е. уметь написать такой алгоритм, который будет обратным тому, который вы имеете. Это непростая задача, но, и она решаема в большинстве случаев.

P.S.

Возможно, статья получилась слишком сумбурной, не знаю. Главная идея, которую я хотел донести: кейген — не такая и сложная штука, если есть мозги, и желание вместе с усидчивостью.

📎📎📎📎📎📎📎📎📎📎