Особое замечание о создании защитного кода на ассемблере
Защитные механизмы, бесспорно, предпочтительнее всего реализовывать на голом ассемблере, используя максимум трюков и извращений. Эффективность ассемблера здесь вторична, главное – максимально запутать взломщика. Компиляторы же генерируют достаточно предсказуемый код и почерк каждого из них профессиональным хакерам хорошо известен. Достоинства ассемблера в том, что он практически не ограничивает полет фантазии и позволяет воплощать в жизнь практически любые идеи. Полиморфный, шифрованный, самомодифицирующийся код, антиотладочные и антидизасемблерные приемы… этом список можно продолжать бесконечно. Целесообразность использования тех или иных защитных механизмов – тема другого разговора, здесь же мы будем обсуждать лишь пути их реализации.
Ассемблерные трюки – вообще больная тема, однако, следует различать трюк как таковой (оригинальная идея и/или нетрадиционный примем программирования) и недокументированные возможности
процессора и операционной системы. Трюки, при грамотном подходе к ним, вполне безобидны и никаких проблем не создают. Классический пример трюка – расшифровка программы одноразовым блокнотом, возвращаемым функцией rand. Поскольку, функция rand всегда возвращает одну и ту же последовательность, она идеально подходит для динамической шифровки/расшифровки программы. Если дизассемблер не сумеет распознать rand в откомпилированной программе, – хакер ногу сломит, пока не догадается: как устроена и работает такая защита. Какие проблемы может создать этот трюк? Совершенно верно, – никаких.
А вот пример "грязного хака", основанного на недокументированных возможностях: в Windows95 регион адресного пространства от 0xC0000000 до 0xF0000000, хранящий низкоуровневые компоненты системы, свободно доступен прикладным приложениям, что очень облегчает борьбу с отладчиками и всякими мониторами. Правда, под Window NT первая же попытка обращения к этой области приводит к генерации исключения с последующим закрытием приложения–нарушителя. В результате, конечный пользователь теряет возможность запускать защищенную программу под Windows NT.
Вот за это многие и не любят ассемблер. Но, позвольте, разве ж ассемблер виноват? Не используйте недокументированных особенностей (а если уж совсем невтерпеж, то используйте их с умом) – и проблем ни у кого не будет!
В последнее время, кстати, наметилась устойчивая тенденция к отказу от ассемблера даже в защитных механизмах. Действительно, многие трюки замечательно реализуются и на языках высокого уровня. В частности, динамическую расшифровку кода (равно как и исполнение кода в стеке) можно реализовать и на чистом Си/Си++, достаточно лишь получить указатель на функцию (Си это позволяет), после чего с ее содержимым можно делать все, что угодно. И вовсе не обязательно для этого спускаться на уровень голого ассемблера. Так же, язык высокого уровня облегчает написание полиморфных генераторов и виртуальных машин (машин Тьюринга, сетей Петри, стрелок Пирса и т.д.). Единственное, что нельзя на нем реализовать – так это самомодифицирующийся код. Вернее, можно, но с жесткой привязкой к конкретному компилятору (ибо необходимо знать: как и во что транслируется каждая строка), а подобная практика – дурной тон. Привязываться ни к чему и ни когда не стоит, к тому же трудозатраты при создании самомодифицирующегося кода на языке высокого уровня намного выше, чем на ассемблере.
Тем не менее, возможность создания защит на чистых Си/Си++ многих хакеров старого поколения просто корежит, – они и слышать об этом не хотят (автор, кстати, сам такой). Что поделаешь! Традиции и привычки – штучки упрямые. Ну, красиво программирование на голом ассемблере, понимаете? А создание защит непосредственно в машинных кодах вызывает ничем не передаваемое удовлетворение по своему эмоциональному накалу сравнимое разве что с оргазмом.
Это – программирование ради программирования, нацеленное не на конечный результат, а на сам процесс его достижения. Не могу удержать, чтобы не процитировать: "Для некоторых людей программирование является такой же внутренней потребностью, подобно тому, как коровы дают молоко, или писатели стремятся писать" Николай Безруков