Шифровка — это мощное оружие против взлома, оно бьет точно в цель и высаживает хакера на конкретный геморрой. Есть два вида шифровки: статическая и динамическая.
При статической зашифрованный код/данные расшифровываются один-единственный раз, на самой ранней стадии инициализации программы, после чего расшифрованному коду передается управление. Это программируется просто и ломается еще проще: дождавшись завершения расшифровки, хакер снимает с программы дамп и дизассемблирует его.
Динамические шифровщики расшифровывают код по мере возникновения необходимости в нем и, когда управление будет возвращено, тут же зашифровывают его вновь. Чем меньшие порции кода (данных) расшифровываются за раз, тем лучше (если извернуться, можно расшифровывать по одной машинной команде или байту данных). Таким образом, при динамической шифровке в каждый момент времени в памяти присутствуют только крохотные куски расшифрованного кода/данных и снятие дампа дает немного пользы.
Широкому внедрению динамической шифровки препятствуют следующие проблемы. Во-первых, сложность разработки и трудоемкость отладки. Во-вторых, производительность, точнее, полное отсутствие таковой (что очень критично в играх). В-третьих, возможность снять дамп руками самого расшифровщика, которому последовательно передаются адреса всех зашифрованных регионов. Есть другой способ, при котором легким битхаком его тело исправляется таким образом, чтобы он только расшифровывал, но ничего не зашифровывал. Конечно, способы сопротивления существуют. Например, используют перекрывающие шифроблоки, которые могут быть расшифрованы только по очереди, но не все сразу, или многослойную шифровку типа «луковицы», при которой один шифровщик плюс немного полезного кода вложен в другой, а тот в третий и т.д. Шифровщики как бы перемешаны с кодом, и «отодрать» чистый дамп невозможно. Очень надежно, но реализуется очень и очень сложно.
Бесспорных лидеров среди алгоритмов шифровки нет. Выбор предпочтительного метода защиты не столь однозначен, и статическая шифровка при всех своих недостатках продолжает удерживать прочные позиции, она не собирается сдаваться.
p-код
Термин p-код восходит к интерпретатору Visual Basic’а, но в хакерских кругах означает нечто большее и подразумевает любой интерпретируемый код произвольной виртуальной машины (не путать с VMWare). Непосредственное дизассемблирование в этом случае становится невозможно, и приходится прибегать к декомпиляции, для чего необходимо проанализировать алгоритм работы интерпретатора, написать (и отладить!) декомпилятор, что требует пива и времени. Правда, разработка (и отладка) интерпретатора тоже не обходится даром. Плюс разработка языка тянет за собой кучу вспомогательного инструментария (в первую очередь понадобится отладчик). Иначе как на нем программировать?..
Все это выливается в солидный проект, который может быть использован всего один раз, в одной-единственной программе, причем желательно слегка изменять ядро интерпретатора после выхода нескольких версий, чтобы написанный хакером декомпилятор перестал работать. Что поделаешь, защита требует жертв и больших вложений. Плюс производительность интерпретируемого кода плетется в самом хвосте, отставая от динамической расшифровки и обфускации, но… Может быть, есть возможность реализовать на p-коде только защитные модули? Нет! Тогда их отломают! Не глядя! На p-коде должна быть реализована вся программа целиком, в том числе защитный механизм, тогда без декомпилятора его будет не хакнуть. Но это все теория. На практике полностью загнать программу в p-код не удается из-за производительности и критичные к быстродействию функции пишутся на языке высокого уровня или даже ассемблере. Зато вызываются они уже из p-кода, в котором сосредоточена основная логика по типу «если нельзя, то все-таки».
Кстати, большинство игровых миров управляются своим собственным скриптовым языком, который описывает движение облаков, поведение мячика и характер разных монстров. На чистом С не запрограммируешь никакую игру сложнее «тетриса»! Если мы уже имеем скриптовый язык, то почему бы не включить в него несколько защитных функций? И в этом случае, чтобы взломать программу, хакеру придется разобраться в работе скриптового движка.
Чаще всего разработчики используют Pascal- или Basic-подобные языки — не самый лучший выбор в плане защищенности. Программа на p-коде при этом представляет собой последовательность ключевых слов (операторов языка) с аргументами и декомпилируется очень просто. На другом конце шкалы сложности находится Машина Тьюринга, Сети Петри, Стрелка Пирса и прочие примитивные виртуальные машины, которые реализуют фундаментальные логические операции, в результате чего даже такая конструкция, как «IF THEN ELSE», распадается на сотни микрокоманд! Прежде чем хакер проанализирует их, солнце успеет погаснуть, или… Может, все-таки не погаснет? Существует множество продвинутых способов наглядной визуализации таких алгоритмов, к тому же мы опять забываем о производительности… Реализовать crackme на базе Машины Тьюринга еще можно, но коммерческое приложение — едва ли.
Хорошая идея — приложить к этому делу Форт. Его преимущества: простота реализации Форт-машины, компактность p-кода, довольно высокая производительность, сложность декомпиляции и ни-на-что-непохожесть. Форт стоит особняком от всех языков, совсем не стремясь соответствовать человеческим привычкам, «здравому смыслу» и «логике». Разобраться в его идеологии с нуля непросто. Хакеру придется нарыть кучу литературы и запастись терпением. Даже если ему не наскучит, разработчик защиты получит хорошую фору по времени…
Листинг
дизассемблерный листинг Форт-машины (из программы see.exe, поставляемой вместе с Interrupt List’ом)
seg000:1F29 loc_1F29: ; CODE XREF: start+39B9vj
seg000:1F29 call sub_1F04
seg000:1F2C mov word_A5C, bx
seg000:1F30 mov si, dx
seg000:1F32 mov bp, [bx+38h]
seg000:1F35 mov sp, [bx+36h]
seg000:1F38 lodsw
seg000:1F39 mov bx, ax
seg000:1F3B jmp word ptr [bx]
Другая хорошая идея — использовать готовые интерпретаторы малораспространенных языков (Пролог, Хаскель, Оберон), под которые еще не написаны декомпиляторы. Многие из них разработаны в недрах исследовательских институтов и поставляются в исходных текстах, что упрощает модификацию ядра интерпретатора и позволяет легко адаптировать чужой язык к своим целям.
Круче всего — нарыть в Сети эмулятор малоизвестной ЭВМ, под которую есть компилятор с языка высокого уровня (С, ФОРТАН), и… Хакеру придется очень худо, придется осваивать неизвестный ему машинный язык, искать дизассемблер (или, скорее, писать собственный), прилагая титанические усилия для взлома. Разработчик тем временем будет пить пиво и кодить в привычной для него среде. Самое замечательное — то, что в следующей версии программы можно использовать эмулятор от «другой» ЭВМ, перекомпилировав весь код без адаптации и каких-либо изменений. Точнее, совсем без изменений, конечно, не получится, но трудоемкость переноса не сравнить с тем объемом работы, который предстоит проделать взломщику!
Секреты привязки
Рассуждая о методах защиты, мы не касались вопроса привязки, то есть такой характеристики, которая бы отличала одну копию программы от другой. Самые надежные защиты основаны на привязке к носителю. Они же — самые капризные и глюкавые.
Практически нереально создать защиту, которая безошибочно распознавалась бы любым приводом (включая что-то сильно раздолбанное или слишком нестандартное), но в то же время не копировалась бы ни одним известным копировщиком. Лучший результат дает привязка к геометрии спиральной дорожки (защиты типа CD-COPS, Star-Force) — ее невозможно скопировать, но легко проэмулировать или… подобрать диск с похожими характеристиками. Для предотвращения эмуляции разработчикам Star-Force пришлось очень глубоко зарыться в систему, в результате чего получился полный глюкодром, а установка очередного пакета обновлений на Windows требует параллельной установки соответствующего обновления для Star-Force. Так что лучше похоронить эту тему раз и навсегда и даже не пытаться возвращаться к ней. Иначе столкнешься с таким количеством проблем, осилить которое сможет только крупная компания, да и то…