Аппаратная защита программного обеспечения
 

Допустим, вы написали супер-крутую программу. И хотите ее продавать. И даже существуют люди, которым ваша программа нужна позарез. Но почему-то находится очень мало желающих платить за вашу программу назначенную вами цену. И вот - программа расходится по рукам, выкладывается на сайтах в сети Интернет, появляется на пиратских компактах... Обидно? Да и затраченные на разработку силы и средства хочется как-то оправдать - вы же не благотворительностью занимаетесь… 

И вот перед вами встает вопрос защиты вашей программы от несанкционированного использования. Что тут можно сделать?

На заре компьютеризации в России чаще всего применяли защиту, основанную на нестандартном использовании носителей информации – в основном, гибких дисков (“хитрое” форматирование, запись в служебные промежутки между секторами, проверка специально сделанных дефектов носителя и т.п.). Таким образом обычно защищалась программа установки, а сама прикладная программа “привязывалась” к аппаратным характеристикам компьютера (типу материнской платы или процессора, размеру оперативной памяти, размеру жесткого диска…). При очевидной простоте и дешевизне, для пользователя этот способ ужасно неудобен: резервную копию софта сделать невозможно, на другой компьютер перенести – тоже; а при порче дистрибутива приходилось снова обращаться к поставщику. К тому же, со временем появились средства “побитового” копирования таких “защищенных” носителей информации (например, программка FDA), которые свели все усилия разработчиков защиты на нет.

Еще один вариант защиты – использование так называемого “серийного номера”, т.е. сформированного по определенному алгоритму числа, которое указывается при установке программы. Программа установки (или сама прикладная программа) проверяет введенное значение на соответствие известному ей алгоритму, и в случае успеха - продолжает работать в штатном режиме. Таким образом защищаются, например, операционные системы Windows, программные пакеты Corel Draw!, Adobe PhotoShop и многие другие. Вариант очень удобен для конечного пользователя: можно сделать сколько угодно резервных копий, можно установить программу на любое количество компьютеров… Но именно это и не устраивает разработчиков ПО: действительно, имея один “правильный” ключ, можно создать сколько угодно “пиратских” копий, за которые платить совсем не обязательно.

В результате появилось целое направление в компьютерной индустрии, занимающееся обеспечением защиты тиражируемого программного обеспечения от несанкционированного использования. И сегодня практически все “коробочные” варианты серьезного коммерческого ПО используют программно-аппаратные комплексы защиты, более известные как “аппаратные ключи защиты”. В России их разработкой занимаются компании “Актив” (www.novex.ru), “Aladdin Software Security R.D.” (www.aladdin.ru) и некоторые другие.

Черный ящик

Если вы полезете в блок питания с отверткой,
он может вас убить в целях самозащиты.

Аппаратные ключи защиты состоят из собственно ключа, подключаемого к LPT или COM-порту компьютера (недавно анонсированы ключи, подключаемые к USB-шине), и программного обеспечения (драйверов для различных операционных систем и модуля, встраиваемого в защищаемую программу).

Аппаратная часть таких ключей выполнена на микросхемах FLASH-памяти, на PIC-котроллерах или на заказных ASIC-чипах Эта элементная база отличается очень низким энергопотреблением, поэтому для питания ключей используются выводы, изначально для этого не предназначенные (-AUTO FEED, -INIT, -SLCT IN, -STROBE или одна из информационных шин для LPT-порта; DTR, RTS для COM-порта). Информационный обмен между ключом и компьютером происходит, обычно, в последовательном виде, с использованием стробирующего сигнала, формируемого драйвером. В качестве выходных информационной и стробирующих линий используются вышеперечисленные выводы, а в качестве входной линии используются сигналы –STROBE, -ACK, BUSY, PE, SLCT или ERROR для LPT-порта и DSR, CTS для COM-порта.

