Мы уже достаточно многое обсудили в рамках этой темы до Нового года, однако разговор прервался, в то время как его не помешало бы закончить. Поэтому будем плавно двигаться к логическому завершению, стараясь не останавливаться на малозначащих подробностях.
Пересказывать содержание предыдущих статей, думаю, не стоит, просто сошлюсь на прошлогодние выпуски "КВ", где вы можете найти все материалы из серии.
Что ж, давайте поговорим, каким именно образом можно помешать взломщику в том, что принято называть reverse engineering'ом. Методов, на самом деле, достаточно много, и лучше применять каждый из них, потому что, сами понимаете, чем больше вы усложните задачу взломщику, тем больше шансов, что он, как минимум, в чем-то ошибется, или, в идеальном случае, вовсе откажется от своих намерений по взлому защиты созданного вами программного продукта.
Итак, приступим к рассмотрению тех самых способов, которые должны обеспечить нам защиту от нехороших и падких на халяву людей. И пойдем мы в этом, конечно же, от простого к сложному.
Первый оборонительный рубеж
Первый рубеж обороны против злокозненных взломщиков - это отсутствие в коде программы текста, непосредственно указывающего на те места, в которых реализованы компоненты защиты. Речь идет о строковых ресурсах, названиях ваших собственных функций, а также вызовах системных API, которые подсказывают "кодокопателю", где именно нужно искать код, отвечающий за лицензирование и защиту программы.
Давайте поговорим обо всех компонентах "первого рубежа" по порядку. Начнем со строковых ресурсов. Если после попытки регистрации ваша программа сразу выдаёт на-гора сообщение наподобие "Thank you for registering our product"/"The registration code is not valid", причём это сообщение само по себе хранится в незашифрованном даже минимальным образом виде в строковых ресурсах приложения, то найти его не составит никакого труда, а найдя, злоумышленник сможет быстро обнаружить способ модифицировать программный код приложения, чтобы помешать корректной работе механизмов защиты. Посему лучше хранить такие сообщения в более-менее измененном виде. Не обязательно, конечно, защищать их с помощью алгоритмов стойкой криптографии RSA или AES, но хотя бы банальное XOR'енье или даже "замусоривание" текста с помощью каких-то дополнительных букв через одну должно присутствовать.
Дальше - имена функций. Это, пожалуй, даже более серьезно, потому что сам по себе используемый строковый ресурс может отстоять в ассемблерном коде, с которым имеет дело взломщик, не на одну тысячу линий от, собственно, той функции проверки правильности регистрации программного продукта, которая его, в первую очередь, интересует. А вот само название этой функции, выполненное в духе VerifyRegistrationCode, будет подсказкой, лучше которой придумать просто невозможно. Потому что теперь взломщику вообще не нужно копаться в хитросплетениях вашего алгоритма проверки регистрационного кода, чтобы писать кодогенератор или вручную подбирать ключ (кстати, о подборе ключа... впрочем, об этом лучше немного ниже и отдельным пунктом). Ему достаточно удалить весь код из вашей функции с таким всесторонне замечательным названием и заменить его буквально на одну строчку, выдающую в качестве результата всегда положительное значение. Так что лучше называть подобные функции (а она не должна быть в программе в гордом одиночестве - чем больше проверок ключей, тем надёжнее защита) какими-то менее говорящими именами. Пусть лучше она называется CheckOSSettings или LoadInterfaceLanguage. Боитесь запутаться? Напишите в комментариях к коду или на бумажке (бумажку лучше потом сжечь). В общем, как бы то ни было, лучше назвать функцию так, чтобы её название служило отвлекающим манёвром, а самому запомнить её содержание каким-нибудь другим образом. Всё сказанное, кстати, относится и к названию ресурсов, внешних библиотек, переменных, классов и т.п.
Теперь по поводу используемых функций API. Если вы показываете пользователю сообщения о регистрации с помощью MessageBox, то это, в общем-то, ещё полбеды, потому что вызовов подобных функций в программе обычно вагон и маленькая тележка. А вот если вы используете, например, функции для работы с датой, чтобы определить, когда у вашей программы кончается испытательный срок, то здесь уже сложно утаить шило в мешке. Универсального списка функций, как и универсального рецепта, здесь нет. В случае с датой можно, например, попробовать узнавать дату последнего изменения файла с системным реестром или файла подкачки, которая будет с точностью совпадать с текущей датой. Ну и в случае с другими аналогичными функциями, которые взломщик будет искать в первую очередь для того, чтобы понять, где именно следует "ковырять" ваш программный код, нужно по возможности прибегнуть к другим таким же ухищрениям.
Выжидательная позиция
Как я уже говорил выше, проверки регистрационного кода и другие защитные процедуры должны быть разнесены, и не только по коду вашего приложения, но и по времени. Зачем? Затем, что сами взломщики, в отличие от способных купить ваше детище пользователей, вряд ли станут по нескольку часов просиживать за программой, работая с ней. Они взломают, поместят "на обложку" патча или кодогенератора свой ник и развесят гордо архив со взломом вашего продукта на каком-нибудь из "варезных" порталов. На этом их "общение" с созданной вами программой с вероятностью 95% закончится (конечно, бывают исключения, но наибольшую вероятность добиться пристального внимания "кулхацкеров" имеют авторы средств защиты ПО, которые вряд ли будут читать эту статью). Поэтому если вы проверите правильность регистрационного кода через несколько минут после старта программы, то, скорее всего, эта проверка так и останется незамеченной для взломщика, и выпущенный им кодогенератор будет генерировать существенно ограниченные по времени ключи, то есть будет не особенно полезен для желающих получить полную "халяву" пользователей. Само собой, проверка основная и неосновная должны отличаться на уровне программного кода, чтобы взломщик не нашёл её банальным поиском. Возможно, этот метод покажется вам недостаточно эффективным, и вы посчитаете, что он сработает только с недалёким взломщиком - что ж, не буду тратить газетную площадь на убеждения сомневающихся, всё равно его простота позволяет легко выделить время и силы на его реализацию, потому что остальная программа от выделенных вами на него тридцати минут ну уж точно никак не пострадает.
Подбор ключа
Бывает и так, что взломщики не копаются в коде программного продукта, а просто подбирают регистрационный код, как некоторые другие не слишком чистые на руку товарищи подбирают пароли буквально ко всему, что только можно запаролить, начиная от операционной системы на ноутбуке и заканчивая экаунтом на "Фейсбуке". Конечно, не сказать, чтобы это делалось часто, потому что, с точки зрения взломщика, такой способ очень уж не спортивный и не вызывает уважения со стороны "однопартийцев", но он существует, и есть даже специальные инструменты автоматизации взлома, которые дают возможность подобрать регистрационный код обычному пользователю, не владеющему премудростями ассемблера, да и не сильно старающегмуся ими овладеть.
Защита от этой напасти ещё более проста, чем фокус с таймером. Достаточно перед выводом сообщения о неправильном регистрационном коде (да, впрочем, и о правильном тоже) сделать паузу в несколько секунд, которая напрочь отобьёт у любителей "халявы" желание подбирать регистрационный код в автоматическом режиме - потому что на такой подбор уйдёт такое количество времени, что, если верить формуле "время = деньги", получившийся на выходе ключ окажется просто золотым.
Хранение и обработка ключа
Ключ и регистрационные данные пользователя после их введения в диалог регистрации нужно где-то хранить, и тут уж, что называется, ничего не попишешь - с этим можно особенно не "заморачиваться", просто записывать всё в открытом виде в реестр или в текстовый файл в директории, где находится само приложение. А вот работать в программе с ними нужно аккуратно.
Ни в коем случае не нужно создавать глобальные переменные со значениям ключа и имени пользователя и всё время работать с ними. Лучше для каждой отдельной проверки делать новую копию в новой переменной, локальной в рамках данной проверочной процедуры, и копировать значения регистрационных данных не банальным присваиванием, а каким-нибудь более сложным и хитрым путём - скажем, перебирать по одной букве, добавить XOR'енье, собрать из трех разных переменных и т.д. и т.п. В общем, тут ваша фантазия будет заметным подспорьем, затруднив анализ программного кода, что уже достаточно неплохо.
Как видите, все перечисленные методы достаточно просты и очень хорошо подходят для начальных версий программы, когда нет смысла вкладывать много сил и средств в качественную и действительно надежную защиту, а совсем без защиты выставлять программу тоже как-то не комильфо. В следующий раз поговорим о более сложных и продвинутых способах защиты, которые уже имеет смысл реализовывать с помощью профессиональных решений других разработчиков.
Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by