<< Все статьи по взлому Исследование алгоритма генерации кода в BlueFace и написание KeyGen'а Обсудить эту статью >>
Автор:MozgC [TSRh]
Уровень:Для начинающих
Программа:BlueFace (Final Release)
Инструменты:SoftICE 4.05, Win32DASM

Начинаем: И еще раз всем привет! На этот раз будем исследовать "сложнейший" алгоритм генерации кода в уже знакомой нам программе BlueFace. Вам ОБЯЗАТЕЛЬНО надо прочитать мою первую статью, где я описывал, как просто узнать правильный код для регистрации программы. Там я также давал некоторые необходимые основы для новичков, приводил ссылки на статьи/учебники по ассемблеру и т.д. Вообщем, если вы собрались читать эту статью, не читая первой, и тем более у вас совсем нет опыта в исследовании програм, то я даже не советую, а командую вам прочитать мою первую статью по исследованию данной программы. Статья находится тут ! Почему я решил написать это дополнение к статье? Ну вообще на почту пришло около 20 писем, с комментариями и отзывами о статье, среди которых были просьбы написать продолжение, где описать процедуру генерации кода и также написание своего генератора правильных кодов (такие генераторы попросту называют кейгены). Почти все отзывы были очень положительные, спасибо всем, кто черкнул мне хотя бы пару строк - было приятно читать. Этой статьей надеюсь не разочаровать вас и постараюсь, как и в первой статье, написать и разжевать так, чтобы было понятно АБСОЛЮТНО всем. Кстати, пока писал, только что вспомнил, что если вы уже зарегестрировали программу BlueFace, пользуясь инструкциями из первой статьи, то, чтобы программа была снова незарегестрированной (а нам этой понадобится для дальнейших исследований), просто переустановите программу, с заменой всех файлов. Вы уже знакомы с моими длинными и нудными введениями =) Что ж, давайте начнем, ближе к делу.

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

1) Для регистрации вас просят ввести ваше имя, возможно еще какую-то информацию, и регистрационный код.

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

3) В случае пункта 1 или 2 программа возможно просит отослать эту информацию (ваше имя, или серийный номер и т.д.) авторам вместе с деньгами, после чего вам вышлют регистрационный код =)

4) После переустановки, либо при установке на другом компьютере программа пишет другой серийный номер, либо регистрационный код, который работал на одном компьютере - не работает на другом (ну или соотвественно код, который работал сначала - не подходит после переустановки).

Вот это основные случаи. Это конечно не обязательно значит, что код не постоянен, а генерируется, но все-таки дает очень большую вероятность =) Откройте теперь окно регистрации программы (из первой статьи вы должны знать как это сделать, а я специально ничего не повторяю, чтобы точно знать, что вы прочитали первую статью =) и попытайтесь определить под какой пункт попадает наш случай !? Рад за тех, кто догадался, а для тех кто в танке объясню : видите там в поле serial number идет набор символов, так вот serial number - это серийный номер =). Теперь смотрим на пункт 2 - подходит ? Подходит! Также при более тесных экспериментах над программой вы сразу заметите, что на других компьютерах, а также после простой переустановки программы, в окне serial number уже другой серийный номер, и, соотвественно, старый код не подходит. Смотрим пункт 4. Подходит? Подходит ! Совпало 2 пункта из 4. Это говорит о большой вероятности того, что программа использует набор символов в окне serial number для генерации по нему правильного кода и потом сверяет этот сгенерированный код с введенным нами. В первой статье мы нашли то место, где происходит сравнение сгенерированного кода с введенным нами и просто подсмотрели код. В общеобразовательных целях, чтобы набраться опыта, а также чтобы например не смотреть код каждый раз с помощью отладчика, давайте разберем алгоритм генерации регистрационного кода и напишем кейген. Кейген будем писать на Delphi, т.к. я считаю, что Delphi - наиболее распространенный язык программирования среди начинающих и среди программистов среднего уровня. Тем, у кого Delphi не установлен, кто не собирается его устанавливать, кто не умеет программировать на Pascale/Delphi и т.д. предлагаю просто дочитать статью до конца с целью приобретения начального опыта, который пригодится в будущем. Иначе, какого фига, извините, вы вообще начали статью читать ? =)
Итак, разобрались с тем, что наша подопытная программа использует строку в поле Serial Number для генерации регистрационного кода. В принципе нам даже не надо знать, откуда она берет эту строку, но я об этом упомяну. Теперь давайте перейдем к практике.

