понедельник, 3 сентября 2012 г.

Теория использования электронных ключей защиты.

Версия 1.02
Security is a process, not a product. 
© Bruce Schneier


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

    Цель данной статьи - попробовать дать направление для фантазии разработчику ПО, приступившему к реализации защиты приложения с использованием ключей защиты. Впрочем, она может заинтересовать и практиков.

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

Что я могу сделать изначально?
Итак - у вас есть ключ и комплект разработчика (SDK).
Как правило в это-же время у вас уже есть на руках "горящий проект", который должен быть сдан уже вчера и полное отсутствие времени на изучение возможностей самого ключа. Начальство, конечно не поймет, что сам ключ еще нужно "освоить". Поэтому вам не остается ничего другого, как воспользоваться утилитами "автозащиты", поставляемыми вместе с SDK, навесив нечто, что "теоретически" воспрепятствует взломщику изучить Ваше ПО.

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

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

Что есть ключ?
1. это черный ящик - все что вы храните в нем будет недоступно взломщику (с оговорками, конечно)
2. это криптография блоков данных с закрытым алгоритмом и/или ключом (шифрование/хеширование/эцп)
3. реальная привязка к железу (не нужно думать о смене процессора и материнской платы - мы привязаны к ключу)
4. возможность создания триала как по времени, так и по количеству запусков (не на всех ключах)
5. изъятие кода приложения из его состава и исполнение внутри ключа (защита от отладки данной части кода, так-же не на всех ключах)

Как подготовить ключ к работе и начать его использовать?
Прежде чем использовать ключ, нужно его подготовить к работе. Разработчику ключ передается с настройками по умолчанию. Это означает что ваш ключ отличается от демонстрационного только кодами доступа.  Для исправления данного недостатка, ключ требует перепрошивки. На этапе перепрошивки разработчик помещает в память ключа свой собственный набор алгоритмов, со своими собственными уникальными дескрипторами, свой набор защищенных ячеек и свой уникальный блок данных в открытой памяти ключа, доступный только для чтения.
Если этого не сделать, то есть большая вероятность очень быстрого создания табличного эмулятора, т.к. для его создания пригодится обычный демонстрационный ключ распространяемый бесплатно в составе SDK.

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

Как не допустить ошибок при работе с ключом?
1. Не полагайтесь только на проверку наличия ключа:
После перепрошивки ключа, используя SDK, вы можете определить факт его наличия в компьютере. Но нельзя полагаться только на проверку его наличия. Очень часто встречаются программы использующие ключи защиты только на уровне "есть устройство - работаем, нет  - закрываемся". Проблема в том, что примеры эмуляции наличия устройств идут в составе DDK, поэтому обойти такую проверку не составит труда, да и в принципе (зачем подключать "тяжелую артиллерию") достаточно банального "занопливания" такой проверки в теле исполняемого файла.

2. Используйте аппаратные алгоритмы ключа:
В составе SDK, поставляемого в комплекте разработчика, есть АПИ, реализация которых выполнена полностью софтверно, т.е. без участия аппаратного ключа защиты. Такие функции, конечно гораздо более производительны, чем аппаратное преобразование через ключ, но построив защиту только с их участием, разработчик должен задуматься, а зачем ему тогда нужен собственно САМ ключ?

3. Не используйте внешнюю библиотеку:
В составе SDK поставляются объектные файлы, линкуемые к приложению и библиотека. При разработке кода защиты рекомендуется использовать только объектные файлы, т.к. если вы будете работать с ключами посредством библиотеки, взломщик, зная описание функций, сможет примерно за час написать полноценный ее аналог (шлюз) и без всяких сложностей с драйвером снимет весь ваш обмен данными с ключом прямо в UserMode.

