DotFix :: Портал разработки и защиты программ
Главная
Программы
Статьи
Разное
Форум
Контакты
Автор: 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’ом. При этом процедуры генерации кода по имени и наоборот могут выглядеть так, как это изображено ниже.

Генерация пароля из имени пользователя

CODE NOW!

’циклично шифруем каждый символ имени пользователя числом n, которое варьируется от 0 до 10
Public Function GetPass(sName As String)
Dim n As Byte
For i = 1 To Len(sName)
sPass = sPass & Hex(Asc(Mid$(sName, i, 1)) Xor n)
n = n + 1
If n > 10 Then n = 0
Next
GetPass = sPass
End Function



Получение имени пользователя из пароля

CODE NOW!

Public Function GetName(sPass As String)
Dim n As Byte
For i = 1 To Len(sPass) Step 2
sName = sName & Chr(Val("&H" & Mid$(sPass, i, 2)) Xor n)
n = n + 1
If n > 10 Then n = 0
Next
GetName = sName
End Function



Как видно из функций - они похожи и это является одним из свойств логической операции XOR - эта операция полностью обратима. Вторая функция в данном случае отличается от первой лишь преобразованием HEX в CHR (без этого не обойтись, так как если мы не будем преобразовывать пароль в HEX в первой функции - в нем могут появиться непечатаемые символы, что явно не понравится конечному пользователю :)). Теперь, когда пользователь введет пароль и имя, мы сможем легко проверить правильность этих данных, сгенерировав пароль функцией GetPass по имени и сравнив с паролем, что ввел юзер. В случае различия кодов мы можем вывести сообщение об ошибке. Но и это еще не все. Создадим глобальные переменные strName и strPass в разделе объявлений любого модуля:

CODE NOW!

public strName as string
public strPass as string



И занесем в них имя и пароль, введенные пользователем. Зачем это нужно? Во всех расчетах в программе, в конце вычисления, мы будем плюсовать результат вычитания первого (введенного пользователем) и второго (который нам вернет процедура GetPass) пароля. Что это нам даст? Если пароли равны, то плюсоваться будет ноль и результат вычисления не изменится, в противном случае программа попросту начнет работать не так как нужно, поскольку результат ее работы будет неверным. Вот небольшой пример использования данного метода, если нам нужно посчитать произведение числа 2 на 2:

CODE NOW!

srtResult=(2*2)+(val("&H" & GetPass(strName))-val("&H" & strPass))



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

CODE NOW!

frmMain.Show (val("&H" & GetPass(strName))-val("&H" & strPass))



Если параметр будет не ноль и ты не напишешь что-нибудь типа "on error resume next", то прога может просто вызвать недопустимую операцию после взлома, так как в качестве параметра для функции загрузки формы в случае неверного пароля может передаваться что угодно. Однако, этот способ защиты с трудом, но можно обойти. Ниже я рассмотрю действительно мощные алгоритмы, которые, тем не менее, желательно использовать совместно с рассмотренным методом "глючной арифметики".

Вызов функции по имени

К примеру, нам нужно, чтобы пункт "Сохранить" в программе был доступен только после регистрации ее юзером. Для этого пишем отдельно функцию сохранения и именуем ее, к примеру "save" (почему выбрано такое маленькое имя, станет понятно дальше). Теперь нам нужно, чтобы из имени пользователя можно было получить слово save. Простейший способ - это использовать уже знакомый нам XOR. Для этого будем ксорить имя пользователя с этим словом побайтно:
user name
XOR
savesaves
Если имя пользователя больше 4 символов, мы просто нарастим второй параметр криптовки (слово "save") до нужного нам размера (минимальное имя пользователя - 4 символа). Результат XOR’а это и есть пароль, который мы дадим пользователю, когда он купит нашу программу. Как известно, операция XOR обратима, то есть мы легко можем из имени и пароля получить обратно строку "savesaves", проксорив имя с паролем. Саму же строчку "save" мы легко получим, считав первые 4 символа. Надеюсь, ты помнишь, что введенные пользователем данные нужно хранить в глобальных переменных? Так вот, в обработчик кнопки "Сохранить" мы напишем следующее:

CODE NOW!

callbyname frmMain, GetFunction(strName, strPass), vbMethod