Конечно, аппаратные ключи, подключаемые к LPT- и COM-портам, должны обеспечивать “прозрачный” режим обмена по стандартным для этих портов протоколам. Например обмен с ключами, подключаемыми к LPT-порту, будет вестись только при пассивном уровне сигнала –SLCT IN (т.е. “принтер не выбран”), а обмен с ключами для COM-портов будет происходить только при пассивном уровне DTR (“Data terminal ready”). Впрочем, эти ухищрения все равно не помогают избежать конфликтов со стандартными устройствами, предназначенными для подключения к данным портам (последними моделями принтеров и сканеров, использующих двунаправленный обмен по параллельному порту или с манипуляторами типа “мышь” и модемами, подключаемыми к последовательному порту). От подобных недостатков должны быть свободны ключи, подключаемые к USB-шине, но пока их серийное производство только начинается.

Итак, рассмотрим аппаратные реализации ключей защиты.
Самый простой и самый легко взламываемый - ключ на основе FLASH-памяти. Основная его идея в том, чтобы перед продажей защищаемого программного обеспечения записать в ключ некоторые данные и/или части программного кода, а на этапе проверки легальности использования ПО считать эти данные из ключа. Ломается защита примерно так: определяется алгоритм обмена информацией между компьютером и ключом, считывается информация из FLASH-памяти ключа и пишется соответствующий эмулятор (драйвер, который подменяет собой штатный драйвера электронного ключа и - вместо обмена с реальным устройством - передает прикладной программе заранее подготовленные данные). Кроме того, такие ключи обладают наименьшей степенью прозрачности для стандартных протоколов обмена (т.к. данные, не предназначенные для ключа, теоретически могут быть восприняты им как команда на чтение или запись FLASH-памяти, что приведет либо к порче хранимой информации, либо к нарушению протокола обмена с другим устройством, подключенным к тому же порту компьютера).

Ключи, сделанные на основе PIC или ASIC-чипов, имеют на порядок большую устойчивость к взлому и “прозрачность” для штатных протоколов обмена. Обе эти микросхемы представляют собой контроллеры, содержащие в себе процессор, некоторое количество оперативной памяти, FLASH-память команд и память для хранения микропрограммы. Микропрограмма и внутренняя память обычно защищается от внешнего считывания, так что сделать аппаратную копию ключа довольно проблематично. Основное отличие PIC-ключей от ASIC-ключей в том, что PIC-чипы программируются разработчиком ключей (т.е. он может относительно легко изменить алгоритмы работы), а ASIC-чипы являются заказными микросхемами (т.е. алгоритмы жестко задаются на этапе производства микросхем). Поэтому ASIC-ключи получаются более дешевыми, чем собранные на основе PIC-чипов, но по этой же причине защита на их основе менее надежна (определив алгоритм обработки данных в одном из ASIC-чипов, можно написать эмулятор ключа для всей партии, которая - в силу особенностей производства - обычно бывает достаточно большой). Широко известен случай, когда был определен алгоритм работы электронного ключа производства компании “Aladdin Software Security R.D.” (как оказалось, данная функция может быть реализована одной строкой на языке С), после чего появилось большое количество эмуляторов ключей данной фирмы. И разработчики ничего не могли с этим поделать, так как для изменения алгоритма им пришлось бы заказывать производство новой партии микросхем.

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

Программная часть

Усложним программу. Запутаем алгоритм. Избавим от комментариев. 
Снимем порчу с винчестера и сглаз с монитора.
/Группа программистов-экстрасенсов/

Программная часть защитного комплекса состоит из драйвера аппаратного ключа и модуля, встраиваемого в прикладную программу.

1. Драйвер ключа.
Так как ни одна уважающая себя операционная система не позволит прикладной программе напрямую общаться с портами ввода/вывода, требуется наличие драйвера, выполняющегося в режиме ядра ОС. Это условие является обязательным для всех клонов Unix, Novell Netware, MS Windows NT.

Задача драйвера – обеспечить самый низкий уровень обмена данными между ключом и прикладной программой. Для ключей, подключаемых к COM- и LPT-портам, драйвер отвечает за формирование синхронизирующих и информационных сигналов на выходах соответствующих разъемов и за расшифровку последовательного кода, получаемого от аппаратного ключа. Кроме того, для PIC- и ASIC-ключей драйвер формирует инициирующую последовательность (данные, приняв которые, ключ начинает обрабатывать все последующие данные по заданному алгоритму).

