Абсолютное большинство защит на серийных ключах построено по одному и тому же принципу. Их даже ломать становится неинтересно. Но вот crackme WhatHTML, написанный программистом c ником Chiwaka, представляет собой пример хорошей защиты на серийном ключе. Именно ее мы сегодня и разберем.

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

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

Взлом таких защит сводится к простому «выдиранию» алгоритма генерирования серийного номера на основе имени пользователя. Причем мне встречались такие crackme'сы, в которых для написания keygen'а не требовалось даже разбираться в этом самом алгоритме: все решалось тупым копированием процедур, ответственных за генерацию валидного серийного номера. Понятно, что это достаточно скучное дело, даже не требующее серьезного напряжения извилин.

 

Что мы будем ломать

Crackme WhatHTML — это тоже пример защиты на регистрационном ключе. Но построенный по совершенно иному принципу. Написан он программистом, известным под ником Chiwaka. Скачать сам crackme можно бесплатно по ссылке cracklab.ru/crackme/whathtml.zip. Именно это я и предлагаю тебе сделать, закрыв эту статью. Ведь ломать самому всегда намного интереснее, чем читать инструкции по взлому. Главное окно программы представлено на одноименном рисунке. Значение в поле «Machinecode» у тебя будет другим. Откуда оно берется, узнаем позже. Как нетрудно догадаться, нужно найти валидные комбинации значений полей «Name» и «Serial», а также по возможности написать keygen.

Ну что ж, со знакомством, думаю, закончили. Теперь переходим к самому интересному.

 

Первый взгляд

Итак, приступим. Начинается программа с получения адреса функции ShowHTMLDialog из библиотеки mshtml.dll (делается это с помощью стандартных функций LoadLibraryA и GetProcAddress).

После этого вызывается несколько процедур, которые и выполняют основную работу. Ниже приводится дизассемблерный листинг этой части программы, полученный с помощью IDA Pro,а также мои комментарии к нему:

.text:00401043 call sub_4010B2
.text:00401048 call sub_401298
.text:0040104D call sub_401169
; Если пользователь просто закрыл окно, то выходим
; из программы, иначе вызываем процедуру sub_401298
.text:00401052 cmp dword_40303B, 0
.text:00401059 jnz short loc_401060
.text:0040105B call sub_401298
; Завершаем работу приложения
.text:00401060 pop large dword ptr fs:0
.text:00401067 add esp, 4
.text:0040106A push 0 ; uExitCode
.text:0040106C call ExitProcess
.text:0040106C start endp

Из листинга видно, что программа последовательно вызывает три процедуры (sub_4010B2, sub_401298, sub_401169); в том случае, если пользователь нажал на кнопку «Register», то еще раз вызывается процедура sub_401060. Что ж, пока вроде ничего сложного. Теперь познакомимся с нашими процедурами поближе.

 

Таинственное поле Machinecode

Первой на очереди стоит процедура sub_4010B2. В этой процедуре вызывается функция GetSystemInfo, и на основе возвращенных ей данных по определенному алгоритму формируется значение, которое и идет в поле «Machinecode».

 

Не ассемблером единым

Процедура sub_401298 ответственна за формирование и отображение окна на экране. В ней происходит вызов найденной ранее функции ShowHTMLDialog. В качестве параметра она принимает строку с html-кодом окна. В целях экономии места демонстрировать весь html-код окна я не буду, приведу лишь код обработчика нажатия на кнопку «Register», написанный на JavaScript.

Вот он:

function okButtonClick()
{
var x = 0;
var y = 0;
var z = 0;
var charx = 0;
var chary = 0;
var myName = Name.value;
var mySerial = Serial.value;
var myRandom = Random.value;

for (var i=0; i<myName.length; i++)
{
x = x + myName.charCodeAt(i);
}
for (var i=0; i <mySerial.length; i++)
{
charx = mySerial.charCodeAt(i-1);
chary = mySerial.charCodeAt(i);
if (charx != chary)
{
z = z + chary;
charx = chary;
}
}
for (var i=0; i <arrArgs.length;i++)
{
y = y + arrArgs.charCodeAt(i);
}

window.returnValue = x.toString(10)+ "?" + z.toString(16) + "?" + y.toString(10);
window.close();
}

Если ты знаком с языком JavaScript, то легко увидишь, что здесь происходит суммирование кодов символов, составляющих имя, серийный номер и код машины (поле «Machinecode»). Причем очередной символ серийного номера добавляется только в том случае, если он отличается от предыдущего символа серийного номера. Полученные значения оформляются в строку, символомразделителем служит знак «?». Эта строка возвращается приложению, создавшему окно.

 

Защита

Следом за вызовом процедуры sub_401298, создающей окно, идет вызов процедуры sub_401169, которая представляет для нас особый интерес, поскольку именно в ней сосредоточен защитный механизм.