callbyname здесь – это весьма позитивная штука, поскольку данный оператор присутствует только в Visual Basic’е и не имеет аналогов ни в Delphi, ни в C++. Служит он для вызова функции по ее имени, которое может храниться где угодно, включая переменные. Первым параметром данной функции служит объект, который содержит вызываемую нами функцию, вторым - имя функции и третьим - тип функции (vbGet, vbLet, vbMethod, vbSet). Имя функции мы будем получать из имени пользователя и его пароля функцией GetFunction. Соответственно, если мы проXORим верное имя с паролем, то первые четыре символа будут именем функции - их и возвратит функция GetFunction (посмотреть ее код можно на врезке 1). В противном случае GetFunction может возвратить что угодно, но не "save", при этом программа сглючит. Чтобы этого не произошло - напиши код так:

CODE NOW!

On Error GoTo lamo
Callbyname frmMain,vbMethod, GetFunction(strName, strPass)
Exit Sub
lamo: msgbox "Пасс неверный"



Теперь программа просто выведет сообщение, в случае если пароль неправильный. И пусть крэкер ломает функцию "on error" чтобы сообщение не выводилось - все равно без пароля программа работать не будет. Небольшое предостережение: если в твоей программе функций мало, то крэкер оттрассирует перебираемые функцией callbyname варианты и методом подбора найдет нужную функцию, подставит в процедуру генерации пароля и получит код. Поэтому эту защиту есть смысл применять только в больших проектах, где функций несколько десятков или сотен и все перебрать крэкеру будет просто лень.


Мой первый CrackMe, использующий в качестве проверки пароля метод вызова функции по имени. Его сломали лишь потому, что в нем функций мало

А теперь поговорим про действительно хардкорный метод защиты программ паролем - использование ассемблерной функции.

Пароль - функция на ассемблере

Вот мы и дошли до самого интересного. А что, если в качестве пароля использовать ассемблерную функцию, возвращающую одну из составляющих имени пользователя, например, ASCII-код второго символа имени? Неплохо, но что это нам даст? Это нам позволит сравнить пароль пользователя с результатом работы ассемблерной функции, имя же пользователя мы можем использовать в качестве ключа для шифровки ассемблерной функции от чужих глаз. Я для этих целей использую алгоритм blowfish. У этого алгоритма есть одна особенность - в качестве ключа для шифровки он принимает только цифры, поэтому проще шифровать не всем именем пользователя, а например его контрольной суммой. Это я думаю, ты реализуешь сам, здесь же для простоты мы будем шифровать ASCII-кодом третьего символа имени пользователя. То есть - пользователь вводит имя и пароль, мы декриптуем пароль третьим символом имени и запускаем полученную в результате декриптовки ассемблерную функцию с помощью API функции CallWindowProc. Функция должна нам возвратить ASCII-код второго символа имени. Если это так - пользователь ввел верный пароль, иначе, если в качестве пароля был введен просто мусор - произойдет ошибка либо при декриптовке, либо при вызове этого мусора и прога вызовет недопустимую операцию, от этого нас спасет уже известный нам On Error GoTo lamo :). Хотя передача мусора непосредственно в CallWindowsProc крайне нежелательна (представь что будет, если процессору на исполнение пойдет мусор - так можно и винт форматнуть по глупости). Но я тебя обрадую - если пользователь введет мусор вместо пароля, то ошибка в 99% случаев произойдет в функции декриптовки и до процессора дело не дойдет. Сама же ункция проверки в общем виде представлена в листинге 3.

Листинг 3

CODE NOW!

’получим ассемблерный код
sASM = BlowFish.DecodeString(strPass, Asc(Mid$(strName, 3, 1)))
’переведем его в массив байт
’такой функции нет в бейсике, ее ты найдешь в листинге 4
’или можешь написать сам, благо это дело пяти минут
Call ToBytes(sASM)
’вызываем ассемблерную функцию
’для этого передаем API функции CallWindowsProc
’адрес на первый байт ассемблерного кода
sASCII = CallWindowProc(VarPtr(bytes(0)))
’сравниваем
If sASCII = Asc(Mid$(strName, 2, 1)) Then
MsgBox "Пароль верный"
Else
MsgBox "Пароль неверный"
End If



Листинг 4

CODE NOW!

Private Sub ToBytes(strBin As String)
For i = 0 To Len(strBin) - 1
Bytes(i) = Asc(Mid$(strBin, i + 1, 1))
Next
End Sub