Что на практике: Давайте вспомним, где вообще находится процедура генерации регистрационного кода и проверки его с тем кодом, который вводим мы. Из первой части вы, возможно, помните (ну если не помните - ничего страшного, или можете вкратце просмотреть еще раз первую статью), что по адресу 407279 вызывалась эта самая процедура генерации/проверки. Давайте посмотрим. Загрузите в дизассемблере (лучше в уже знакомом W32Dasm) исследуемую программу BlueFace. По адресу 407279 происходит вызов процедуры генерации/проверки кода:
00407279 E8621D0000              call 00408FE0
Как видно из этой строчки, процедура находится по адресу 408FE0. В принципе, листинг этой процедуры я приводил в первой статье. Но для удобства приведу его еще раз :
:00408FE0 6AFF                    push FFFFFFFF
:00408FE2 6883FA4000 push 0040FA83
:00408FE7 64A100000000 mov eax, dword ptr fs:[00000000]
:00408FED 50 push eax
:00408FEE 64892500000000 mov dword ptr fs:[00000000], esp
:00408FF5 81EC08010000 sub esp, 00000108
:00408FFB 56 push esi
:00408FFC 8B84241C010000 mov eax, dword ptr [esp+0000011C]
:00409003 C784241401000000000000 mov dword ptr [esp+00000114], 00000000
:0040900E 50 push eax

* Reference To: MSVCRT.atol, Ord:023Eh
|
:0040900F FF1504144100 Call dword ptr [00411404]
:00409015 83C404 add esp, 00000004
:00409018 8D4C2408 lea ecx, dword ptr [esp+08]
:0040901C 8D54240C lea edx, dword ptr [esp+0C]
:00409020 8BF0 mov esi, eax
:00409022 51 push ecx
:00409023 52 push edx
:00409024 C744241000010000 mov [esp+10], 00000100

* Reference To: KERNEL32.GetComputerNameA, Ord:00CEh
|
:0040902C FF1568104100 Call dword ptr [00411068]
:00409032 8D44240C lea eax, dword ptr [esp+0C]
:00409036 8D4C2404 lea ecx, dword ptr [esp+04]
:0040903A 50 push eax

* Reference To: MFC42.MFC42:NoName0040, Ord:0219h
|
:0040903B E818510000 Call 0040E158
:00409040 8B4C2404 mov ecx, dword ptr [esp+04]
:00409044 8B15AC704100 mov edx, dword ptr [004170AC]
:0040904A C684241401000000 mov byte ptr [esp+00000114], 00
:00409052 8B41F8 mov eax, dword ptr [ecx-08]
:00409055 8D4C0201 lea ecx, dword ptr [edx+eax+01]
:00409059 40 inc eax
:0040905A 0FAFC8 imul ecx, eax
:0040905D 3BF1 cmp esi, ecx
:0040905F 5E pop esi
:00409060 8D4C2400 lea ecx, dword ptr [esp]
:00409064 7536 jne 0040909C

* Reference To: MFC42.MFC42:NoName0024, Ord:0320h
|
:00409066 E893500000 Call 0040E0FE
:0040906B 8D8C2418010000 lea ecx, dword ptr [esp+00000118]
:00409072 C7842410010000FFFFFFFF mov dword ptr [esp+00000110], FFFFFFFF

