02 июня 2020 года    
Вторник | 13:00    
Главная
 Новости
Базы данных
Безопасность PC
Всё о компьютерах
Графика и дизайн
Интернет-технологии
Мобильные устройства
Операционные системы
Программирование
Программы
Связь
Сети
 Документация
Статьи
Самоучители
 Общение
Форум







Разделы / Безопасность PC / Криптография

Технология криптозащиты кода

Технология криптозащиты кода 
Автор: Дмитрий Козлов

Введение

За время развития теории криптозащиты многое уже было сказано о защите кода от копирования. В сети можно встретить материалы, содержащие подробный анализ популярных систем препятствования незаконному копированию данных и кода, поэтому в данной статье я не стану подробно их описывать.

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

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

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

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

Теоретическая основа

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

Закодируем важные участки кода так, чтобы правильно декодировать их можно было только с помощью законно полученного ключа. Без ключа приложение перестает быть работоспособным, или работает в режиме оценочной версии. Алгоритм кодирования будет являться тестом законного использования. Условный переход IF «декодировано_правильно» THEN ... – это всего лишь формальность, которая исключит ошибку нарушения доступа при выполнении неверно декодированного участка.

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

Приложение с криптозащитой остается уязвимым по отношению к атаке, когда взломщик покупает лицензированную копию и, запустив ее, снимает из дампа памяти декодированную версию. Следовательно, сразу после исполнения декодированного участка приложения необходимо его вновь закодировать, или переносить обратно заранее сохраненный закодированный участок. У взломщика останется единственная возможность поймать момент, когда в памяти исполняется декодированный участок, сохранить рабочий код приложения и действовать таким образом далее, в конечном итоге собирая приложение в единое целое по кускам, но это ручная кропотливая работа, тем более если предусмотреть большое количество маленьких участков кода, с их последовательной расшифровкой. Борьба с атаками такого рода заключается в как можно большем количестве закодированных участков, их замене в каждой новой версии системы. Предлагаемая мной технология позволит облегчить процесс встраивания защиты, в которой «увязнет» взломщик.

Как работает алгоритм?

Область данных приложения и исполнимый код процесса лежат в одном виртуальном пространстве адресов, поэтому можно работать с кодом как с данными. Ограничение, которое накладывает загрузчик – это защита от записи тех областей памяти, которые содержат исполнимый код. Попытка произвести запись в страницы с кодом вызовет ошибку нарушения доступа. Однако есть возможность с помощью функции VirtuаlРrоtect изменить атрибут pаge_еxеcute на pagе_exесute_rеаdwrite.

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

Рис.1

После обработки откомпилированного юнита код будет выглядеть так:

Рис.2

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

Рис.3

В первом случае код установки и снятия криптозащиты, а также защищаемый код должны размещаться внутри одной или в последовательно выполняющихся процедурах. Для случаев 2 и 3 удобнее поместить код в двух процедурах – установка и снятие защиты – в главной процедуре, защищенный код – в вызываемой процедуре. Рассмотрим подробно вариант #1, на его основе несложно реализовать и другие алгоритмы.

Как вносить в код особые указатели? Этот вопрос решает встроенный ассемблер:

asm
  JMP @@ор
  DB "особый указатель"
  @@ор:
end;
В случае с первым указателем следует проявить осторожность, при криптовании надо будет получить дальний прыжок, который в прямом смысле может стать дальним. Надо зарезервировать достаточное место под команду нужной длины:
 
asm
  DB $E9
  DD длина_указателя
  DB "особый указатель"
end;
Программа, производящая кодирование юнита, должна изменять первый безусловный прыжок, то есть удваивать 4х-байтное значение «длина_указателя» и прибавлять к нему расстояние между указателями.

Какова необходимая длина особого указателя? Считается, чем длиннее указатель, тем меньше вероятность встретить в коде такие же последовательности байтов. По опыту могу сказать, что 8-ми байтов достаточно, но каждый может выбрать это значение для себя сам. Опять же при установке криптозащиты можно дополнительно контролировать код конкретного приложения, произведя в нем поиск выбранной в качестве указателя последовательности байтов. Желательно чтобы указатель был похож на реальный код. Ни в коем случае нельзя использовать бросающееся в глаза значение типа «FFFF...» или «КРИПТУЕМ ЗДЕСЬ». Я обычно использую датчик случайных чисел для их генерации.

