|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Автор: GPcH. Дата публикации: 03.05.2005
|
Пишем профессиональную защитуЛикбез о защите прог на Visual BasicМне встречалось множество статей, расписывающих методику написания функций проверки регистрации в shareware-прогах, но в большинстве своем они были написаны людьми, не имеющими опыта в исследовании защит, а следовательно - описываемые методики защищали программу лишь от начинающих крэкеров. Опыт показывает, что новички не представляют опасности для автора shareware-программ, так как они не релизят крэки и об их "грандиозных взломах" узнают лишь немногие. Настоящие же крэкеры чаще всего входят в одну из хак-групп, которые публикуют релизы на крайне любимых поисковиками типа astalavista, сайтах. Поэтому разбирать мы будем защиту именно от таких крэкеров. Языки программирования Как известно, про защиту программ, написанных на Delphi и C++ рассказано уже довольно много. Большинство авторов уже сошлись на мнении, что лучше всего для них использовать всем известные навесные защиты (я не буду их называть - ты и сам про них знаешь :)). Последние версии этих защит настолько мощно защищают таблицу импорта и код, что можно считать их отличным средством для защиты Delphi и C++ приложений. Совсем иначе обстоит дело с программами на Visual Basic 6.0. Навесные защиты пока не научились защищать VB-код и снимаются не сложнее упаковщиков (подробнее можно прочитать в одной из моих статей на www.dotfix.net), что сильно огорчает VB-программеров. Давай разберемся, с чем же это связано. Начнем с таблицы импорта, то есть, с той самой таблички, что хранит адреса и имена вызываемых программой функций. Она в основной своей массе вызывает не стандартные API функции, а их аналоги из библиотеки MSVBVM60.DLL. Это, вместе с необходимостью подстраиваться под все версии VB (а каждая версия VB привязывает создаваемое приложение к собственной версии рантайм-библиотеки MSVBVMXX.DLL, где XX - номер версии VB), создает большие проблемы для навесной защиты. Не будем вникать в проблемы защиты импорта, а поговорим немного про защиту методом "спертых байт", которую в последнее время используют многие навесные защиты. Она представляет собой перемешивание с мусором части кода программы (обычно - несколько десятков байт от точки входа) и мешает крэкеру восстановить программу после снятия с нее навесной защиты. Этот метод также не прокатит в программах, написанных на VB, потому что на точке входа в программу можно замусорить лишь 2 ассемблерные инструкции: "push <смещение кода программы>" и "call <MSVBVM60.ThunRTMain>", остальное инициализируется самой функцией ThunRTMain, и перед ее вызовом все должно быть в незашифрованном виде. В основном, именно это не дает создать нормальную навесную защиту для программ, написанных на VB. Ниже я расскажу про наиболее сложные и неломаемые защиты, которые ты можешь реализовать сам в своих программах. Итак, приступим. Метод "глючной арифметики" Для начала напишем функцию, генерящую правильный пароль. В функцию будет передаваться имя пользователя, а она должна, отталкиваясь от него, генерить уникальный пароль, действительный только для этого имени. Самое простое - использовать криптовку XOR’ом. При этом процедуры генерации кода по имени и наоборот могут выглядеть так, как это изображено ниже. Генерация пароля из имени пользователя
Получение имени пользователя из пароля
Как видно из функций - они похожи и это является одним из свойств логической операции XOR - эта операция полностью обратима. Вторая функция в данном случае отличается от первой лишь преобразованием HEX в CHR (без этого не обойтись, так как если мы не будем преобразовывать пароль в HEX в первой функции - в нем могут появиться непечатаемые символы, что явно не понравится конечному пользователю :)). Теперь, когда пользователь введет пароль и имя, мы сможем легко проверить правильность этих данных, сгенерировав пароль функцией GetPass по имени и сравнив с паролем, что ввел юзер. В случае различия кодов мы можем вывести сообщение об ошибке. Но и это еще не все. Создадим глобальные переменные strName и strPass в разделе объявлений любого модуля:
И занесем в них имя и пароль, введенные пользователем. Зачем это нужно? Во всех расчетах в программе, в конце вычисления, мы будем плюсовать результат вычитания первого (введенного пользователем) и второго (который нам вернет процедура GetPass) пароля. Что это нам даст? Если пароли равны, то плюсоваться будет ноль и результат вычисления не изменится, в противном случае программа попросту начнет работать не так как нужно, поскольку результат ее работы будет неверным. Вот небольшой пример использования данного метода, если нам нужно посчитать произведение числа 2 на 2:
В результате, если пароли будут одинаковы, то прибавляться будет ноль, иначе (если крэкер взломал процедуру проверки) - пароли будут различны и программа начнет глючить. К чему это приведет? Пользователь скачает крэк, поработает с взломанной прогой, заметит в ней кучу глюков и, если программа действительно ему нужна, переустановит ее и купит. Крэкеру же убрать все проверки будет крайне тяжело, так что не поленись их ввести везде, где прога выполняет арифметические вычисления. Если вычислений в твоей проге нет, результат сравнения легко можно пихать в вызов, например, диалоговых окон. Как? Очень просто:
Если параметр будет не ноль и ты не напишешь что-нибудь типа "on error resume next", то прога может просто вызвать недопустимую операцию после взлома, так как в качестве параметра для функции загрузки формы в случае неверного пароля может передаваться что угодно. Однако, этот способ защиты с трудом, но можно обойти. Ниже я рассмотрю действительно мощные алгоритмы, которые, тем не менее, желательно использовать совместно с рассмотренным методом "глючной арифметики". Вызов функции по имени К примеру, нам нужно, чтобы пункт "Сохранить" в программе был доступен только после регистрации ее юзером. Для этого пишем отдельно функцию сохранения и именуем ее, к примеру "save" (почему выбрано такое маленькое имя, станет понятно дальше). Теперь нам нужно, чтобы из имени пользователя можно было получить слово save. Простейший способ - это использовать уже знакомый нам XOR. Для этого будем ксорить имя пользователя с этим словом побайтно: user name XOR savesaves Если имя пользователя больше 4 символов, мы просто нарастим второй параметр криптовки (слово "save") до нужного нам размера (минимальное имя пользователя - 4 символа). Результат XOR’а это и есть пароль, который мы дадим пользователю, когда он купит нашу программу. Как известно, операция XOR обратима, то есть мы легко можем из имени и пароля получить обратно строку "savesaves", проксорив имя с паролем. Саму же строчку "save" мы легко получим, считав первые 4 символа. Надеюсь, ты помнишь, что введенные пользователем данные нужно хранить в глобальных переменных? Так вот, в обработчик кнопки "Сохранить" мы напишем следующее:
callbyname здесь – это весьма позитивная штука, поскольку данный оператор присутствует только в Visual Basic’е и не имеет аналогов ни в Delphi, ни в C++. Служит он для вызова функции по ее имени, которое может храниться где угодно, включая переменные. Первым параметром данной функции служит объект, который содержит вызываемую нами функцию, вторым - имя функции и третьим - тип функции (vbGet, vbLet, vbMethod, vbSet). Имя функции мы будем получать из имени пользователя и его пароля функцией GetFunction. Соответственно, если мы проXORим верное имя с паролем, то первые четыре символа будут именем функции - их и возвратит функция GetFunction (посмотреть ее код можно на врезке 1). В противном случае GetFunction может возвратить что угодно, но не "save", при этом программа сглючит. Чтобы этого не произошло - напиши код так:
Теперь программа просто выведет сообщение, в случае если пароль неправильный. И пусть крэкер ломает функцию "on error" чтобы сообщение не выводилось - все равно без пароля программа работать не будет. Небольшое предостережение: если в твоей программе функций мало, то крэкер оттрассирует перебираемые функцией callbyname варианты и методом подбора найдет нужную функцию, подставит в процедуру генерации пароля и получит код. Поэтому эту защиту есть смысл применять только в больших проектах, где функций несколько десятков или сотен и все перебрать крэкеру будет просто лень. ![]() Мой первый CrackMe, использующий в качестве проверки пароля метод вызова функции по имени. Его сломали лишь потому, что в нем функций мало А теперь поговорим про действительно хардкорный метод защиты программ паролем - использование ассемблерной функции. Пароль - функция на ассемблере Вот мы и дошли до самого интересного. А что, если в качестве пароля использовать ассемблерную функцию, возвращающую одну из составляющих имени пользователя, например, ASCII-код второго символа имени? Неплохо, но что это нам даст? Это нам позволит сравнить пароль пользователя с результатом работы ассемблерной функции, имя же пользователя мы можем использовать в качестве ключа для шифровки ассемблерной функции от чужих глаз. Я для этих целей использую алгоритм blowfish. У этого алгоритма есть одна особенность - в качестве ключа для шифровки он принимает только цифры, поэтому проще шифровать не всем именем пользователя, а например его контрольной суммой. Это я думаю, ты реализуешь сам, здесь же для простоты мы будем шифровать ASCII-кодом третьего символа имени пользователя. То есть - пользователь вводит имя и пароль, мы декриптуем пароль третьим символом имени и запускаем полученную в результате декриптовки ассемблерную функцию с помощью API функции CallWindowProc. Функция должна нам возвратить ASCII-код второго символа имени. Если это так - пользователь ввел верный пароль, иначе, если в качестве пароля был введен просто мусор - произойдет ошибка либо при декриптовке, либо при вызове этого мусора и прога вызовет недопустимую операцию, от этого нас спасет уже известный нам On Error GoTo lamo :). Хотя передача мусора непосредственно в CallWindowsProc крайне нежелательна (представь что будет, если процессору на исполнение пойдет мусор - так можно и винт форматнуть по глупости). Но я тебя обрадую - если пользователь введет мусор вместо пароля, то ошибка в 99% случаев произойдет в функции декриптовки и до процессора дело не дойдет. Сама же ункция проверки в общем виде представлена в листинге 3. Листинг 3
Листинг 4
Повторюсь: функция представлена в общем виде. Для ее работоспособности нам потребуется объявить API функцию CallWindowsProc:
и глобальный массив bytes:
в разделе объявлений программы, а также - подключить класс модуль blowfish и объявить его так:
Собственно сама ассемблерная функция должна иметь вид:
12 - возвращаемый символ, он передается в регистр eax и должен генериться генератором ключей для твоей проги в зависимости от второго ASCII-символа имени пользователя. С генератором ключей придется потрудиться, так как тебе придется написать функцию, которая будет изменять этот символ, перекомпилировать ассемблерную программу и шифровать ее. Я, правда сделал проще, чего и тебе советую – просто откомпилируй один вариант ассемблерной функции, дизассемблируй его и погляди, где 12 заносится в eax и пусть твой кейген меняет этот байт, а не перекомпилирует все заново. Теперь осталось разобраться, как же откомпилировать эту функцию в машинный код? Для этого лучше использовать компилятор ассемблера nasm, так как он умеет создвать не EXE, а BIN-файлы. Этот BIN-файл мы и будем криптовать BlowFish’ем и при вводе этой криптованной строки юзером декриптовывать и заносить в массив байт. Чтобы пользователю удобнее было вводить пароль - шифруй функцию с установкой параметра HEX в true, тогда BlowFish будет возвращать шестнадцатеричные коды байт. При этом пароль увеличится в 2 раза, но будет состоять только из цифр и букв от A до F. ![]() Мой второй CrackMe, использующий в качестве последних двух паролей ассемблерный код. На момент написания статьи его еще никто не закейгенил, хотя лежит в сети он уже месяцев 6 Подробнее о вставке ассемблерных процедур в код на VB можно прочитать в 2 моих статьях на эту тему на сайте www.dotfix.net. ![]() Окно регистрации моей программы DotFix FakeSigner. При защите своих программ я использую многие методы сразу - это увеличивает стойкость защиты. Как можно догадаться "глючную арифметику" в этом методе также рекомендуется использовать, так как крэкер вряд ли сможет написать генератор ассемблерных процедур, не зная, что твоя программа способна на такие приколы. Вывод Если, прочитав про оследний метод защиты, ты ничего не понял, но имеешь желание разобраться, придется ознакомиться с работой ассемблерных процедур, запускаемых из-под VB и почитать документацию к ассемблеру nasm (лежит также на моем сайте). Только тогда все встанет на свои места. Описанные методы - практически максимум, что можно выжать из VB в плане защиты. Все необходимое (класс модуль blowfish, документацию по nasm’у и электронные варианты моих статей по вставке ассемблерных процедур в код на Visual Basic), естественно, можно найти и на диске. Врезка 1
Модуль со всеми описанными в статье функциями сливай с http://www.dotfix.net/xdocsrc.rar INFO Полезные статьи по теме ты всегда можешь найти на сайте www.dotfix.net. Там же почитай про использование ассемблерных процедур. Еще советую тебе шифровать строки в программах, для этого скачай прогу VB AntiCrack. WARNING Помни об особенностях третьего метода и используй его с осторожностью. Также не забывай, что если твоя прога жутко полезна и стоит очень дорого - ее все равно рано или поздно взломают.
|
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||