<< Предыдущая

стр. 21
(из 39 стр.)

ОГЛАВЛЕНИЕ

Следующая >>

12.9. Что происходит на практике
На практике в подавляющем большинстве случаев программисты не исполь-
зуют все возможности, предоставляемые аппаратными ключами. Так, на-
пример, очень часто в алгоритмических ключах с памятью используется
только память, не говоря уже о случаях, когда все проверки наличия ключа
производятся в одной функции, которая возвращает результат в виде логи-
ческого значения. И для получения полнофункциональной версии програм-
мы даже не требуется ключ— достаточно исправить функцию проверки,
чтобы она всегда возвращала состояние, соответствующее наличию ключа.
Некоторые ключи (например Sentinel SuperPro) имеют довольно сложную
систему разграничения доступа. Ключи Sentinel SuperPro поддерживают па-
роли для активации алгоритмов, выбираемые при программировании, и раз-
дельные пароли для записи и перезаписи, одинаковые для всех ключей од-
ной серии, поставляемых одному разработчику. И очень часто в теле
программы оказывается пароль перезаписи, который позволяет противнику
перепрограммировать ключ по своему усмотрению.
Ключи HASP Time, напротив, не имеют разграничения доступа — для того
чтобы изменить время в ключе, нужно знать те же самые пароли, которые
используются для чтения времени. То есть противнику для продления пе-
риода работоспособности программы, ограниченной по времени, достаточно
отвести назад часы в ключе, и ничто не мешает ему это сделать.
Некоторые неизвестные алгоритмы, реачизованные в ключах, были подверг-
нуты анализу. Так был восстановлен алгоритм функции seedcode, используе-
мой в ключах HASP. А по некоторым сообщениям в Интернете, не являются
больше секретными и алгоритмы, реализуемые ключами Sentinel SuperPro, и
даже новые алгоритмы кодирования и декодирования в ключах HASP4.
Ключи с асимметричными алгоритмами выпускаются уже многими произ-
водителями, но позиционируются как устройства для идентификации и ау-
тентификации, а не для защиты информации.
А ключи с программируемым алгоритмом, напротив, не выпускаются круп-
нейшими производителями ключей и, следовательно, практически не при-
меняются. Возможно, это обусловлено тем, что они получаются слишком
дорогими или слишком сложными для использования.


12.10. Выводы о полезности
аппаратных ключей
Утверждение, что аппаратные ключи способны остановить компьютерное
пиратство, является мифом, многие годы распространяемым производите-
Глава 12. Аппаратные ключи защиты 141_

лями ключей. Для хорошо подготовленного противника ключ редко являет-
ся серьезным препятствием.
К тому же, часто программисты слепо доверяют автоматизированным сред-
ствам защиты, поставляемым в составе SDK-ключа, и не прикладывают са-
мостоятельных усилий для усиления защиты. Обещания производителей
создают иллюзию защищенности, но на самом деле практически для всех
автоматизированных средств защиты давно разработаны эффективные спо-
собы нейтрализации защитных механизмов.
Большая часть защитных механизмов, применяемых в современных ключах,
оказывается работоспособной только в предположении, что противник не
сможет обеспечить эмуляцию ключа, т. е. реализуются на программном
уровне. Следовательно, почти всегда тот же уровень защиты может быть
достигнут без привлечения аппаратных средств.
1ава 13


^пользование
тесных защит
1им из популярных способов защиты программ является использование
называемых протекторов — программных инструментов, предназначен-
. для зашиты других программ.
нно сценарий установки защиты следующий. Разработчик создает про-
емный продукт с использованием некоторых программных средств: ви-
дных сред, компиляторов и т. д. После того как получен работающий
олняемый файл, этот файл обрабатывается с помощью программы-
тектора и создается новый исполняемый файл, в котором реализованы
аторые средства защиты.