Итак, особые указатели расставлены. Поиск их откомпилированном файле не труден – получаем файл как указатель посредством MapViewOfFile и ищем. А что же искать во время выполнения? Ответ очевиден: переменная Hinstance представляет собой адрес, по которому загрузилось приложение или dll-ка. Надо сканировать, начиная с этого адреса.

В Delphi есть функция сканирования StrPos, но она не подходит, так как и сам код приложения, и искомый указатель могут содержать «нули». Маленькая переделка кода на ассемблере – и в распоряжении у нас функция StrPosN, не зависящая от концевых нулей (#0).

Блок-схема

Приведу схему действий, которые происходят в процедуре, когда криптуется часть ее кода:

получить значение указателя;
try
  if <найден указатель в приложении> then 
  begin
    снять копию n байтов после указателя;
    декодировать n байтов после указателя; 
    if <последние байты совпали с указателем> then
      заменить перед указателем длинный прыжок коротким;
  end;

  ОСОБЫЙ_УКАЗАТЕЛЬ
  КОД, КОТОРЫЙ БУДЕТ ПОДВЕРГНУТ КРИПТОЗАЩИТЕ
  ОСОБЫЙ_УКАЗАТЕЛЬ

finally
  if <существует копия участка> then
    вернуть на место копию и длинный прыжок;
end;
Число «n байтов» находится из четырех байтов, стоящих перед первым указателем.

В «получении указателя» и в «декодировании» обязательно следует использовать средства аппаратной защиты. Так приложение «привязывается» к ключу. В простейшем случае можно использовать кодирование на основе скрытого значения пароля. Такая схема пока еще не претендует называться «технологичной», так как необходимо помещать большое количество строк непосредственно в исходник приложения, причем тут не обойдешься простым копированием – указатели на каждом участке должны быть уникальными. Еще могут возникнуть трудности с отладкой этого приложения, при тестировании функциональности, а не самой защиты. На помощь приходит PreCompiler, призванный автоматизировать процесс. Опция {$I file.inc} помещает в указанное место приложения содержимое file.inc, а в inc-файле можно запрограммировать условную вставку. Создадим inc-файл со следующим содержимым:

crypt.inc

{$IFNDEF CRYPTOPASS0}
{$DEFINE CRYPTOPASS0}
var
  переменные для криптокодирования
{$ELSE}
{$IFNDEF CRYPTOPASS1}
{$DEFINE CRYPTOPASS1}
  Действия по декодированию;
  в самом конце - первый указатель
{$ELSE}
  В самом начале - второй указатель
  Далее зачиcтка
{$UNDEF CRYPTOPASS1}
{$UNDEF CRYPTOPASS0}
{$ENDIF} // CRYPTOPASS1
{$ENDIF} // CRYPTOPASS0

Затем в защищаемой процедуре достаточно три раза указать опцию $I и PreCompiler сделает все сам:

procedure FullSecure;
var
 . . .
{$I crypt.inc}
begin
 . . .
{$I crypt.inc}
 защищаемый участок
{$I crypt.inc}
 . . .
end;
Второй и третий $I должны находиться на «одном» уровне begin...end или try...end. Хотя, компилятор сможет это отследить. Намного осторожнее надо работать с условным и безусловным прыжками – компилятор может пропустить подобные ошибки.

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

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

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

Выводы

Описана технология криптозащиты кода в среде Delphi, позволяющая создавать программы, противостоящие незаконному копированию, без внесения существенных изменений в их исходные тексты.

Технология базируется на трех основных идеях:

  • определение и поиск особых указателей в юните;
  • использование $IFNDEF, позволяющее получать криптозащищенные юниты без изменения их исходных текстов;
  • автоматизированная генерация исходных текстов, обеспечивающая изменчивость криптозащиты.
Технология криптозащиты кода
Лента новостей


2006 (c) Copyright Hardline.ru