4. Не используйте устаревшие версии аппаратных алгоритмов:
Базовый алгоритм ключа Guardant Stealth I не соответствует сегодняшним требованиям к безопасности. Для совместимости со старыми программами он все еще поддерживается. Но, если вы будете использовать его в своих приложениях, то очень скоро обнаружите в интернете полноценный эмулятор вашего продукта. Поэтому при защите приложения ориентируйтесь на алгоритм GSII64 и его вариации.

5. Думайте :)




С теорией закончили:

    Ну а теперь давайте посмотрим, что же именно может нам дать электронный ключ защиты.
Я приведу три варианта построения защиты на базе электронного ключа Guardant. Для простоты в примерах используется только GSII64 алгоритм. Так же в примерах используется понятие «документ». Под этим понятием подразумевается любой блок данных, который требует защиты. Это может быть отдельный файл, запись в базе данных, отдельный ресурс в программе или даже участок кода. Все три решения являются законченными вариантами, вытекают друг из друга и отличаются лишь сложностью внедрения в готовый продукт, трудозатратами на само внедрение и объемом времени, затраченного на предпродажную подготовку.

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

Решение №1:
Самое простое, что мы можем сделать, это зашифровать документ. 

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

Минусы также очевидны: т.к. используется всего лишь один единственный вопрос/ответ, то для исследователя не составит труда, вооружившись логером шины, снять эту единственную валидную пару и создать для вашего приложения эмулятор.

Первый вариант модификации - генерируем мусор:

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

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

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

Второй вариант модификации - расширяем размер запроса на ключ:

Второй вариант сокрытия правильного запроса заключается в том, что правильный запрос на ключ прячется не в куче мусорного трафика, а в теле еще более объемного запроса. 
Например запрос у нас имеет размер 8 байт. Мы можем выделить большой буфер кратный 8 байтам (например 80 байт), заполнить его мусором, поместить вместо любого 8-байтного блока наш запрос (блоки идут последовательно по 8 байт, с.м. документацию на функцию GrdTransform) и в режиме GrdAm_ECB отправить этот запрос на ключ. В данном режиме каждый 8-байтный блок кодируется независимо от других, поэтому получив ответ, мы можем забрать наш ответ из заранее известного участка буфера. 

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

Минусы: такие же, как у предыдущего варианта. Только в данном случае взломщик должен будет искать совпадение двух запросов уже не в куче вопросов, а в самом большом блоке. 

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

Третий  вариант модификации - сохраняем мусорный трафик

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

Плюсы данного подхода: теперь невозможно определить, где мусор, а где валидные данные. 

Минусы: если хранить этот трафик в памяти программы, то он потеряется при перезапуске приложения, если во внешнем файле, доступ к которому есть и у самого исследователя, то он имеет возможность подмены данного файла, на файл с нужными ему запросами.

Четвертый  вариант модификации - генерируем псевдослучайный мусор:  

Т.к. хранение мусорного трафика немного чревато, то давайте его будем генерировать самостоятельно. Например, любой функцией. К примеру, при открытии документа мы посылаем еще 10 мусорных запросов. Возьмем функцию (например, синус от Х умноженное на ПИ и поксоренное на пароль открытия документа) и прогоним в цикле переменную Х от нуля до девяти. Все 10 значений, которые вернет данная функция, и будут нашим мусорным трафиком. 

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

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

Пятый  вариант модификации - вводим контроль мусорного трафика: 

А может все-таки хранить ранее сгенерированный трафик где-нибудь? Ну например, если у вас есть постоянно изменяемая база данных, данные в которой держатся по большей части в зашифрованном виде, то вы можете тихой сапой спрятать мусорный трафик в ней, а до тех пор, пока не наберется достаточный объем, генерировать его через вышеописанную функцию. Это уже более интересный вариант, ведь генерируя мусор и получая на него ответы, мы можем этот мусор хранить в базе – причем, зная вопросы мусорного трафика, и ответы на них мы можем сверять их с ответами ключа и поднимать исключение если что-то не совпало. Т.е. теперь у нас мусорный трафик плавно превратился в контрольный трафик. Причем теперь мы  имеем возможность плавно (раз в два-три дня) пополнять эту базу мусорных (контрольных) вопросов/ответов новыми значениями, плавно вводя их в состав мусорного (контрольного) трафика, посылаемого при открытии документа. Также теперь мы имеем возможность периодически в фоне опрашивать ключ запросами из базы контрольного трафика и сверять правильность ответа на тот или иной вопрос. Причем сам контрольный трафик мы также может обрамлять каким либо мусором, который по такому же принципу можно запоминать в базе, переводя в состав контрольного, или генерировать мусор случайным образом без проверки на ответ. 

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

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