* Reference To: MFC42.MFC42:NoName0024, Ord:0320h
|
:0040907D E87C500000 Call 0040E0FE
:00409082 B801000000 mov eax, 00000001
:00409087 8B8C2408010000 mov ecx, dword ptr [esp+00000108]
:0040908E 64890D00000000 mov dword ptr fs:[00000000], ecx
:00409095 81C414010000 add esp, 00000114
:0040909B C3 ret
Давайте пойдем с конца, я думаю так будет легче, потому что будем смотреть только на интересующие нас строки, чтобы не забивать мозги лишними командами. Я как всегда стараюсь вас успокоить, - по себе знаю, что когда новички видят такой большой код, просто могут даже бросить читать статью. Еще раз говорю : 3/4 этого кода нас не интересуют ! Будем разбирать только ключевые строки/команды. Итак, начинаем с конца. Как вы должны помнить из первой статьи, в строке:
:0040905D 3BF1                    cmp esi, ecx
сверяется введенный нами код, который содержится в esi, с правильным кодом, который содержится в ecx. Значит код генерируется и по ходу генерации (если это можно назвать генерацией, потом смеяться будете) заносится в ecx. Посмотрим, что делается с ecx до этого сравнения.
:00409052 8B41F8                  mov eax, dword ptr [ecx-08] ; В eax помещается
значение (dword - двойное слово = 4 байта), которое хранится по адресу ecx-8
:00409055 8D4C0201 lea ecx, dword ptr [edx+eax+01] ; эту строчку
объясню отдельно
:00409059 40 inc eax ; eax увеличивается на 1
:0040905A 0FAFC8 imul ecx, eax ; Значение ecx умножается на eax
и результат помещается в ecx
Сначала объясню про команду lea. С ее помощью в регистр загружается так называемый эффективный адрес (смещение). Например если нам надо засунуть в ecx смещение массива mas для последующей работы с ним, то нужно написать так: lea ecx, mas. Это для общего развития. Более подробную информацию можете посмотреть в справочниках и учебниках по ассемблеру (ссылку на рассылку Калашникова я давал вначале первой статьи). Иногда (редко, но в нашем случае это так) с помощью команды lea в регистр просто помещается значение других регистров. Например команда lea eax, dword ptr [ebx+ecx] аналогична команде mov eax, ebx+ecx . Запомните это. Тогда такая форма записи не будет в будущем вводить вас в смущение. В общем случае lea что-то, dword ptr [что-то другое] помещает что-то другое в что-то =), т.е. приравнивает их значения. Надеюсь я вас не запутал. Вернемся к нашему примеру. После всего вышесказанного понятно, что lea ecx, dword ptr [edx+eax+01] помещает edx+eax+01 в ecx, т.е. после выполнения команды ecx = edx+eax+1 . Осталось разобраться, что у нас в eax и в edx. Начнем с edx. Для этого надо встать в отладчике на одну из вышеприведенных четырех команд. Для примера возьмем нашу любимую строчку
:00409055 8D4C0201 lea ecx, dword ptr [edx+eax+01]
Вообще, прочитав мою первую статью, вы уже должны знать, как это сделать. Но для танкистов вкратце объясню. Открываем окно отладчика (CTRL+D), и теперь нам надо сделать нашу подопытную программу BlueFace текущим процессом, чтобы можно было устанавливать брейкпоинты на любые адреса программы. Для этого в отладчике пишем "addr blueface" (без кавычек) и жмем Enter. (Кстати, каждый раз, когда вы будете выходить из отладчика, а потом опять захотите поставить брейкпоинт на другой адрес, необходимо будет перед установкой брейкпоинта опять выделить процесс с помощью команды "addr blueface" и после этого ставить брейкпоинт. И еще не забывайте удалять уже ненужные брейкпоинты с помощью команды "bc *" - удалить все установленные брейкпоинты). Теперь, можно ставить брейкпоинт на адрес 409055 (это адрес команды lea ecx, dword ptr [edx+eax+01]), пишем "bpx 409055" и опять жмем Enter. Нажимаем F5 для продолжения. Теперь заходим в окно регистрации программы и пишем там любой код (я обычно пишу 1234321), после чего нажимам ОК. Срабатывает брейкпоинт по адресу 409055 и мы находимся на строке
:00409055 8D4C0201 lea ecx, dword ptr [edx+eax+01]
Вспоминаем, что мы хотели. А хотели мы посмотреть, что хранится в edx. Пишем в отладчике "? edx" и как всегда жмем Enter. Самые внимательные возможно заметят, что они уже где-то видели это значение (смотрите в десятичной записи числа). Для остальных подскажу, что это последняя часть строки, записанной в поле serial number в окне регистрации. Кстати строка Serial number будет у всех выглядеть по разному. Также в ней может быть одно или несколько тире. Значит в edx записывается эта последняя часть строки. В принципе нам неважно знать откуда она берется или генерируется. Ведь для генерирования правильного кода, мы будем просто брать эту часть и все. Осталось узнать, что у нас в eax и подвести итоги!
Те из вас, кто в крэкинге не полные новички, возможно уже про себя подумали, почему это я ничего не говорю про вызов функции GetComputerNameA по адресу 40902C. Ладно, так и быть, расскажу =) Функция GetComputerName - принимает входными значениями адрес строки, в которую поместится имя компьютера и адрес, по которому находится значение максимальной длины строки - имени компьютера. После выполнения функции, в соответствующие адреса кладется имя компьютера и длина имени клмпьютера (эта длина кладется по адресу, в котором раньше указывалась максимальная длина строки - имени компьютера). Как вы должны знать из первой статьи, входные параметры передаются функциям для последующей работы с ними через стек. Смотрим :
:00409022 51                      push ecx ; Кладем в стек адрес, по которому
хранится значение максимально допустимой длины строки, отведенной для сохранения в ней
имени компьютера
:00409023 52 push edx ; Кладем в стек адрес, по которому
сохранится имя компьютера
:00409024 C744241000010000 mov [esp+10], 00000100 ; записываем, что макс.
длина имени компьютера может быть равна 100h в шестнадцатеричном
(или 256 в десятичном) исчислении

