Блог → О защите компьютерных программ от вскрытия. Часть 2

В этой заметке я бы хотел поговорить о общей структуре защитных механизмов от несанкционированного копирования. Не секрет, что вопросы защиты имущества были всегда важными вопросами для человечества, буквально с самого начала цивилизации или, если угодно - с момента появления хоть какой-то частной собственности. На протяжении всей своей истории человек что-то защищал от похитителей, поэтому при изложении материала и формулировке задач защиты программного обеспечения от нелегального копирования, авторы подобного софта стали придерживаться древнего принципа: "Фунт аналогии стоит тонны пота".

Как весь комплекс мероприятий защиты, так и отдельные её отдельные элементы испытаны природой на всех уровнях развития: в неживой природе, на биологическом уровне и в человеческом обществе. Каким образом это происходит? Система взаимодействующих элементов защищена от изъятия её из интегрирующей системы благодаря наличию разнообразных взаимодействий с внешними элементами на физическом, химическом и биологическом уровнях. Нормального функционирования изъятой системы в чужой для неё интегрирующей среде можно добиться только исключительно точным перенесением вместе с ней и окружающих взаимовлияющих внешних сил.

Программа, пересаженная в чужеродную среду, должна быть отторгнута, наподобие того, как отторгаются от организма пересаженные чужие ткани, при этом процесс отторжения может вызвать необратимые изменения и в самом "организме" компьютера. Для реализации такого механизма отторжения необходимо, чтобы программа имела возможность предварительно "привыкнуть" к своей среде, а взаимодействие её с чужой средой приводило бы к мгновенной гибели либо её самой, либо окружающей среды или же носило на себе следы постепенного разрушения.

Механизм защиты программного обеспечения, как подсказывает опыт работы в данной области, может включать следующие компоненты (каждый из них мы разберём более подробно ниже):
- блок защиты от вскрытия;
- блок установки характеристик среды;
- блок сравнения характеристик среды;
- блок ответной реакции.

Блок защиты от вскрытия

Программа-взломщик (если коротко, рипер) - это целый комплекс программных средств, ориентированных на исследование защищенного программного продукта. Возможно, термин "рипер" является не очень удачным, но в данном случае, мы отдаем дань существующей жаргонной терминологии профессиональных хакеров.

Главная функция БЗОР (блока защиты от рипера) заключается в том, чтобы обеспечить надёжную защиту от профессиональных любителей "рипать" чужие программы, т.е. снимать защиту с защищённого программного обеспечения. Ниже изложены некоторые способы, которые направлены на выявление и ликвидацию непредусмотренных внешних воздействий на защищаемую программу, характерных для средств снятия защиты.

Для снятия защиты используется комбинация двух основных методов: статический и динамический. Статические методы предусматривают анализ текстов защищённых программ в естественном или преобразованном виде. Динамические методы предусматривают слежение за выполнением программы с помощью специальных средств. Обработка результатов слежения может быть частично или полностью автоматизирована.

Начнём по порядку, со статических методов. Итак, какие же возможности противодействия статическим методам защиты существуют и используются разработчиками современных защитных средств?

1. Шифрование. Модификация и шифрование программного кода - вот два типичных способа внесения особенностей в программную среду. Очень эффективным проявило себя изменение способа шифрования, непосредственно в ходе работы программы. В качестве примера приведем программу CRYPT.EXE (разработчик MamSoft), которая использует алгоритм DES (Data Encryption Standard). Вообще говоря, тема шифрования и дешифрования поистине неисчерпаема, и опять же, подробное её рассмотрение выходит за рамки нашей темы.

2. Включение в тело программы переходов по динамически изменяемым адресам и прерываниям, а также самогенериругацихся команд (например, команд, полученных с помощью сложения и вычитания).

3. Скрытый переход. Вместо команды безусловного перехода (JMP) используется возврат из подпрограммы (RET). Предварительно в стек записывается адрес перехода, который в процессе работы программы модифицируется непосредственно в стеке.

4. Прием "Baba Yaga". Стек определяется непосредственно в области исполняемых команд, что приводит к затиранию команд при работе со стеком. Этот способ весьма хорош, когда не требуется повторное исполнение программного кода, кроме того, таким же способом можно генерировать исполняемые команды впереди вычислительного процесса.