Итого:

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

Решение №2:

В первом варианте решения задачи был один огромный минус. Пароль на открытие документа всегда был один единственный и как бы мы ни старались его спрятать, исследователь всегда получал к нему доступ. То, что он теперь не знает, где именно правильный пароль, ничего не меняет. Так может быть сделаем так, чтобы документ открывался не одним паролем, а многими? 
К примеру, если у документа десять тысяч паролей, и каждый из них открывает документ, то мы можем при каждом новом открытии посылать на ключ новый пароль (обрамляя его мусором по варианту пятой модификации первого решения задачи). В этом варианте исследователю придется серьезно попотеть, ибо заставить приложение выдать такую гору паролей, требуется достаточно большое время, и это только при одном документе. А если документов тысячи? 

Как же этого добиться при использовании синхронных алгоритмов шифрования документа? Очень просто, достаточно изменить принцип хранения пароля в документе...

Вместо вопроса, помещаемого в тело документа и при помощи которого мы получаем ответ ключа для дешифровки документа, мы выполним следующий «алгоритм генерации плавающего трафика» (АГПТ): 
  • сгенерируем пароль самостоятельно 
  • зашифруем этим паролем документ 
  • объявим переменную Question, присвоим ей «заведомо случайное число – например ноль»
  • отправим значение переменной на ключ 
  • полученный ответ поксорим на значение пароля 
  • результат поместим в документ. 
Таким образом, общий вид защищенного документа на данном этапе у нас не изменился, разница только в том, что вместо вопроса, там немного другое число. 

Для того чтобы расшифровать документ, нам необходимо выполнить «алгоритм извлечения пароля из плавающего трафика» (АИППТ): 
  • опять отправить Question на ключ 
  • результат поксорить на сохраненное в теле документа число 
  • полученным значением расшифровать сам документ. 
Но где же обещанные десять тысяч паролей? 

В данном алгоритме: если мы выполним цикл, в котором значение переменной Question (инициализированной выше нулем) будем изменять от нуля до 99999, и с каждым изменением значения будем выполнять преобразование, описанное в АГПТ, то мы получим на руки ровно десять тысяч уникальных запросов на ключ, каждый из которых, гарантированно будет открывать документ. Все эти 10к паролей должны быть также помещены в документ, тогда при открытии документа нам нужно будет узнать объем (Count) плавающего трафика документа, выбрать случайное значение в диапазоне от нуля до Count – 1 и на этом числе выполнить АИППТ. 

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

Минусы: к сожалению, "ключ" не самая быстрая железяка из существующих и выполнение десяти тысяч итераций АГПТ на ключе займет достаточно продолжительное время (но всегда есть выход – трансформ большим блоком, с.м. вторую модификацию первого решения задачи). Второй минус – взломщик также может прокрутить цикл от нуля до 99999 и запомнить все вопросы/ответы.

Первый вариант модификации - генератор запросов на ключ:

Для того чтобы обеспечить достаточно хороший разброс данных, отправляемых на ключ, и отойти от минуса описанного выше, использовать итератор Question нежелательно. Давайте изменим АГПТ таким образом, что за элемент массива плавающего трафика будет браться не значение Question, а результат выполнения операции преобразования над Question, т.е. F:[ Question]. Данную функцию назовем ValueGenerator. Функцию уже думайте сами. Это может быть тот же синус от Question умноженного на ПИ, ну или что-либо другое. Целый простор для фантазии...

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

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

Второй вариант модификации - персонализируем запросы к документу:


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

Введем в АГПТ дополнительную переменную KEY, данная переменная должна быть уникальна для каждого документа и должна храниться вместе с блоком плавающего трафика в теле документа. Задача данной переменной будет сводиться в модификации каждого значения плавающего трафика. Например, данная переменная может означать начало цикла (не от нуля до 99999, а от KEY до 99999 + KEY), или на данную переменную будет ксорится значение ValueGenerator (Question) на каждой итерации цикла. 

Таким образом, после второй модификации АГПТ у нас будет выглядеть следующим образом (модификатор KEY используется в качестве ксора): 
  • сгенерировать KEY 
  • сгенерировать пароль 
  • зашифровать документ паролем 
  • в тело документа поместить модификатор KEY 
  • начать цикл for Question:= 0 to Count – 1 do или
    for (Question =0; Question < Count; Question ++) 
  • выполнить преобразование ValueGenerator (Question) 
  • результат преобразования поксорить на модификатор KEY 
  • результат ксора отправить на ключ 
  • полученный ответ поксорить на значение пароля 
  • результат поместить в тело документа 
  • перейти к следующей итерации цикла 
Новый вариант АИППТ после второй модификации:
  • Извлечь из тела документа KEY 
  • Определить размер блока с плавающим трафиком и результат поместить в Count 
  • Выбрать любое случайное число Х в диапазоне 0.. Count - 1 
  • Выполнить преобразование ValueGenerator (Х) 
  • результат преобразования поксорить на модификатор KEY 
  • результат ксора отправить на ключ 
  • полученный ответ поксорить на значение ячейки Х плавающего трафика документа 
  • расшифровать документ полученным паролем 
Плюсы данного подхода: мы имеем уникальный плавающий трафик для каждого документа, который не под силу проанализировать взломщику самостоятельно при помощи логера шины. 

Минусы: Если взломщик подменит значение Count на единицу, то трафик будет спрямлен (всегда будет браться самое первое значение из блока плавающего трафика – думайте над этим моментом, как вы будете защищаться от такой подмены). Но самый больший минус скрыт в третьем пункте АИППТ, а именно в выборе случайного числа.

Небольшое техническое отступление по вопросам рандомизации: 


Давайте посмотрим, какие методы доступны нам для генерации случайного значения. 

Delphi – random/randomize. Обе этих функции работают через опорное число ГПСЧ RandSeed. По умолчанию оно равно нулю. Никто не помешает взломщику внедриться в наш процесс и писать по адресу данной переменной ноль, предварительно выставив флаг на область памяти PAGE_GUARD чтобы отслеживать ее изменения (как получить адрес данной переменной – вне рамок статьи). Но это перестраховка на случай изменения значения RandSeed руками, обычно же данную переменную инициализируют вызовом randomize, которая берет «случайные» значения у функции QueryPerformanceCounter, ну или если не получилось то у GetTickCount (о них речь пойдет позже). 

VC++ - srand/rand. По аналогии с Delphi аналогами данные функции также работают с опорным числом, которое храниться в TLS секции нити, доступ к которому можно получиться прочитав TIB потока (mov eax, fs[$18]) 

Функция QueryPerformanceCounter плоха тем, что во первых может не поддерживаться, во вторых, в зависимости от конфигурации вашего компьютера, она реализуется как на таймере (I8254 или I8253 для ХТ), так и на уязвимой инструкции RDTSC (в частности Атлон), о которой пойдет речь ниже. 

Функция GetTickCount берет данные из KE_USER_SHARED_DATA расположенной фиксировано по адресу 7FFE0000h, собственно это наглядно видно по коду функции: 
mov edx,$7ffe0000 ;получаем адрес KE_USER_SHARED_DATA
mov eax,[edx] ; берем значение TickCountLow
mul dword ptr [edx+$04] ; перемножаем его на TickCountMultiplier
shrd eax,edx,$18 ; суммируем со смещением
ret
Достаточно положить ноль вместо TickCountMultiplier и GetTickCount будет всегда возвращать его же. 