* Reference To: KERNEL32.GetComputerNameA, Ord:00CEh
|
:0040902C FF1568104100 Call dword ptr [00411068] ; Вызывается функция
GetComputerName
Теперь, если вы посмотрите память по соотвествующим адресам (которые сохранялись в стек перед вызовом функции) в отладчике (извиняюсь, но вы уже должны уметь это делать, я не могу объяснять одно и то же по 10 раз), то увидите, что в них сохранились длина имени компьютера и само имя компьютера (так и быть, скажу, что чтобы посмотреть это, вам сначала надо было записать адреса, хранящиеся в ecx и edx, когда они помещались в стек. И потом после выполнения функции GetComputerNameA в отладчике написать команду "dd XXXXXXXX", где XXXXXXXX - соотвествующий адрес, после чего выведется область памяти, начиная с указанного адреса). Посмотрите внимательно на имя компьютера! (Для этого наберите "dd XXXXXXXX", ХХХХХХХХ - адрес который был в edx при сохранении в стек, т.е. выолнении команды push edx. И посмотрите справа от выведенной области памяти) Ничего не напоминает? Да, да, да, это первая часть поля serial number в окне регистрации. Значит в поле serial number записывается имя компьютера, потом ставится тире, и потом тот номер, который мы до этого видели в edx, помните? Ладно, смотрим дальше и видим, что вызывается еще какая-то функция:
:0040903A 50                      push eax ; Помещается в стек параметр, 
передаваемый функции

