Автор: Пользователь скрыл имя, 19 Октября 2011 в 03:02, курсовая работа
Целью данной работы является создание эффективного программного обеспечения для внедрения в исходные тексты программ информации об авторских правах, что позволит исключить несанкционированное их использование.
Введение 4
1 Описание методов и средств решения задачи 6
1.1 Постановка задачи на курсовое проектирование 6
1.2 Общие аспекты проблемы авторского права 6
1.3 Современные методы защиты программного обеспечения 7
2 Методы внедрения водяных знаков 19
2.1 Исследование статистических свойств исполняемого кода 19
2.2 Предлагаемые методы внедрения водяных знаков 21
2.3 Оценка эффективности подстановок 24
3 Описание разработанного программного обеспечения 26
3.1 Общая структура 26
3.2 Проектирование базы данных 26
3.3 Описание основных компонентов 27
3.4 Описание алгоритма программы 30
4 Описание результатов работы 32
4.1 Описание главной формы программы 32
4.2 Описание результатов работы программы и выводы по ним 33
Заключение 35
Список использованных источников 36
Приложение А. Листинг программы 37
Маскирующие преобразования программ
Защита ПО от нелегального использования его отдельных функциональных модулей в последние годы все более чаще достигается путем обфусцирования (obfuscation) исходного кода. Под обфусцированием понимается преобразование кода программы так, что бы максимально затруднить процесс декомпиляции и анализа кода с целью восстановления реализованных в нем алгоритмов.
Одним из способов такой защиты является маскировка программ, заключающаяся в применении к исходному тексту программы цепочки маскирующих преобразований, то есть преобразований, сохраняющих реализуемую программой функцию (являющихся функционально эквивалентными), но затрудняющих понимание этой функции.
Все
маскирующие преобразования делятся
на текстуальные преобразования, преобразования
управляющей структуры и
В
группе текстуальных маскирующих преобразований
рассматриваются преобразования удаления
комментариев, переформатирования текста
программы и изменения
Int fib(int n)
{
int a, b, c;
a = 1;
b = 1;
if (n <= 1) return 1;
for (; n > 1; n--) {
c = a + b;
a = b;
b = c;
}
return c;
}
Рисунок 1 – Исходная программа
int fib(int n)
{
int a, b, c, i;
long long t;
a = 1;
b = 1;
if (n <= 1) return 1;
while (1) {
t = n * n % 65537;
for (i = 0; i < 15; i++)
t = t * t % 65537;
if (n * t <= 1) break;
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
Рисунок
2 – Обфусцированная программа
Другое маскирующее преобразование - введение непрозрачных предикатов - является ключевым для повышения устойчивости других маскирующих преобразований, например, внесения недостижимого кода. Непрозрачным предикатом называется предикат, всегда принимающий единственное значение true или false. При маскировке программы предикат строится таким образом, что его значение известно, но установить по тексту замаскированной программы, является ли некоторый предикат непрозрачным, трудно.
Лексические преобразования. Этот вид затемняющих преобразований является самым простым и в то же время обеспечивающим наименьшую защиту. Действие лексических преобразований распространяется только на лексическую структуру программы, т.е. фактически сводится к переименованию идентификаторов (переменных, классов, методов), затрудняя, таким образом, анализ логики программы на основании имен этих идентификаторов.
Основной сферой применения лексических преобразований являются интерпретируемые языки программирования исходные программы на которых компилируются не в машинный код, а в байт-код – машинно-независимый псевдоязык, понятный интерпретатору языка. Обычно байт-код содержит информацию об исходном коде (имена идентификаторов, названия классов и т.п.), поэтому вопрос скрытия такой информации является существенным для интерпретируемых языков. Пример лексического преобразования показан на рисунках 1 и 2. На рисунке 1 приведен оригинальный код программы, на втором – код той же программы после лексического затемнения.
Лексические преобразования предотвращают некоторое количество попыток воровства интеллектуальной собственности, однако не создают серьезного препятствия для опытного реверс-инженера.
Коллберг
предложил несколько
Существует достаточно большое количество преобразований процесса выполнения, похожих на показанные на рисунке 4. Устойчивость таких преобразований напрямую зависит от устойчивости непроницаемых предикатов, на которые они опираются. Таким образом, очень важно уметь конструировать устойчивые непроницаемые предикаты, важными критериями выбора которых является их незаметность и низкая стоимость.
При выборе непроницаемых предикатов логично исходить из проблем, с которыми сталкиваются автоматизированные системы, проводящие анализ затемненного кода. Так, большинство таких систем используют методы, основанные на статическом анализе. Принимая во внимание факт, что точный статический анализ структур, основанных на указателях и параллельных регионах, является достаточно затруднительным, эти структуры можно использовать в качестве непроницаемых предикатов.
В качестве примера сильных непроницаемых предикатов можно привести предикаты на основе псевдонимов. Основной идеей этого метода является добавление в программу кода, который строит множество сложных динамических структур. Множество глобальных указателей ссылаются на вершины внутри этих структур. Вышеуказанные структуры в ходе выполнения программы будут случайным образом обновляться (будут изменяться внутренние указатели, добавляться и удаляться вершины и т.п.), но некоторые условия будут поддерживаться в неизменном состоянии. Примерами таких условий могут быть «указатели p и q никогда не будут указывать на один и тот же элемент в куче», «существует путь от p до q» и т.п. Впоследствии эти условия будут использоваться для генерации непроницаемых предикатов.
Определение графа логики программы извне – достаточно простая операция, линейно зависящая от количества простых блоков в программе. По вырожденному же графу достаточно сложно определить ход работы программы. [3]
Вырождение
графа логики проходит в два этапа.
На первом этапе высокоуровневые
структуры (например, циклы) преобразуются
в эквивалентные if-then-goto-
На втором этапе операторы goto, появившиеся в результате первого этапа, модифицируются таким образом, что цели этих операторов goto определяются динамически. Пример второго этапа приведен на рисунке 5.
Рисунок
5 – Пример второго этапа
Преобразования данных. Третьим типом затемняющих преобразований является затемнение данных. Наиболее распространенным преобразованием является разделение переменной – представление атомарного значения (например, целого или булевого) в виде некоторой сложной структуры данных. На рисунке 6 показана реализация операции логического И.
На рисунке 6 показано разделение трех булевых переменных A, B и C на короткие целые переменные а1, а2, b1, b2, c1, c2 соответственно. Интересным аспектом выбранной интерпретации является возможность вычислить одно значение несколькими способами. Например, операторы (2’) и (3’) отличаются друг от друга, однако оба присваивают переменной значение False. [3]
Рисунок
6 – Разделение трех булевых переменных
Шифрование кода. Отдельным случаем затемнения являются методы, реализующие шифрование программного кода (как всего кода программы, так и отдельных его участков). Эти методы используются в основном для затемнения выполняемого машинного (не интерпретируемого) кода.
Ключ для шифрования (и соответственно расшифрования) кода должен быть надежно защищен. В идеале, он должен отличаться для каждого пользователя программы. Однако такой подход является неприемлемым для продуктов с большим количеством пользователей, поскольку предполагает, что каждому пользователю будет выдаваться отдельная копия продукта, зашифрованная ключом этого пользователя. Поэтому более удобным вариантом является использование общего ключа и его хранение в недоступном месте (например, на смарт-карте или сервере приложений). Еще одним подходом к хранению ключа является его генерация в ходе выполнения программы, однако такой подход более уязвим к реверс-инженерии.
Можно выделить два подхода к расшифровке выполняемого кода: расшифровка всего кода при запуске программы и поэтапная расшифровка необходимых участков кода непосредственно перед их выполнением с последующей обратной зашифровкой. Слабостью первого подхода является возможность снять копию участка памяти, содержащего расшифрованную программу, поэтому этот подход крайне ненадежен. Для того чтобы получить программу в незашифрованном виде, атакующему нужно выполнить все зашифрованные фрагменты, для каждого фрагмента снимая дамп памяти. При большом количестве зашифрованных фрагментов эта задача становится практически невыполнимой.
Шифрование программного кода также может применяться и для защиты кода от модификации. При шифровании некоторого участка кода совместно с данными этого участка кода можно поместить контрольную сумму его байт. Таким образом, если злоумышленник внесет изменения в шифртекст (например, с экспериментальной целью), контрольная сумма при расшифровке не совпадет с оригинальной и эти изменения можно будет отследить.
Определенный интерес представляет метод на основании саморасшифровывающегося кода. Данный метод не предполагает наличия некоторого фиксированного ключа. Программный код разбивается на участки, которые шифруются независимо друг от друга. В процессе выполнения программы каждый следующий участок расшифровывается ключом, полученным применением некоторой функции к байтам предыдущего. Очевидно, что этот метод не предоставляет защиты от неавторизованного выполнения кода, а только от реверс-инженерии и модификации выполняемого кода. [3]