2. Встраиваемый модуль.
Как уже было сказано выше, для ключей на основе FLASH-памяти защита заключается в считывания из ключа некоторых данных и/или участков программного кода.
Для PIC- и ASIC-ключей защита строится по принципиально другому методу.
На этапе программирования ключа (или производства ASIC-чипа) в него записывается микропрограмма, реализующая некоторую функцию y = F(x1,x2,…xn), где x1…xn – входные параметры, а y – выходной параметр. Обычно один или несколько параметров x1…xn представляют собой случайные числа (для затруднения определения вида функции F), один из параметров – уникальный номер ключа (“серийный номер”), еще один - идентификатор защищаемого программного обеспечения, и т.п.

После обработки ключом входных параметров он формирует и выдает в компьютер выходное значение y. Это значение передается в модуль, интегрированный в прикладную программу, где над этим значением и параметрами x1…xn производится преобразование вида 
y’ = f(y,x1,x2…xn).

Результирующим значением y’ могут быть константы, необходимые для работы программы, участки программного кода, адреса подпрограмм и т.п. При этом необходимое условие - невозможность восстановления функции F(x1,x2…xn) по функции f(y,x1,x2…xn), которую довольно легко получить, реассемблировав участок прикладной программы, отвечающий за проверку данных, полученных от ключа.

При наличии в ключе энергонезависимой памяти и/или таймеров можно добавить их текущие значения в качестве аргументов функции F(x1,x2…xn), что расширяет возможности построения систем защиты.

3. Реализация.
Конечно, разработчики электронных ключей пытаются как можно больше усложнить жизнь хакерам, которые будут взламывать их защиту. Для этого применяются:
- защита от реассемблирования (условные и безусловные переходы по содержимому регистров или ячеек памяти, после которых ставятся несколько байт, реассемблируемых в реальную команду процессора; “размывание” программного кода путем размещения его в разных местах программного модуля с выполнением безусловных или неочевидных условных переходов после каждой ассемблерной команды), 
- защита от трассировки (перехват INT1 и INT3, издевательства над регистрами SS/SP/ESP, работа в режиме запрещенных прерываний и т.п.), 
- защита от отладчиков (проверка их наличия через API-функции), 
- проверка изменения кода драйвера ключа или встраиваемого модуля (проверка контрольной суммы, проверка по контрольным точкам и т.п.). 

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

И что дальше?

Чем хакер отличается от юзеpа? 
- Хакеp подбирает пароль с третьего раза,
а юзеp набирает с пятого.

Так что же – разработчики прикладного ПО могут спать спокойно? Да ничего подобного. Все вышеперечисленные методы эффективны, фактически, только для защиты от пользователей и начинающих хакеров.

Ломают электронные ключи, и очень легко ломают. Кто умеет держать в руках паяльник и имеет необходимое оборудование (минимум – цифровой осциллограф с памятью) – те снимают информацию непосредственно с разъема и делают аппаратные эмуляторы ключей. Кто паяльнику предпочитает отладчик – те определяют алгоритм обмена путем перехвата обращений к регистрам управления COM- и LPT-портами. При этом достаточно иметь один экземпляр легального ключа, чтобы определить если не вид функции F(x1,x2…xn), то хотя бы набор значений y в зависимости от набора входных аргументов x1…xn. Естественно, если один из аргументов является случайным числом или значением встроенного таймера или энергонезависимой памяти, то написание эмулятора сильно затрудняется; но в этом случае остается возможность просто “откусить” защитный модуль, подставив в соответствующих местах программы значения y’, вычисленные при использовании одного “легального” ключа. Защита от трассировки и отладчиков практически бесполезна - особенно при использовании могучего отладчика SoftIce (“Compuware NuMega”, www.numega.com). В частности, если убрать из этого отладчика его внешние API-функции (а такие патчи давно имеются в Интернете), защищаемая программа вообще никак не сможет обнаружить его присутствие, а все остальные функции процессора SoftIce эмулирует на уровне “виртуальной машины” (т.е. манипуляции со стеком, трассировка по INT 1 и INT 3, а также работа в режиме с запрещенными прерываниями в данном случае не помогут).

Так что - в области защиты программного обеспечения наблюдается обычная борьба щита и меча, брони и снаряда; средств защиты и средств ее взлома…

Вот такой вот прогресс с НТР.

 
Автор: Игорь Николаев
 
Оригинал статьи: http://woweb.ru/publ/49-1-0-636