* Reference To: MFC42.MFC42:NoName0040, Ord:0219h
|
:0040903B E818510000 Call 0040E158 ; Ну и вызывается сама функция
Сейчас надо будет узнать, что же это за функция, и что ей передается. В отладчике посмотрите, что хранится по адресу eax (этот адрес передается функции). Для этого вам надо каким-либо образом (еще раз повторяю, вы это уже должны знать, а если не поняли - советую заново перечитать первую статью) попасть в отладчике на адрес 40903A, набрать "dd eax", и нажать Enter. "dd eax" - команда отладчику вывести на экран кусок памяти начиная с адреса, который хранится в eax. Вы увидите (справа, где показывается этот кусок памяти, в виде ASCII-символов), что по этому адресу хранится имя вашего компьютера, которое получили с помощью функции GetComputerNameA. Значит имя компьютера передается функции. Зачем? Давайте посмотрим. Для этого в отладчике, когда дойдете до команды вызова функции (call 0040E158), нажмите F8 для входа внутрь этой функции. На экране появится куча команд JMP на различные адреса, и вы тоже окажетесь на одной из таких команд. На всякий случай упомяну (вообще-то я просил вас уже иметь начальные минимальные знания ассемблера), что команда JMP XXXXXXXX делает переход на адрес XXXXXXXXX, т.е. следующая команда, которая будет выполняться, будет по адресу XXXXXXXX. Нажимаем F8 или F10 и попадаем в некую процедуру, смотрим ниже и видим:
CALL   [Kernel32!lstrlen]
Это означает, что вызывается функция lstrlen из библиотеки kernel32. Функция lstrlen предназначена для измерения длины строки, которая передается этой функции. А адрес какой строки перед этим помещался в стек? Адрес строки, содержащей имя компьютера! Значит эта процедура, в которую мы зашли, находит длину имени компьютера! Кстати, давайте сосчитаем длину имени компьютера. Она у всех может быть разной, как и имя компьютера. У меня получилось 15 символов (в шестнадцатеричной форме = 0F). Сосчитали? Хорошо, значит тут зачем-то вычисляется длина имени компьютера. Ок, идем дальше. Давайте все же посмотрим, что у нас в eax. Нам ведь это нужно знать, чтобы понять алгоритм генерации регистрационного кода!? Поставим брейкпоинт на адрес 409055 ("bpx 409055"), чтобы прерваться на уже знакомой нами строке
:00409055 8D4C0201                lea ecx, dword ptr [edx+eax+01]
Почему именно на этой строке? Да потому что перед этой командой (смотрите листинг процедуры вначале) в eax помещается какое-то значение, вот мы прервемся и посмотрим, что поместилось в eax. После того, как вы попали на эту строчку в отладчике наберите "? eax" и жмите Enter (о том, что надо жать Enter после всех команд отладчику, я больше не буду упоминать). Что мы увидели ? Лично я увидел вот что (у вас скорее всего будет другое значение, но это не важно, вы поймете) :
:? eax
0000000F 0000000015
Ничего не напоминает? Самые сообразительные, я уверен, догадались, что это длина имени компьютера! Теперь когда мы знаем все, что нам необходимо, давайте подведем итоги и определимся с алгоритмом генерации регистрационного кода. Смотрим еще раз вот этот участок программы:
:00409052 8B41F8                  mov eax, dword ptr [ecx-08] ; В eax, как мы только что
узнали, помещается длина имени компьютера
:00409055 8D4C0201 lea ecx, dword ptr [edx+eax+01] ; В ecx помещается
сумма - число, хранящееся в edx (edx = последняя часть в поле serial number,
после последнего тире, или просто после тире, если у вас только
одно тире в Serial number =) + длина имени компьютера + 1
:00409059 40 inc eax ; eax (длина имени компьютера)
увеличивается на 1
:0040905A 0FAFC8 imul ecx, eax ; Полученная перед этим сумма
умножается на eax (т.е. на длину имени компьютера + 1) и результат помещается в ecx
:0040905D 3BF1 cmp esi, ecx ; Сравнивается введенный нами
код (в esi) с только что сгенерированным правильным кодом (в ecx)
Итак, подводим итоги! Правильный регистрационный код генерируется по такому алгоритму:

1) Из поля serial number в регистрационном окне берется число - последняя часть строки, после второго тире.
2) К этому числу прибавляется длина имени компьютера + 1 (имя компьютера записано в первой части поля serial number, до второго тире)
3) Полученное число умножается на длину имени компьютера + 1.

Все! Вот и весь алгоритм! Можете вручную проверить! Для тех, кто не дай бог не понял, приведу пример, как это было в моем случае. В поле serial number у меня была строка:

VAZ-DN7WUNSYNWE-31586016

Первая часть "VAZ-DN7WUNSYNWE" - это имя компьютера, ее длина 15. Вторая часть "31586016". К 31586016 прибавляем 15 и еще 1, получаем 31586032. Теперь это число умножаем на (15+1), получаем 505376512 - это и есть правильный регистрационный код в моем случае! У вас он естественно будет другим (как я уже говорил, имя компьютера как и вся строка serial number, у вас будет другим. Например, у вас может быть такой серийный номер - "SuperHacker-1366613"). Ну и как? Сложно? Приступим к написанию кейгена!