5. Включение в тело программы, если её размер недостаточно велик, "пустышек". Под "пустышкой" понимается модуль на который имитируется передача управления, но реально никогда не осуществляется. Этот модуль содержит большое количество команд, не имеющих никакого отношения к логике работы программы. Но нужно подчеркнуть, что "ненужность" этих команд не должна быть очевидна потенциальному хакеру, иначе теряется смысл. К примеру, такая "пустышка" может содержать команды, присущие определенным методам защиты, это команды типа int21h, int13h со специфическими функциями и т.д. Хакер, работающий над вскрытием программы, обязательно обратит внимание на эти команды, начнёт разбираться с ними и тем самым потеряет столь драгоценное время.

6. Изменение начала защищаемой программы таким образом, чтобы стандартный дизассемблер не смог её правильно дизассемблировать. Например, такие пакеты защиты, как Hota и CopyLock, при внедрении защитного механизма в защищаемый файл, полностью модифицируют исходный заголовок EXE файла. Ниже приведен пример заголовка EXE файла до защиты, и после её установки. В данном случае уже можно говорить не о внедрении защитного механизма в задачу, а наоборот, о включении исходной защищаемой задачи в модуль защитного механизма. CopyLock изменяет такие параметра заголовка, как число элементов заголовка, адрес стека и адрес первой исполняемой команды, в результате чего дизассемблер относит к области стека первые исполняемые команды, так как адрес стека SS:SP - 0004:0003, а адрес первой исполняемой команды CS:IP - 0004:0020.

Теперь переходим к более сложному случаю, и именно - рассмотрим варианты противодействия динамическим методам защиты, которые подразумевают более сложную и вдумчивую работу. Итак, поехали.

1. Периодический подсчет контрольной суммы, занимаемой образом задачи области оперативной памяти, в процессе выполнения. Это позволяет: заметить изменения, внесенные в загрузочный модуль; в случае, если программу пытаются "раздеть", выявить контрольные точки, установленные отладчиком.

2. Проверка количества свободной памяти и сравнения с тем объёмом, к которому задача "привыкла" или вы её "приучили". Это действие позволит застраховаться от слишком грубой слежки за защищаемой программой с помощью резидентных модулей (об этом я расскажу подробнее в следующих заметках).

3. Проверка содержимого незадействованных для решения защищаемой программы областей памяти, которые не попадают под общее распределение оперативной памяти, доступной для программиста, что позволяет добиться "монопольного" режима работы программы.

4. Проверка содержимого векторов прерываний (особенно 13h и 21h) на наличие тех значений, к которым задача "приучена". Иногда полезным бывает сравнение первых команд операционной системы, отрабатывающих эти прерывания, с теми командами, которые там должны быть. Вместе с предварительной очисткой оперативной памяти проверка векторов прерываний и их принудительное восстановление позволяет избавиться от большинства присутствующих в памяти резидентных программ.

5. Переустановка векторов прерываний. Содержимое некоторых векторов прерываний (например, 13h и 21h) копируется в область свободных векторов, при этом соответственно изменяются и обращения к самим прерываниям. Таким образом, слежение за известными векторами не даст желаемого результата. Например, самыми первыми исполняемыми командами программы копируется содержимое вектора 21h (4 байта) в вектор 60h, а вместо команд int21h в программе везде записывается команда int60h. В результате чего, в тексте вашей программы в явном виде нет ни одной команды работы с прерыванием 21h (согласитесь, очень просто и изящно).

6. Постоянное чередование команд разрешения и запрещения прерывания, что затрудняет установку отладчиком контрольных точек.

7. Контроль времени выполнения отдельных частей программы, что позволяет выявить "остановки" в теле исполняемого модуля.

Тут необходимо заметить, что многие из перечисленных выше защитных средств, могут быть реализованы исключительно на языке Ассемблер, а как вы знаете, одной из основных отличительных особенностей этого языка является то, что для него не существует ограничений в области работы со стеком, регистрами, памятью, портами ввода/вывода и т.д.