Продолжим разговор о создании защиты для коммерческих программных продуктов. Сегодня поговорим о более сложных техниках.
Warning!
И начнем с угроз и предупреждений. Да-да, понимаю, звучит несколько странно, но, в общем, думаю, вполне оправданно в силу всего того, что будет изложено ниже. Так что даже если вы уже наловчились пропускать вступления к моим статьям, где "льётся вода" и идут размышления о жизни, сейчас лучше этого не делать. Почему? Потому что речь будет идти о некоторых "подводных камнях" сложных элементов защиты.
До сих пор мы говорили о совершенно безобидных, с любой точки зрения, приёмах, не особо влияющих ни на работоспособность программы, ни на её анализ антивирусным программным обеспечением. Однако, конечно, все эти приёмы для того, кто действительно вознамерился качественно "сломать" ваш программный продукт, будут не слишком серьёзными препятствиями - рассчитаны они, в основном, на новичков-дилетантов. Те меры, которые принимаются против настоящих профессионалов в сложном и малопочетном деле создания "кейгенов", уже могут не столь однозначно восприниматься теми же антивирусами - в первую очередь, речь идёт о шифровании исполняемого кода, его исполнении во внутренней виртуальной машине и т.д. и т.п. Поэтому прежде чем приниматься за их реализацию, нужно обязательно подумать о том, какой процент ваших потенциальных покупателей, увидев сообщение о том, что EXE-файл вашего приложения якобы содержит вредоносный код, удалят его и скажут своим друзьям и знакомым, что ваша программа - на самом деле никакой не файловый менеджер и не конвертер MP3 в MP4, а злобный вирус, замаскировавшийся под мирную утилиту. Ситуация сегодня довольно частая (да что там сегодня - известная испокон веков, от троянского коня до советского трактора: a-pesni.golosa.info/dvor/sovtraktor.htm), и вряд ли кто-то будет разбираться, вправду ли ваша программа вредоносная или просто антивирус перестарался.
Так что прежде чем начинать реализовывать сложную защиту, прикиньте, окупятся ли ваши старания или приведут к тому, что число покупателей вашей программы сократится, и придётся ещё придумывать, как "подружить" сложную защиту с антивирусами.
Проверка целостности файла
Первый приём, о котором мы с вами сегодня поговорим и который действительно можно считать профессиональным для защиты программного обеспечения от взлома, пожалуй, вряд ли может вызвать какое-либо неудовольствие со стороны антивирусов. Вместе с тем, конечно, лучше его использовать совместно с теми приёмами, которые будут описаны ниже и которые подобное неудовольствие вызвать очень даже могут.
Проверка целостности файла - необходимое условие для успешной борьбы с разными патчами. Конечно, патчи сегодня не так широко применяются, как кодогенераторы, потому что считаются "неспортивным" способом взлома софта, но никто их не отменял и, соответственно, борьба с ними по-прежнему чрезвычайно актуальна. Бороться же можно следующим образом: подсчитываем контрольную сумму оригинального файла, который "выплюнул" ваш компилятор, и затем сравниваем её с вычисленной контрольной суммой файла, который находится на компьютере пользователя. На основании получившихся в итоге совпадений или различий делаем соответствующие выводы.
В общем-то, на практике способ практически так же прост, как и в теории. Алгоритмов вычисления контрольной суммы для файлов существует достаточно много, и, в общем-то, наверное, не так уж важно, какой именно вы выберете - достаточно убедиться в том, что его конкретная реализация одинаково корректно работает на 32-х и 64-битных платформах (если, конечно, и ваша программа работает на тех и на других), и помнить, что исходный код (для непропатченного файла) и вычисляемый (для любого) должны вычисляться по совершенно идентичному алгоритму.
Пожалуй, единственная проблема, с которой можно столкнуться, - это где именно хранить код непропатченного файла. В самом EXE, к сожалению, это сделать вряд ли получится, поскольку при записи этого кода в него автоматически изменится вычисленный код со всеми вытекающими последствиями. На мой взгляд, лучшее решение - хранить в реестре, замаскировав под какой-нибудь нужный для программы GUID или другую числовую последовательность, которая не должна вызвать особых подозрений у взломщика, мониторящего действия вашей программы с помощью SysInternals'овских утилит.
Детектирование отладчиков
Для того чтобы исследовать вашу программу, скомпилированную в виде исполняемого файла, злоумышленник использует инструменты отладки - дизассемблер и отладчик. Первый переводит код из машинных команд в команды языка ассемблера, ну а со вторым вы просто не могли не сталкиваться, занимаясь программированием на чём бы то ни было сложнее BAT-файлов. Так вот, идея большинства приёмов профессиональной защиты заключается в том, чтобы помешать взломщику воспользоваться этими инструментами. Это относится, в общем-то, и к защите приложений, исполняющихся через виртуальную машину (.Net, Java) или интерпретатор, хотя там, конечно, инструменты и приёмы немного другие, но основная идея от этого совсем не меняется.
Конечно, большая часть приёмов защиты направлена на борьбу именно с дизассемблерами, потому что это эффективнее - опытный взломщик часто может идентифицировать блоки кода, отвечающие за защиту, и просто по листингу на ассемблере, безо всякого отладчика. Но и от отладчиков есть смысл защищаться, потому что ваше приложение должны отлаживать только вы или ваши помощники, а никак не взломщики. Хотя на детектирование отладчиков и не очень хорошо реагируют некоторые антивирусы, о чем я уже предупреждал выше, но, в целом, подавляющее большинство относится к этому достаточно спокойно.
Детектирование отладчиков - сравнительно простая задача, если подойти к ней формально, и достаточно сложная, если нужно действительно постараться исключить запуск вашего приложения под самыми разными отладчиками. В любом случае, при реализации этой защитной процедуры, как и при написании остальных частей защиты, нужно помнить, что невзламываемых защит не бывает и что усилия по защите приложения должны соответствовать его стоимости, в противном случае мы получаем серебряное колечко, запертое в стоящем несколько десятков тысяч долларов бронированном чемодане. Так что для начала, в любом случае, стоит попробовать пойти по более простому и дешевому формальному пути, а потом, если он покажется вам недостаточным, уже заниматься обстоятельной работой по борьбе с каждым из отладчиков в отдельности.
Так вот, формальный (он же простой) путь заключается в использовании замечательной системной функции IsDebuggerPresent. В справочнике по Windows API (msdn.microsoft.com/en-us/library/ms680345%28v=vs.85%29.aspx) она описывается следующим образом: BOOL WINAPI IsDebuggerPresent(void). Функция эта, как видите, не требует никаких параметров, а возвращает, собственно говоря, именно то, что записано у неё в названии: запущен ли процесс из-под отладчика или нет. То есть, если запущен из-под отладчика, то возвращаемое значение ненулевое, а если нет, то функция вернёт ноль.
Ещё одна функция, полезная для разработчиков, называется CheckRemoteDebuggerPresent (msdn.microsoft.com/en-us/library/ms679280%28v=vs.85%29.aspx). Она описывается так: BOOL WINAPI CheckRemoteDebuggerPresent( __in HANDLE hProcess, __inout PBOOL pbDebuggerPresent). Не нужно думать, что она будет полезна только разработчикам клиент-серверных систем: remote в данном случае означает не то, что отладчик в обязательном порядке запущен на другом компьютере, а всего лишь то, что он выполняется в рамках другого процесса (а компьютер и даже операционная система совершенно те же самые).
Но, конечно, не всё так просто в датском королевстве, и было бы очень странно, если бы современные отладчики не научились противостоять заложенным корпорацией Microsoft в саму операционную систему механизмам определения присутствия работающего отладчика. Например, для очень популярного в среде взломщиков отладчика OllyDbg (о котором, кстати, я когда-то - очень давно тому назад - рассказывал читателям "Компьютерных вестей"), есть плагин под названием HideDbg, который как раз и скрывает этот отладчик от зоркого ока функции IsDebuggerPresent.
Теперь несколько слов о другом пути - длинном и более результативном. Для того, чтобы успешно бороться с ухищрениями отладчиков, нужно воспользоваться чем-то более сложным, чем простой вызов функции IsDebuggerPresent. Каждый отладчик имеет свои особенности, которые он обязательно проявляет во время своей работы. Какие это особенности? Скажем, наличие в системе каких-то специфических процессов, загруженных DLL-библиотек и т.п. Написание такого детектирования отладчика - сложная и кропотливая работа, которая, конечно, даст гораздо лучшие результаты, чем описанный выше способ, но при этом потребует и значительных вложений сил и времени.
Что ж, пожалуй, на сегодня всё, а о том, какие ещё есть способы борьбы со взломщиками коммерческих программных продуктов, мы с вами поговорим в будущих выпусках "Компьютерных вестей".
Вадим СТАНКЕВИЧ,
[email protected]
Горячие темы