Т.к. и QueryPerformanceCounter и GetTickCount и многие другие функции-рандомизаторы являются внешними АПИ-функциями, то можно даже не лезть так глубоко. Методы перехвата АПИ правкой таблиц импорта/экспорта уже давно доступны в интернете. Можно конечно не использовать статическую линковку функций, но тогда перехват функций происходит методом сплайсинга (заменой первых нескольких байт функции на прыжок в перехватчик). Это конечно можно тоже отследить, сравнив первые байты вызываемой функции в памяти и на диске, но взломщик против такой проверки может применить дизассемблер длин и поставить перехватчик чуть дальше по коду функции, либо подменит вызов CreateFile, которой вы будете зачитывать образ библиотеки с диска, либо произведет перехват в самом ядре. 

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

Выбор случайного значения:

Так что же нам тогда делать? Каким образом нам выбрать случайное число, значение которого будет достаточно проблематично подменить взломщику? На самом деле у нас остается еще много вариантов получить случайное число без использования стандартных методов. При работе программы вы создаете множество экземпляров объектов, работаете с окнами, запускаете задачи в нитях. Все перечисленное имеет уникальные наборы данных, окна/нити имеют уникальные хэндлы, объекты уникальные адреса. Все это можно использовать как опорное число (RandSeed) для собственного алгоритма генерации псевдослучайных чисел. Эти значения можно также пропускать через ключ, или использовать данные полученные при генерации мусорного/контрольного трафика. Можно производить выделение больших блоков памяти, рассчитывать контрольную сумму части выделенных блоков, после чего использование данную память под служебные нужды. Используйте неинициализированные переменные внутри локальных процедур, как составную часть опорного числа. Эти переменные располагаются на стеке и никогда не угадаешь, какое значение они будут иметь. Если вы работаете с изменяемыми пользователем данными (графика, текст, да все что угодно), используйте эти данные в качестве рандомизатора. Анализируйте частоту нажатий клавиш и перемещение мышки – все это есть случайные числа. 

Итого: 


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

Решение №3 - лицензирование и разграничение доступа:

Ну что ж, будем считать, что вторая модификация второго решения, полностью удовлетворяет нашим требованиям. Т.е. у нас есть программа, которая может открывать множество защищенных документов и при этом на ключ в течение продолжительного времени идет совершенно уникальный трафик. Вы распространили вашу программу по большому количеству пользователей и она уже приносит хорошую прибыль. Но! Вы, как и любой другой человек, можете допустить ошибку при проектировании вашей защиты. Представьте что это так и исследователь, воспользовавшись вашей ошибкой, смог снять весь диапазон вопросов/ответов и написал табличный эмулятор. И что самое плохое, теперь этот эмулятор может себе поставить любой из ваших текущих пользователей и пользоваться им, а вам вернуть ключ, и затребовать возврат денег, сославшись на то что программа его не устраивает (ну тут я, конечно, утрирую – но все же…). 

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

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

Каким образом это можно сделать? 

Любой ключ Guardant имеет свой уникальный ID. Любой алгоритм электронного ключа Guardant можно сделать зависимым от ID. Т.е. если ключ А в ответ на запрос 123, отправленный алгоритму с зависимостью от ID, отвечает 456, то ключ Б, на этот же запрос вернет уже совершенно другое число. Если сделать алгоритмы ключа зависимыми от ID, то каждый ключ, выданный каждому клиенту, будет уникален и никто (даже вы сами) не сможет запрограммировать второй ключ, который будет идентичен первому. 

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