Повторюсь: функция представлена в общем виде. Для ее работоспособности нам потребуется объявить API функцию CallWindowsProc:

CODE NOW!

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA"
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long,
ByVal lParam As Long) As Long



и глобальный массив bytes:

CODE NOW!
Public bytes() as byte



в разделе объявлений программы, а также - подключить класс модуль blowfish и объявить его так:

CODE NOW!
Dim BlowFish As New clsBlowFish



Собственно сама ассемблерная функция должна иметь вид:

CODE NOW!

[bits 32]
mov eax, 12



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

CODE NOW!

Public Function GetFunction(xStringToCrypt, xStringKey)
’если имя функции меньше имени пользователя - нарастим
If Len(xStringKey) < Len(xStringToCrypt) Then
For i = 1 To Len(xStringToCrypt)
xStringKey = xStringKey & xStringKey
If Len(xStringKey) > Len(xStringToCrypt) Then Exit For
Next
xStringKey = Mid$(xStringKey, 1, Len(xStringToCrypt))
’иначе урезаем имя функции :) - это недопустимо - сделай проверку этого сам
Else
xStringKey = Mid$(xStringKey, 1, Len(xStringToCrypt))
End If
’шифруем
For i = 1 To Len(xStringToCrypt)
sCrypt = Asc(Mid$(xStringKey, i, 1)) Xor Asc(Mid$(xStringToCrypt, i, 1))
sCryptedString = sCryptedString & Chr(sCrypt)
Next
sRepeateString = InStr(2, sCryptedString, Left$(sCryptedString, 4))
If sRepeateString > 0 Then sCryptedString = Left$(sCryptedString, sRepeateString - 1)
GetFunction = sCryptedString
End Function



Модуль со всеми описанными в статье функциями сливай с http://www.dotfix.net/xdocsrc.rar

INFO

Полезные статьи по теме ты всегда можешь найти на сайте www.dotfix.net. Там же почитай про использование ассемблерных процедур. Еще советую тебе
шифровать строки в программах, для этого скачай прогу VB AntiCrack.

WARNING

Помни об особенностях третьего метода и используй его с осторожностью. Также не забывай, что если твоя прога жутко полезна и стоит очень дорого - ее все равно рано или поздно взломают.



Комментарии
Добавил: gl.sys Дата: 03.05.2005

Метод "глючной арифметики" на самом деле не так хорош как пишет автор, эта защита подходит для его кракмисов, но не для shareware продуктов. А если легальный пользователь ошибется при вводе серийника?

Добавил: GPcH Дата: 05.05.2005

Ты наверное плохо прочитал статью. Для избежания проблем перед использованием "глючной арифметики" нужно писать:
on error resume next
При этом любая ошибка не приведет к краху программы. Если отключать проверку ошибок нет желания - можно ввести дополнительные проверки серийника. В любом случае все ошибки можно обработать и на легального пользователя это не повлияет, а вот на того, кто скачает кривой кряк может и повлияет, но тут уже никаких гарантий, так как кряк писать будешь уж точно не ты, потому и нкаких гарантий работоспособности взломанной проги ты давать не обязан никому.


Добавление комментария

Ваше имя (на форуме):

Ваш пароль (на форуме):

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

Комментарий:



04.09.2011 Долгожданный релиз VB Decompiler. Масса улучшений декомпиляции Native Code. Значительно расширенна и обновлена справочная система на русском и английском языках.
20.12.2010 DotFix Software поздравляет наших клиентов и посетителей сайта с наступающим Новым Годом и рождеством! Желаем приятно провести праздники и успехов в новом году!
28.11.2010 Выпущена новая версия защиты DotFix NiceProtect. Основные изменения коснулись обфускатора Delphi программ. Теперь имеется полная поддержка Tab и Page контролов на формах, что обеспечивает максимальную совместимость обфускации с Delphi XE программами.
21.10.2010 Обновлен декомпилятор Visual Basic программ до версии 8.1. Декомпиляция P-Code программ становится все более идеальной, также проделана большая работа по улучшению анализа Native Code и .NET приложений.
16.09.2009 Полностью обновлен движок сайта! Теперь все ссылки имеют читаемый понятный вид, разного рода глюки на страницах убраны. И теперь сайт полноценно работает на второй версии нашего движка.
Архив новостей
Яндекс цитирования

Движок сайта: DotFix Engine v0.2
Администрация сайта: