Приглашение к дискуссии или новые приемы защиты
"многочисленные критические нападки -- неизбежный удел всякой новой концепции"
Ганс Селье. "От мечты к открытию"
В заключении книги мне хотелось бы поделиться собственным опытом создания защит, сломать которые принципиально невозможно. Точнее, их взлом потребовал бы многих тысяч, а то и миллионов лет на типичном бытовом компьютере (во всяком случае, очень хочется на это надеяться).
Гарантированно воспрепятствовать анализу кода позволяет только шифрование программы. Но сам процессор не может непосредственно исполнять зашифрованный код, поэтому перед передачей управления его необходимо расшифровать. Если ключ содержится внутри программы, стойкость такой защиты близка к нулю. Все, чего может добиться разработчик, - затруднить поиск и получение этого ключа, тем или иным способом препятствуя отладке и дизассемблированию программы.
Другое дело, если ключ содержится вне программы. Тогда стойкость защиты определяется стойкостью используемого криптоалгоритма (при условии, что ключ перехватить невозможно). В настоящее время опубликованы и детально описаны многие криптостойкие шифры, взлом которых заведомо недоступен рядовым злоумышленникам.
В общих чертах идея защиты заключается в описании алгоритма с помощью некой математической модели, одновременно с этим используемой для генерации ключа. Разные ветви программы зашифрованы различными ключами, и чтобы вычислить этот ключ, необходимо знать состояние модели на момент передачи управления соответствующей ветви программы. Код динамически расшифровывается в процессе его выполнения, а чтобы расшифровать его целиком, нужно последовательно перебрать все возможные состояния модели. Если их число будет очень велико (чего нетрудно добиться), восстановить весь код станет практически невозможно!
Для реализации этой идеи автором был создан специальный событийно-ориентированный язык программирования. События в нем представляют собой единственное средство вызова подпрограммы. Каждое событие имеет свой код и один (или несколько) аргументов.
Событие может иметь какое угодно количество обработчиков, а может не иметь ни одного (в этом случае вызываемому коду возвращается ошибка).
На основе кода события и значения аргументов менеджер событий генерирует три ключа - первый только на основе кода события, второй - только на основе аргументов, и третий на основе кода и аргументов (см. пояснение 1). Затем он пытается полученными ключами последовательно расшифровать всех обработчиков событий. Если расшифровка происходит успешно, это означает, что данный обработчик готов обработать данное событие, и тогда ему передается управление.
Алгоритм шифрования должен быть выбран так, чтобы обратная операция была невозможна. При этом установить, какое событие данный обработчик обрабатывает, можно только полным перебором. Для блокирования возможности перебора в язык была введена контекстная зависимость - генерация дополнительной серии ключей, учитывающих некоторое количество предыдущих событий. Это позволило устанавливать обработчики на любые последовательности действий пользователя, например, на открытие файла с именем "Мой файл", запись в него строки "Моя строка" и переименование его в "Не мой файл".
Очевидно, что перебор комбинаций всех событий со всеми возможными аргументами займет бесконечное время и принципиально невозможен. Восстановить исходный код программы, защищенной таким образом, удастся не раньше, чем все ее ветви хотя бы однократно получат управление. Но частота вызова различных ветвей не одинакова, и у некоторых из них очень мала. Например, можно установить на слово "сосна", введенное в текстовом редакторе, свой обработчик, выполняющий некоторые дополнительные проверки на целостность кода программы или на лицензионную чистоту используемого ПО.
Взломщик не сможет быстро выяснить - до конца ли взломана программа или нет. Ему придется провести тщательное и кропотливое тестирование, но даже после этого он не будет в этом уверен!
Таким же точно образом осуществляется ограничение срока службы демонстрационных версий.
Разумеется, обращаться к часам реального времени бесполезно, их очень легко перевести назад, вводя защиту в заблуждение. Гораздо надежнее опираться на даты открываемых файлов - даже если часы переведены, созданные другими пользователями файлы в большинстве случаев имеют правильное время. Но взломщик не сможет узнать ни алгоритм определения даты, ни саму дату окончания использования продукта! Впрочем, дату в принципе можно найти и полным перебором, но что это дает? Модификации кода воспрепятствовать очень легко - достаточно, чтобы длина зашифрованного текста была чувствительна к любым изменениям исходного. В этом случае взломщик не сможет подправить "нужный" байт в защитном обработчике и вновь зашифровать его. Придется расшифровывать и вносить изменения во все остальные обработчики (при условии, что они контролируют смещение, по которому расположены), а это невозможно, т.к. соответствующие им ключи заранее неизвестны.
Существенными недостатками предлагаемого решения являются низкая производительность и высокая сложность реализации. Если со сложностью реализации можно смириться, то производительность налагает серьезные ограничения на сферу его применения. Впрочем, можно значительно оптимизировать алгоритм или оставить все критичные к быстродействию модули незашифрованными (или расшифровывать каждый обработчик только один раз), словом, дорогу осилит идущий! Интересно другое - действительно ли эта технология позволяет создавать принципиально неизучаемые приложения или в приведенные рассуждения вкралась ошибка? Было бы очень интересно выслушать мнения коллег, специализирующихся на защите информации.