Пишем кейген: Ну Delphi я вас не собираюсь учить, только вкратце скажу, что надо сделать, с небольшими комментариями. Запускам Delphi, создаем новый проект, кладем на форму компонеты Edit, Label и Button. Очищаем свойства text компонента Edit и caption компонента Label. Два раза кликаем по созданной нами кнопке и попадаем в пустую пока процедуру, которая будет выполняться при нажатии этой кнопки. Вот как должна выглядеть эта процедура в готовом варианте:
procedure TForm1.Button1Click(Sender: TObject);
var
ComputerName, chislo : string; ; инициализация переменных
i,j : integer;
regnumber : longint;
begin
i:=length(Edit1.Text);
while Edit1.Text[i]<>'-' do i:=i-1; ; Цикл c конца строки, пока не наткнемся на тире
j:=i; ; Cохраняем номер символа-тире в строке в переменной j для последующих действий
ComputerName:=copy(Edit1.Text,1,j-1); ; Копируем в переменную ComputerName часть
строки, содержащейся в Edit1.Text, начиная с первого символа и до тире
(j - позиция тире в строке)
chislo:=copy(Edit1.Text,j+1,length(Edit1.Text)-j); ; Аналогично, копируем в переменную
chislo часть строки, содержащейся в Edit1.Text, начиная со следующего после тире
символа (его номер - j+1). Кол-во копируемых символов задается выражением
length(Edit1.Text)-j - так мы получим длину второй части серийного номера
regnumber:=(StrToInt(chislo)+ ; Вычисляем регистрационный код по известному алгоритму
length(ComputerName)+1)*
(length(ComputerName)+1);
Label1.Caption:=IntToStr(regnumber); ; Регистрационный код выводится на экран с помощью
компонента Label
end; ; Конец процедуры
Теперь компилируйте этот проект, вводите serial number в Edit, нажимайте на кнопку и получайте свой регистрационный код! Можете по своему усмотрению оформить все красиво, моя задача была просто вкратце объяснить вам, как написать кейген, если известен алгоритм его генерации. Те, кто умеет хоть чуть-чуть программировать на Delphi, поймут все с легкостью. На всякий случай упомяну, что функция StrToInt преобразовывает строку в число, а функция IntToStr, наоборот, преобразовывает число в строку.
Ну, надеюсь, со своей задачей я справился! Если вы так не считаете - шлите мне свои комментарии (email вверху и внизу страницы) - я все учту в будущих статьях.

Заключение: В своих первых двух статьях я попытался разжевать процесс исследования, регистрации, написания кейгена до того, чтобы было понятно каждое слово, чтобы новичок, который первый раз садится за работу с отладчиком, приобрел основы этого дела, и чтобы у него не осталось никаких вопросов. Этими статьями я хотел дать вам резкий толчок вперед, потому что по себе знаю, сколько времени может быть потрачено впустую, читая непонятные статьи, проводя дни в поисках необходимой информации и т.д., по себе знаю как все сначала кажется непонятным и трудным. Надеюсь мои статью дали вам такой толчок. Если нет - извините, сильнее разжевать уже нельзя, в этом случае читайте дополнительную литературу для приобретения необходимых основ. Я планирую продолжать писать статьи по крэкингу, постепенно увеличивая уровень сложности - нельзя стоять на месте. Поэтому настоятельно вам рекомендую разобраться со всеми возможно оставшимися непонятными моментами, так как если вы что-то не поняли сейчас - в следующих статьях будет труднее.

Приветы: Как и в первой статье, выражаю благодарность freeExec'у и MoonShiner'у - они действительно уделяют мне много времени - всегда помогают и объясняют, когда мне что-то непонятно. Также спасибо тем, кто тестировал, комментировал и помогал что-то подправить в этой статье перед тем как она вышла, и всем тем, кто присылал мне комментарии и отзывы по почте.

Отзывы и комментарии уже по этой статье тоже можете послать на MozgCnoSpam@avtograd.ru

<< Все статьи по взлому Обсудить эту статью >>

ALIEN Hack Team - http://ahteam.org
Только для образовательных целей