Но мы можем сделать все немножко проще. 
По шагам: 
  • Помещаем в ключ два алгоритма, один с зависимостью от ID, другой нет. 
  • Из каждого защищенного документа убираем модификатор KEY и оставляем только массив плавающего трафика, сгенерированный при помощи алгоритма без зависимости от ID 
  • Все значения KEY для каждого документа помещаем в отдельный документ и называем этот документ Лицензией 
  • Лицензию шифруем с использованием АГПТ при помощи алгоритма с зависимостью от ID 
Для открытия документа теперь мы будем предпринимать следующие шаги:
  • С помощью АИППТ расшифруем документ-лицензию на алгоритме с зависимостью от ID 
  • Из лицензии получим модификатор KEY, соответствующий открываемому документу 
  • Если модификатора нет – выводим ошибку и выходим 
  • С помощью АИППТ и полученного модификатора расшифруем сам документ 
Плюсы такого подхода: Мы имеем полный набор защищенных документов, который мы можем спокойно размещать в публичный доступ. Т.к. из тела каждого документа убран модификатор KEY, то открыть этот документ, не зная модификатора, не сможет никто. Каждый пользователь обладает своим уникальным ключом, программой и полным набором документов. Теперь мы можем достаточно гибко контролировать доступ каждого пользователя к тому или иному документу при помощи лицензии, в которую помещаем список модификаторов KEY только для тех документов, к которым пользователь может иметь доступ. Данная лицензия также будет уникальной и расшифровать ее сможет только тот человек, у кого есть в наличии соответствующий ключ. Если исследователь создал эмулятор, то по ответам данного эмулятора мы можем в кратчайшие сроки идентифицировать недальновидного пользователя и разбираться с ним персонализировано. Также мы можем быть в полной уверенности, что данный эмулятор не смог скомпрометировать все установки нашего ПО, т.к. в нем попросту нет уникальных ответов на алгоритм с зависимостью от ID от остальных ключей. 

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

Первая (и единственная) модификация - предпродажная подготовка: 

Встает вопрос, как же нам сгенерировать лицензию, если мы не можем знать, какие именно ответы будет давать ключ? Можно поступить следующим образом: еще на этапе предпродажной подготовки, при программировании ключа нам необходимо снять достаточно большой блок вопросов/ответов с ключа по зависимому алгоритму и запомнить эти данные в базе данных. Каждый из этих вопросов/ответов (или только ответы, или их комбинация) у нас станет модификатором KEY, который у нас применяется в алгоритмах АГПТ /АИППТ. Сам плавающий трафик мы будем генерировать по старинке, на основе не зависящего от ID алгоритма. Значение KEY для лицензии будем хранить в теле самой лицензии, а извлекать его при помощи пятой модификации первого решения задачи. 

Таким образом, мы имеем следующую (финальную) последовательность действий: 
  • Для всех документов нам необходимо сгенерировать пароли и KEY 
  • Зашифровать все документы алгоритмом АГПТ, а все значения KEY поместить в базу. 
  • Запрограммировать набор ключей на два (минимум) алгоритма, один из которых зависит от ID 
  • Снять с каждого ключа блок вопросов/ответов и также поместить в базу 
  • Для каждого пользователя создать уникальную лицензию, в которую поместить набор значений KEY, для доступа к документам. 
  • Сгенерировать пароль для лицензии и, используя любую запись из блока вопросов/ответов ключа в качестве KEY, зашифровать лицензию алгоритмом АГПТ на обычном алгоритме. 
  • Поместить использованную запись (только вопрос, а не ответ :) ) в тело лицензии 
  • Передать лицензию пользователю 
Доступ к документу теперь выглядит следующим образом: 

  • При помощи пятой модификации первого решения задачи извлечь из тела файла лицензии значение KEY, используя алгоритм с зависимостью от ID. 
  • При помощи АИППТ и полученного KEY, расшифровать лицензию обычным алгоритмом. 
  • Получить модификатор KEY, соответствующий открываемому документу 
  • С помощью АИППТ и полученного модификатора расшифровать сам документ 
Итого: 

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

В завершение:

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

Интересные возможности ключа, на которые стоит обратить внимание:

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