Данная процедура осуществляет разбор полученной на прошлом этапе строки и формирует три числа на основе имени пользователя, серийного номера и информации о машине. Числа формируются по следующему принципу: если у нас в качестве строкового выражения было передано «725», то после преобразований будет получено значение 725h.

Полученные таким нехитрым образом числа складываются друг с другом, образуя ключ, которым расшифровывается внутренний буфер. Дизассемблерный код расшифровщика приводится ниже:

; Цикл расшифровки буфера. Алгоритм расшифровки —
; банальный xor. Ключ расшифровки лежит в регистре cx
.text:00401225 mov ebx, dword_403CF4
.text:0040122B lea eax, String ; ".b"
.text:00401231 xor edx, edx
.text:00401233 jmp short loc_40123C
.text:00401235
.text:00401235 loc_401235:
.text:00401235 xor [edx+eax], cx
.text:00401239 add edx, 2
.text:0040123C
.text:0040123C loc_40123C:
.text:0040123C cmp edx, ebx
.text:0040123E jb short loc_401235
; Проверка правильности расшифровки, осуществленная
; путем сравнения трех символов из расшифрованного
; буфера с эталонными символами. Если хотя бы
; один символ не совпадает с образцом, нас посылают
; куда подальше.
.text:00401240 lea eax, unk_403072
.text:00401246 cmp byte ptr [eax], 28h
.text:00401249 jnz short loc_401272
.text:0040124B cmp byte ptr [eax+5], 12h
.text:0040124F jnz short loc_401272
.text:00401251 cmp byte ptr [eax+0Ah], 1
.text:00401255 jnz short loc_401272
; Если мы здесь, значит, нас признали легальными
; пользователями
.text:00401257 call sub_401372

Подытожив все вышесказанное, можно описать работу программы так:

  1. На основе информации о машине формируется некоторое кодовое число «Machinecode»;
  2. У пользователя запрашивается имя и серийный номер;
  3. По значениям«Machinecode», имени и серийного номера с помощью JavaScript-кода формируется три числа;
  4. На основе полученных трех чисел формируется ключ к расшифровке буфера;
  5. Расшифровывается вшитый буфер;
  6. Правильность расшифровки проверяется по трем символам, взятым из расшифрованного буфера;
  7. Если все расшифровано верно, то пользователь признается легальным правообладателем. Если же буфер расшифрован неправильно, то пользователю сообщается об ошибке.

Теперь, когда мы разобрались с устройством защиты, давай перейдем к ее взлому.

 

Взлом

Вот тут-то начинается самый настоящий простор для фантазии: дело в том, что взломать программу можно не одним и даже не двумя способами. Самое простое, что приходит на ум, это исправить условные переходы в проверке правильности расшифровки так, чтобы они никогда не срабатывали. Или вообще забить эту проверку nop'ами. Но при таком подходе мы не увидим поздравительного сообщения (да-да, в расшифровываемом буфере находится именно оно). Давай думать, что нам известно об используемом шифре.

Во-первых, алгоритм шифрования — xor, а значит, имея пару «зашифрованная строка и расшифрованная строка», а затем выполнив операцию xor между ними, мы найдем ключ расшифровки. Во-вторых, ключ расшифровки находился в регистре cx, а значит, длина ключа — два байта. Помимо всего прочего, у нас есть три пары значений «зашифрованный символ и расшифрованный символ» (эталон, по которому программа определяет правильность расшифровки). Все это позволяет нам без труда определить требуемый ключ расшифровки: 04E6h.

Зная требуемый ключ, мы можем перед циклом расшифровки воткнуть либо команду «mov cx, 04E6h», либо написанный полноценный keygen.

А что, в написании keygenа для этой программы нет ничего сверхсложного. Работать keygen будет приблизительно по следующей схеме:

  1. Точно так же, как это делает ломаемая нами программа, определяем параметры компьютера пользователя и рассчитываем на их основе кодовое число;
  2. Предлагаем пользователю ввести имя, на которое он хочет сгенерировать серийный номер;
  3. На основе введенного имени вычисляем соответствующее ему число (снова так же, как это делает программа);
  4. Зная числа, основанные на имени пользователя, параметрах компа и ключе расшифровки, определяем третье число — основанное на серийном номере;
  5. Зная число, к которому должен приводить серийный номер, находим сумму символов серийного номера;
  6. Зная сумму символов серийного номера, генерируем сам номер и отдаем его пользователю.

Вот и весь keygen.

 

Заключение

Мы рассмотрели пример неплохой защиты, основанной на серийном номере. Как видишь, даже здесь, во вроде бы давно избитой методике защиты приложений, есть место воображению. Так что, если ты программист и хакер, то проявляй фантазию даже в тривиальных, казалось бы, вещах, и всем станет жить намного интереснее.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии