Практика создания защиты
Автор: Krott
Все хотят кроме удовольствия получать еще и деньги. Для каждого программиста создавать программы - удовольствие. Что же может быть лучше для него, чем продавать свои творения? Вообще я - сторонник бесплатности ПО, но в этой статье речь пойдёт не о деньгах. Я продолжу тему, начатую в одном из прошлых выпусков журнала. Только на сей раз займёмся практической реализацией защиты. Надо сказать, я не стремлюсь написать сложную и совершенную защиту - будем придерживаться принципа, что лучше направить силы в сторону улучшения программы. По сути дела, в этой статье я несколько улучшу защиту, написанную мной минут за двадцать для топика Игрушка для форума - Ломалки. Набросаю план, которого будем придерживаться по ходу размышлений.
- Создание простой защиты.
- ввод и проверка регистрационного кода
- маскировка
- отвлекающие маневры
- фичи
- Надёжная защита и как её реализовать.
1. Создание простой защиты
Здесь мы рассмотрим очень простой способ создания защиты, основанной на использовании регистрационного кода, а также способы улучшения этого метода.
Ввод и проверка регистрационного кода
Итак, у пользователя есть регистрационный код. Для того чтобы он смог его ввести, будем использовать стандартное окошко InputQuery, потом немного поюзаем реестр и сделаем проверку, нарушив тем самым все писаные и неписаные правила создания защит. Объясню смысл действий.
Бывает, что правильный регистрационный код хранят в реестре или в файле на винчестере, чтобы потом можно было сравнить его с введённым (ужас). Не будем долго думать и сделаем всё наоборот - после ввода запишем этот код в реестр, а далее так или иначе будем сравнивать его с эталоном.
Да, и тут же совершим абсолютно очевидную и предсказуемую проверку кода.
На данный момент у нас будет типичная для большинства shareware-программ защита. Я надеюсь, что негодяй-взломщик подумает так же. Он обнаружит обращения к реестру, увидит проверку, сгенерирует по ней регистрационный код и успокоится. Ну и хорошо. Для пущей уверенности поблагодарим его за успешную регистрацию.
Ниже привожу весь код, каким он должен быть на данный момент с подробными комментариями. В этот код желательно насовать разного мусора: ненужных проверок, возвращающих всегда одно и то же значение, математических операций, в общем, всего, чего угодно. Здесь я, естественно, всё это опущу.
//---------------------------------------------------------------------------
#include
#include
#include
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
AnsiString RegKey;
// далее идут восемь функций
// в них помимо мусора вызов Terminate
// это сделано, чтобы не вызывать одну функцию, которую можно заблокировать
void Terminate1(){Application->Terminate();}
void Terminate2(){Application->Terminate();}
void Terminate3(){Application->Terminate();}
void Terminate4(){Application->Terminate();}
void Terminate5(){Application->Terminate();}
void Terminate6(){Application->Terminate();}
void Terminate7(){Application->Terminate();}
void Terminate8(){Application->Terminate();}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
TRegistry *reg=new TRegistry;
if (!reg->OpenKey("Software\MyProg\Reg", false)){
//если первый запуск программы, то ключ надо создать
reg->OpenKey("Software\MyProg\Reg", true);
reg->WriteString("RegKey", "");
}
//читаем значение
RegKey=reg->ReadString("RegKey");
//выводим диалог
if (RegKey=="" ||RegKey.Length()!=15){
if (!InputQuery("Registration", "Type the registration code", RegKey))
rcode-=77;
}
//начинаем проверку
char *first=AnsiString(RegKey.SubString(1, 1)).c_str();
if (isalpha(*first))
rcode+=89;
else
Terminate1();
if (RegKey.SubString(2, 3)=="R14")
rcode/=4;
else
Terminate2();
char *second=AnsiString(RegKey.SubString(5, 1)).c_str();
if (isdigit(*second))
rcode-=15;
else
Terminate3();
if (RegKey.SubString(12, 1)=="6")
rcode*=2;
else
Terminate4();
if (RegKey.SubString(13, 1)=="R" || RegKey.SubString(13, 1)=="E" || RegKey.SubString(13, 1)=="G")
rcode/=13;
else
Terminate5();
if (RegKey.SubString(14, 1)=="0" || RegKey.SubString(14, 1)=="5" || RegKey.SubString(14, 1)=="8")
rcode+=41;
else
Terminate6();
if (RegKey.SubString(15, 1)=="B")
rcode-=1;
else
Terminate7();
//записываем введённое в реестр
reg->WriteString("RegKey", RegKey);}
//---------------------------------------------------------------------------
Маскировка
Итак, у нас есть одна абсолютно очевидная проверка. Мы её афишируем, как можем: пишем в реестр, делаем ненужные операции. Теперь хорошо бы создать ещё одну (две, три, а может и больше - зависит от желания) проверок. Их надо замаскировать как можно тщательнее, можно, например, закосить под обработку исключительных ситуаций. Как реализовать эти проверки - решать вам, я приведу лишь простенький примерчик:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//проверка по алгоритму
//среднее арифметическое шести цифр кода с 6-ой по 11-ую
// должно делиться на пять
int summ=0;
// здесь я сделал двойной цикл, чтобы запутать взломщика
//главное самому не запутаться
//мы складываем шесть цифр из регистрационного кода
//эту операцию проводим шесть раз
// а когда вычисляем среднее арифметическое - делим не на 6, а на 36
//с учётом второго цикла
for (int j=0; j<6; j++){
for (int i=0; i<6; i++){
summ+=StrToInt(RegKey.SubString(i+6, 1));
}
}
if ((summ/36)%5==0)
// запомните, что когда мы делим два числа типа int, то частное
//округляется к меньшему целому
summ+=45;
else{
Application->MessageBoxA("Error during loading the resource. Ask the author about the error # 14", "Error!", MB_OK);
return;
// здесь я сделал очень примитивно, ничто не мешает просто поставить здесь брекпойнт
// надо сделать заковыристее
// хотя бы так же, как в первой проверке
}
}
//---------------------------------------------------------------------------
Разумнее выставить этот код на открытие файла, если вы пишете редактор, тогда в незарегистрированной версии эта функция работать не будет. Да и, конечно, алгоритм проверки желательно усовершенствовать.
Отвлекающие маневры
Надо как-то отвлекать хакера, сводить его с правильного пути взлома. Возможно, на опытных это и не подействует, но от новичков оградит. Вы можете использовать любую чушь, которая в данном месте программы кажется бессмысленной и привлекает на себя внимание. Итак, рассмотрим несколько отвлекающих методов.
Бесполезные расчёты.
Среди такого мусора порой бывает очень сложно рассчитать правильный регистрационный код, так что пренебрегать использованием этого метода не стоит.
Используем цикл.
Его желательно разместить сразу после вызова InputQuery. В цикл можно поместить код, производящий какие-либо действия над паролем, многократные обращения к реестру, вызовы API-функций, это всё реально сможет спрятать за собой дополнительные проверки кода.
Копируем код.
После ввода хорошо бы скопировать его в несколько переменных. Для нас это абсолютно бессмысленно, но взломщика должно отвлечь.
Вообще можно придумать ещё много всякого, оставлю это на ваше усмотрение.
Фичи
Существует множество способов, с помощью которых можно реально осложнить жизнь взломщику. Я познакомлю вас с некоторыми из них:
Защита от крэкерского софта.
Если на компьютере пользователя, использующего программу, установлены SoftIce, IDA, HIEW или WinHEX, RegMon, FileMon, APIMon, Restorator, ImpRec и другие специальные утилиты, это наводит на мысль, что программу нашу хотят немного поломать. Поэтому нужно срочно прикрыть лавочку. Опытных крэкеров это не остановит, но проблемы им создаст.
Реализация:
Для начала, надо узнать, что эти программы установлены на компьютере пользователя. Как это сделать? Можно искать их на жёстком диске - довольно медленно. Лучше обнаружить их по ключам в реестре - это будет намного быстрее, или отлавливать во время исполнения запущенные приложения по таймеру. В Интернете много примеров обнаружения запущенного хакерского софта. Я видел даже компонент, предназначенный для поиска в реестре этих программ, но, к сожалению, он был платным.
Защита от взлома методом String Reference.
String Reference - достаточно распространённый метод взлома. Заключается он в следующем: в уже откомпилированной программе содержатся строки "Unregistered version", "Trial" и так далее. Взломщики (особенно начинающие) дизассемблируют прогу и ищут такие строки. Далее смотрят, откуда они вызываются, пара изменений ассемблерного кода и программа сломана.
Как защититься?
Загружать все строки из файла.
Реализация.
Реализуется достаточно просто, хотя желательно, чтобы эти строки хранились вдобавок в зашифрованном виде.
Проверка CRC.
Это защитит от пропатчивания программы. Можно проверять по таймеру, или в определённых местах.
Реализация.
Проблем с реализацией возникнуть не должно, примеров в сети много (один из вариантов предложен в ФАКе по С++ Builder на форуме).
Использование архиваторов.
ASPack и PEPack, если вы хотите создать качественную защиту, не прокатят. Уже давно научились их распаковывать (например, утилитой ASPackDie). Выход один - писать свой собственный хитроумный упаковщик. При хорошей реализации это может сильно осложнить процесс взлома.
2. Надёжная защита и как её реализовать
Безусловно, описанный мной способ даже после всевозможных улучшений никак не претендует на звание устойчивой защиты. Поэтому сейчас я расскажу о лучших способах защиты.
Лучшие способы
Необратимое шифрование пароля.
Алгоритм прост. После ввода пароля шифруем его необратимым шифрованием, а затем так или иначе сравниваем его с прошитыми в программу шаблонами.
[+] При хорошей реализации пароль можно подобрать только брутфорсом.
[(] Правильные пароли для раздачи пользователям и вам придётся искать перебором.
Шифрование кода.
Можно хранить часть кода в программе зашифрованной. После ввода пароля - расшифровывать им этот код. Если пароль правильный, то программа будет работать нормально, в противном случае - с ошибками.
[+][(] Плюсы и минусы налицо. У того способа, который я описал в статье и у этих двух есть один общий жирный минус.
Он заключается в том, что эти защиты потеряют смысл после разглашения хотя бы одного правильного пароля / регистрационного ключа.
Поэтому, для того чтобы сделать защиту устойчивой, необходимо добавить в неё либо привязку к железу, либо метод активации.
Привязка к железу.
Довольно неудачный способ. Создаёт довольно много проблем как разработчику, так и пользователю.
[+] Теперь одну и ту же копию программы можно установить только на один компьютер.
[(] Если пользователь наметил апгрейд, это грозит и для него и для вас неудобствами.
Система активации.
Алгоритм такой:
1) Пользователь регистрируется, платит денежки и получает регистрационный код. Мы записываем его данные в такую, приблизительно, базу:
Имя |
Фамилия |
E-mail |
Регистрационный код |
Активация |
Здесь, в поле Регистрационный код мы записываем выданный ему регистрационный код, а в поле Активация оставляем значение false до тех пор, пока он не активирует свою копию. То есть, при регистрации запишем false.
2) Пользователь активирует свою копию, то есть указывает полученный регистрационный код (можно вдобавок при регистрации выдавать пароль, по которому точно его авторизируем). После этого в поле Активация надо записать true. Больше использовать этот регистрационный код никто не сможет, то есть если даже взломщик подберёт правильные ключи, то некоторые из них будут уже недоступны, а остальные - в единичном экземпляре.
[+] Гарантирует наиболее полную защиту при хорошей реализации.
[(] Громоздкий способ, хотя его можно сократить только до первого этапа. Нужен хостинг с PHP и базой данных. Необходимо следить ещё и за безопасностью сайта.
Заключение
Я не рассказал вам о способе защиты аппаратным ключом. Это довольно сложный и требующий затрат способ, поэтому мы его и не касаемся.
Свою прогу я продвигаю как бесплатный продукт. Но если когда-нибудь подумаю продавать её за деньги, то для защиты я, скорее всего, выберу именно способ активации, как наиболее надёжный.
Krott.
|