В ключах Guardant Sign/Time (в отличие от более старых моделей) появился алгоритм ЕСС. Данный алгоритм позволяет подписывать блок данных цифровой подписью. Это ассиметричный алгоритм, т.е. используется пара из двух «паролей». Как это выглядит: на этапе программирования ключа для алгоритма ЕСС генерируется пара приват/паблик пароль. Приватный пароль помещается в определитель алгоритма. Публичный мы помещаем в тело программы. После чего берем любой блок данных и выполняем над ним преобразование на алгоритме ЕСС. После чего, используя публичный пароль, мы можем проверить - соответствует ли цифровая подпись этому блоку данных или нет. Если не соответствует, то это означает, что, с большой долей вероятности, ответ мы получили от эмулятора, который не знает секретного пароля, зашитого в ключ. 

Новые модели ключей стали поддерживать технологию доверенного удаленного перепрограммирования ключа (TRU – trusted remote update). Для пользователя будет очень удобно не ездить каждый раз к вам в офис, в тех случаях когда вы решили изменить прошивку ключей (например, по причине ввода нового варианта защиты), а получать новую прошивку ключа вместе с новым дистрибутивом программы. Также эту технологию можно (и нужно) применять для ключей Guardant Time. Guardant Time сами по себе очень вовремя появились, сейчас как раз на дворе мировой кризис и пользователи не могут приобретать новое ПО за большие деньги. Так почему бы не сдавать ПО в аренду, за приемлемую для клиента сумму? Именно этим и занимается данный ключ. Единственное его отличие от Guardant Sign – это то, что при программировании ключа мы также имеем возможность выставить время, в течение которого алгоритм будет работать. По истечении этого срока алгоритм деактивируется, и ключ превращается в бесполезную железяку. Вот тут-то нам и пригодится технология TRU. Пользователю достаточно будет каким либо образом перевести вам сумму на продление срока аренды, уведомить вас, и вы можете выслать ему по электронной почте маленькую утилиту, которая абсолютно безопасно перепрошьет этот ключ, продлив время работы алгоритмов на оплаченное пользователем время. 

Ну и напоследок хочу дать несколько советов по использованию всего вышенаписанного:

Не позволяйте взломщику узнать все значения плавающего трафика методом очень продолжительного снятия всех вопросов ответов. Используйте фильтр, который будет отсекать те вопросы, которые никогда не должны быть заданы ключу именно на этом компьютере. Возьмите HWID компьютера, переведите этот HWID в двоичную систему счисления и наложите на массив значений для плавающего трафика. Те значения, которые соответствуют нулю в битовой маске, должны всегда пропускаться. Например, если HWID равно 170 (10101010), то при выборе случайного значения плавающего трафика в алгоритме АИППТ должны пропускаться все четные элементы массива.

Растягивайте задачу вопросов ключу по времени. Например, у вас есть достаточно большой буфер плавающего трафика, его можно (помимо ограничения по маске) ограничить также по времени, причем не производить переход на следующий блок вопросов моментально.
Пусть у вас есть 365 вопросов (для примера, чтобы было проще понять). Возьмем текущий день (например, 26-го мая 2009-го это был 146-ой день) прибавим к нему по 30 дней с обеих сторон и будем сегодня задавать вопросы только из этого диапазона. Завтра же у нас диапазон сдвинется на 1 день (измениться только на 2 вопроса), но большинство вопросов останутся прежними.

---

Не бойтесь экспериментировать и удачи Вам.
© Александр (Rouse_) Багель
Москва, август 2012

При перепечатке статьи указывайте ссылку на первоисточник: http://alexander-bagel.blogspot.com/2012/09/blog-post.html

3 комментария:

  1. Забыли упомянуть, что все это реализовано и используется уже несколько лет в Грандсмете

    ОтветитьУдалить
    Ответы
    1. Ну если брать защиту сметы, то здесь только верхушка айсберга :)

      Удалить