. 1 . Какую защиту
еспечивают протекторы
•текторы, прежде всего, защищают программу от исследования. Исследо-
> можно различные области программы, но наиболее часто проводится
чедование кода, причем с совершенно разными целями. Исследование
а вируса может проводиться с целью определения методов заражения и
>аботки вакцины. Исследование кода операционной системы помогает
эдить уязвимости, а также писать приложения, взаимодействующие
крационной системой на более низком уровне. Программы исследуются
елью обнаружения недокументированных возможностей, а иногда для
становления алгоритма, по которому программа функционирует. Суще-
'ют и другие причины для исследований.
асти ресурсов и данных также могут содержать некоторую интересную
юрмацию, поэтому часто защите подвергают не только код программы,
i данные с ресурсами.
[ако защищать абсолютно все ресурсы не совсем правильно. Дело в том,
основная часть ресурсов должна быть доступна только в момент выпол-
ия программы, и такие ресурсы можно безбоязненно защищать. Но есть
144 Часть III. Как не надо защищать программы

некоторое количество ресурсов, например информация о версии программы
и ее иконка, которые могут использоваться операционной системой тогда,
когда программа не запущена. И эти ресурсы в защищенной программе
должны оставаться в открытом виде.
То же самое относится и к некоторым служебным структурам данных, хра-
нящимся внутри программы и использующимся в процессе загрузки. Если
эти структуры будут недоступны операционной системе, защищенную про-
грамму не удастся запустить.
Многие протекторы содержат средства, позволяющие создавать версии
с ограничениями. Например, защищенная программа может прекратить ра-
ботать через заданный промежуток времени, если не будет введен правиль-
ный регистрационный код или до ввода кода будет регулярно появляться
окно с напоминанием о том, что программа не зарегистрирована, и с пред-
ложением приобрести лицензию.
Наиболее продвинутые протекторы имеют программные интерфейсы (API),
доступные из защищаемой программы и позволяющие более четко контро-
лировать процесс ее выполнения. Очень часто API используется для дина-
мической разблокировки фрагментов кода, которые должны быть доступны
только в зарегистрированной версии.


13.2. Как работают протекторы
Для того чтобы защитить исполняемый файл, протектор должен каким-то
образом преобразовать его содержимое и добавить свой код, отвечающий за
правильную загрузку измененной программы в память.
Практически для всех форматов исполняемых файлов были разработаны алго-
ритмы, позволяющие добавлять новый код таким образом, чтобы он выпол-
нялся до основной программы, не нарушая при этом ее функциональности.
Скорее всего, основные исследования в этой области были выполнены
авторами вирусов, т. к. добавление тела вируса к программе является одним
из основных методов заражения.
Код, данные и. ресурсы обычно защищаются с помощью шифрования. Исполь-
зуемый алгоритм не обязательно должен быть криптографически стойким, т. к.
ключ шифрования все равно невозможно сохранить в абсолютной тайне. Очень
часто до шифрования применяется сжатие данных, что позволяет компенсиро-
вать увеличение размера исполняемого файла, происходящее вследствие добав-
ления кода протектора. А иногда результирующий защищенный файл даже
уменьшается в размере по сравнению с исходным файлом.
При запуске защищенной программы управление сразу получает код про-
тектора, который выполняет предусмотренные проверки и расшифровывает
Глава 13. Использование навесных защит 145

в памяти все необходимые области, а также проводит настройку таблицы
адресов импортируемых функций. После успешного завершения процедуры
настройки протектор передает управление на оригинальную точку входа
(Original Entry Point, OEP) и начинается выполнение основной программы.
Проверки, выполняемые протектором до начала работы программы, могут
быть разного рода. Это могут быть проверки, касающиеся наличия лицен-
зии (чтобы без лицензии программа просто не запускалась) или сравнения
текущей даты со значением, после которого программа должна перестать
работать, а также попытки определить наличие запущенного отладчика.


13.3. Сценарии атаки
Итак, чаще всего защищенная программа отличается от незащищенной по
следующим параметрам:
• код самой программы зашифрован;
• адрес оригинальной точки входа известен только протектору;
• основная часть ресурсов зашифрована;
• основная часть данных зашифрована;
П таблицы импорта недоступны (настройку импорта выполняет сам протек-
тор, и только он знает, какие функции должны быть импортированы);
• присутствует код протектора.
Для того чтобы обезвредить (удалить) протектор и реконструировать про-
грамму к виду, максимально близкому к незащищенному, человеку, кото-
рый пытается снять защиту, необходимо решить следующие задачи:
• получить расшифрованный код программы;
О найти адрес оригинальной точки входа;
• получить расшифрованные ресурсы;
• получить расшифрованные данные;
• определить все импортируемые программой функции и восстановить
таблицы импорта;
• удатить код протектора.
Для большинства существующих протекторов разными людьми в разное
время были разработаны эвристики, позволяющие успешно решить все или
почти все эти задачи.
Так, например, код обычной программы не должен изменяться в процессе
выполнения. Следовательно, после того, как протектор передал управление
защищенной программе, и до ее завершения код представлен в памяти
146 Часть III. Как не надо защищать программы

в таком же виде, как и у незащищенной программы. То есть для восстанов-
ления оригинальной секции кода достаточно извлечь ее из памяти после
того, как программа была запущена. В современных версиях Windows для
этого очень хорошо подходит стандартная функция Win32 API, носящая на-
звание ReadProcessMemory.
Найти адрес оригинальной точки входа несколько сложнее. Но, имея в рас-
поряжении расшифрованную секцию кода, можно получить очень много
информации, позволяющей эффективно решить эту задачу. По секции кода
довольно легко можно определить, в какой среде разработки создана про-
грамма и каким компилятором она собрана. А наличие такой информации
значительно упрощает отыскание оригинальной точки входа. Так, например,
характерной особенностью программ, собранных с помощью Borland C++
Builder, является то, что точка входа находится в самом начале секции кода.
А точка входа в программы, собранные в Borland Delphi, напротив, соответ-
ствует началу функции, которая расположена в самом конце секции кода.
Также в графических (не консольных) приложениях одной из первых вызы-
ваемых функций Win32 API я&тается GetModuieHandie, т. к. значение, воз-
вращаемое Э О функцией, Д Л Н быть передано В фунКЦИЮ WinMain,
ТЙ ОЖ О
с которой начинаются почти все Win32-nporpaMMbi. Таким образом, если
каким-нибудь способом удастся узнать, с какого адреса происходит вызов
GetModuieHandie, оригинальная точка входа с большой вероятностью будет
где-то неподалеку.
Есть и другой способ выявления оригинальной точки входа. Он заключается
в том, чтобы в момент, когда протектор уже расшифровал секцию кода, но
еще не передал в нее управление, с помощью функции writeProcessMemory
заполнить всю секцию кода байтами со значением ОхСС (что соответствует
команде процессора int3 — вызов отладочного прерывания). Разумеется,
такой код не может выполняться, о чем операционная система и известит
пользователя. А в некоторых случаях (в частности, под Windows NT, 2000 и
ХР) еще и сообщит адрес, по которому произошла ошибка. Этот адрес и
будет я&чяться адресом оригинальной точки входа. Данный метод известен
со времен DOS, где он использовался, в частности, для поиска оригиналь-
ных точек входа в прерывания.
Кстати, во времена DOS также существовали упаковщики и протекторы ис-
полняемых файлов, и для противодействия им разрабатывались автоматиче-
ские депротекторы. Фактически, труднее всего автоматизации поддавалась
именно задача поиска оригинальной точки входа. И существовал депротек-
тор, носивший название Intruder (вторгающийся), который умел в процессе
запуска программы определять, каким компилятором она была создана, и,
исходя из этого, вычислял правильный адрес точки входа. Intruder "знал"
практически все распространенные в то время средства разработки и в по-
Глава 13. Использование навесных защит 147

давляющем большинстве случаев успешно снимат навесную защиту в авто-
матическом режиме.
С извлечением ресурсов больших проблем обычно не возникает. Протектору
приходится расшифровывать и настраивать ресурсы в памяти таким обра-
зом, чтобы функции Win32 API, отвечающие за доступ к ресурсам, могли
нормально работать. Следовательно, действуя точно так же, как действуют
функции Win32 API, из загруженной в память программы можно извлечь
каждый ресурс в отдельности, и тогда для восстановления секции ресурсов
останется только построить дерево ресурсов (в соответствии со специфика-
цией формата Portable Executable).
Данные программы могут изменяться в процессе выполнения. Следователь-
но, для получения неискаженных секций данных необходимо читать их из
памяти процесса именно в тот момент, когда протектор передает управление
основной программе. Если оригинальная точка входа известна, определение
такого момента не составит большого труда. Для этого можно использовать
отладочные регистры центрального процессора.
Начиная с Intel 80386 все процессоры семейства х86 имеют аппаратные
средства для отладки приложений. Процессор позволяет использовать до
4-х аппаратных точек останова. Каждая точка описывается типом доступа,
который будет отслеживаться процессором (чтение, запись, выполнение),
адресом, при обращении по которому процессор сгенерирует исключение, и
размером контролируемой области (BYTE, WORD, DWORD). ДЛЯ работы с аппа-
ратными точками останова используются так называемые отладочные реги-
стры, которые в системе команд х86 имеют логические имена, начинающие-
ся с DR (Debugging Registers). Достаточно установить точку останова на
исполнение кода по адресу, соответствующему найденной оригинальной
точке входа, обработать генерируемую процессором исключительную ситуа-
цию и прочитать содержимое памяти.
Регистры аппаратной отладки не доступны напрямую пользовательским
программам, т. к. операции чтения и записи отладочных регистров являются
привилегированными и разрешены только в режиме ядра (в драйверах).
Но к содержимому этих регистров можно получить доступ несколькими
способами и без драйвера.
Во-первых, содержимое всех регистров в каком-то потоке может быть полу-
чено и установлено через такие функции Win32 API, как GetThreadContext
и SetThreadcontext. Перед обращением к контексту потока рекомендуется
остановить поток функцией suspendThreaa, а по окончании манипуляций
С КОНТеКСТОМ Запустить его ВНОВЬ При ПОМОЩИ ResumeThread.
Во-вторых, существует подмножество Win32 API, называемое Debugging API
(программный интерфейс отладки). С помощью функций, входящих в со-
148 Часть III. Как не надо защищать программы

став Debugging API, очень просто написать собственный отладчик, который
будет получать извещения обо всех важных событиях и исключениях, воз-
никающих в отлаживаемой программе. И отладчик также может использо-
вать фуНКЦИИ GetThreadContext И SetThreadContext Д Я доступа К ОТЛадоч-
Л
ным регистрам.
И наконец, в-третьих, изнутри программы доступ к отладочным регистрам
может быть осуществлен путем использования механизма структурирован-
ной обработки исключений — Structured Exception Handling (SEH). Доста-
точно установить свой обработчик исключения и выполнить заведомо не-
правильную операцию (например обращение по адресу 0x00000000). При
возникновении ошибки будет вызван обработчик исключения, которому
операционная система передаст указатель на структуру контекста потока,

<< Предыдущая

стр. 21
(из 39 стр.)

ОГЛАВЛЕНИЕ

Следующая >>