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

Угрозы безопасности программного обеспечения и классификация средств атаки на средства защиты ПО. Методы и средства защиты программ от компьютерных вирусов и средств исследования программ. Анализ стандартов в области информационной безопасности.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 29.06.2012
Размер файла 1,4 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

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

Взлом программного модуля

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

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

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

Декомпиляция - преобразование исполняемого модуля приложения программный код на языке высокого уровня и получение представления приложения, близкого к исходному коду. Может быть проведена только для некоторых языков программирования (в частности, для .NET приложений, создаваемых на языке C# и распространяемых в байт-коде - интерпретируемом языке относительно высокого уровня).

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

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

Практика использования

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

Ключи HASP Time, напротив, не имеют разграничения доступа - для того чтобы изменить время в ключе, нужно знать те же самые пароли, которые используются для чтения времени. То есть злоумышленнику для продления периода работоспособности программы, ограниченной по времени, достаточно отвести назад часы в ключе, и ничто не мешает ему это сделать. Некоторые неизвестные алгоритмы, реализованные в ключах, были подвергнуты анализу. Так был восстановлен алгоритм функции SeedCode, используемый в ключах HASP. А по некоторым сообщениям в Интернете, не являются больше секретными и алгоритмы, реализуемые ключами Sentinel SuperPro, и даже новые алгоритмы кодирования и декодирования в ключах HASP4.

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

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

Выводы

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

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

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

Защита при помощи компакт дисков

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

• запись информации в неиспользуемых секторах;

• проверка расположения и содержимого «сбойных» секторов;

• проверка скорости чтения отдельных секторов.

Первые два метода бесполезны при снятии полного образа с диска. Третий метод более надёжный. Он используется, например, в защите StarForce. В этой защите также делается попытка проверить возможность записи на вставленный диск. Если запись возможна, то диск считается нелицензионным. Но существуют программы, которые могут эмулировать диски с учётом геометрии расположения данных, тем самым обходя эту защиту, и, к тому же, возможно записать диск CD-R с её учётом, и он будет признаваться лицензионным. Также возможно скрыть тип диска, чтобы CD-R или CD-RW был виден как обычный CD-ROM. Но и системы защиты тоже (используя специальный драйвер) борются с ними, пытаясь обнаружить наличие эмуляции. В настоящее время наибольшую известность в мире имеют системы защиты от копирования SecuROM, StarForce, SafeDisc, CD-RX и Tages.[1]

SecuROM

Эта технология, разработанная специалистами корпорации Sony, сочетает в себе использование специальной цифровой метки и шифрования. В процессе мастеринга на стеклянную матрицу наносится электронная метка, уникальная для каждой партии дисков. Метку нельзя скопировать при записи на диски CD-R, жесткий диск компьютера и при заводском тиражировании. SecuROM прозрачна для пользователей: для активации защищенных ею дисков не требуется вводить пароли или ключи, поскольку идентификация происходит автоматически при запуске содержащихся на диске приложений. В случае использования нелегальной копии приложение будет заблокировано, а на экране появится сообщение об ошибке.Но несмотря на все ухищрения разработчиков SecuROM, защищенные ею диски можно легко скопировать при помощи утилиты CloneCD.[2]

StarForce

Проверка подлинности диска состоит из нескольких этапов. Сначала читается информация о диске, установленном в приводе, и проверяется его метка тома. Затем выполняется 8 запросов на чтение случайных одиночных секторов с номерами в диапазоне от 1 до 65 536. Результаты чтения никак не используются, и, скорее всего, эти действия нужны для разгона диска до номинальной скорости вращения. Затем еще раз читается (но уже не проверяется) информация о диске. Все перечисленное выше проходит через драйвер файловой системы CDFS, никак не защищено от анализа и, следовательно, наверняка не влияет на процесс аутентификации.

Все остальные обращения к диску идут на более низком уровне. В той версии StarForce, анализ которой проводится, обращения адресовались драйверу устройства Cdrom и представляли собой SCSI-команды. Последовательность этих команд такова.

1. Чтение содержания диска (Table Of Content, TOC).

2. Чтение одиночных секторов с номерами 16, 17, 17 (дважды читается 17-ый сектор).

3. Чтение одиночных секторов с номерами 173117, 173099, 173081, 173063, 173045, 173027, 173009, 172991, 172973.

4. Чтение случайных 17 блоков по 8 секторов с номерами первого читаемого сектора в диапазоне примерно от 168100 до 173200.

5. SCSI-команда с кодом ОхВВ, описание которой не удалось найти в документации, но которая, скорее всего, отвечает за управление скоростью вращения привода.

6. Чтение одиночного сектора с номером 173117.

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

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

Чтение ТОС, скорее всего, требуется для определения номера сектора, с которого начинается последняя сессия мультисессионного диска. Так как сессия всего одна, то в 16 и 17 секторах как раз и хранятся описания структуры тома (метка тома, количество секторов, адрес директории диска и т. д.). А повторное чтение сектора 17, скорее всего, используется для того, чтобы примерно оценить порядок времени, затрачиваемого на один оборот диска. Разница времени между двумя чтениями одного сектора должна быть кратна длительности оборота диска.

В последовательности номеров секторов 173117, 173099, 173081, 173063, 173045, 173027, 173009, 172991, 172973 легко усматривается закономерность - каждое следующее значение на 18 меньше предыдущего. Число 18 тоже явно не случайное - на том радиусе диска, где размещаются сектора с указанными номерами, на один виток спирали помещается примерно 18 секторов. А чтение секторов в порядке убывания номера с большой вероятностью используется для того, чтобы предотвратить чтение с предупреждением, когда привод считывает во внутренний буфер не только заданные сектора, но и несколько последующих, на случай если данные читаются последовательно.

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

А дальше выполняется 17 чтений блоков со случайными номерами с целью измерения 16 интервалов времени. Если все интервалы соответствуют нужному значению, то диск признается подлинным. Если же отклонения от ожидаемых величин превышают некоторое пороговое значение, то проводится повторное вычисление скорости вращения и повторное измерение задержек между чтением блоков по 8 секторов.[1]

Способ обхода защиты

Чтобы заставить StarForce поверить, что в приводе стоит оригинальный диск, надо совсем не много: чтобы задержки между чтениями соответствовали ожидаемым. А для этого необходимо знать точные характеристики диска: радиус, на котором начинается спираль, и размер сектора. Для определения этих величин можно провести те же самые измерения, что проводит StarForce при проверке диска, а затем варьировать начальный радиус и размер сектора, пока не будут найдены оптимальные значения. Критерием оптимальности, например, может служить сумма отклонений разностей углов, и углов, полученных из замеренных интервалов.

Современное оборудование (во всяком случае, оборудование бытового класса) действительно не позволяет создавать копии защищенного диска, но написание эмулятора, способного обмануть StarForce, не представляет сверхсложной задачи. Достаточно перехватывать обращения к драйверу CD-ROM и в случае, если выполняется команда чтения, делать временною задержку, какую мог бы иметь оригинальный диск, и только после этого возвращать управление вызывающей программе.

StarForce, несомненно, является неординарной защитой. Ее исключительность заключается хотя бы в том, что до сих пор не существует надежного способа быстро создавать работоспособные копии защищенных дисков.[1]

2.1.2 Программные средства защиты программного обеспечения

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

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

• метод установки;

• используемые механизмы защиты;

• принцип функционирования.

• Системы защиты ПО по методу установки можно подразделить на:

• системы, устанавливаемые на скомпилированные модули ПО;

• системы, встраиваемые в исходный код ПО до компиляции;

• комбинированные.

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

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

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

По используемым механизмам защиты системы защиты можно классифицировать на:

• системы, использующие сложные логические механизмы;

• системы, использующие шифрование защищаемого ПО;

• комбинированные системы.

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

Более стойкими являются системы второго типа. Для дезактивации таких защит необходимо определение ключа дешифрации ПО. Самыми стойкими к атакам являются комбинированные системы.

Для защиты ПО используется ряд методов, таких как:

Алгоритмы запутывания (обфускация)- используются хаотические переходы в разные части кода, внедрение ложных процедур - «пустышек», холостые циклы, искажение количества реальных параметров процедур ПО, разброс участков кода по разным областям ОЗУ и т.п.

Алгоритмы шифрования данных - программа шифруется, а затем расшифровывается по мере выполнения.

• Метод проверки целостности программного кода - проверка на изменения в коде программы;

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

Выполнение на сервере.

По принципу функционирования СЗ можно подразделить на:

• упаковщики/шифраторы;

• СЗ от несанкционированного копирования;

• СЗ от несанкционированного доступа (НСД).

Алгоритмы запутывания (обфускация).

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

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

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

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

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

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

Определение. Пусть "TR" будет трансформирующим процессом, тогда при "PR1 =TR=> PR2" программа "PR2" будет представлять собой трансформированный код программы "PR1". Процесс трансформации "TR" будет считаться процессом обфускации если, будут удовлетворены такие требования:

• код программы "PR2" в результате трансформации будет существенно отличаться от кода программы "PR1", но при этом он будет выполнять те же функции что и код программы "PR1", а также будет работоспособным.

• изучение принципа работы, то есть процесс реверсивной инженерии, программы "PR2" будет более сложным, трудоемким, и будет занимать больше времени, чем программы "PR1".

• при каждом процессе трансформации одного и того же кода программы "PR1", код программ "PR2" будут различны.

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

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

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

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

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

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

Также на сегодняшний день процесс низкоуровневой обфускации исследован мало.

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

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

Оценка процесса обфускации

Существует много методов определения эффективности применения того или иного процесса обфускации, к конкретному программному коду.

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

• Устойчивость - указывает на степень сложности осуществления реверсивной инженерии над кодом прошедшим процесс обфускации.

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

• Стоимость преобразования - позволяет оценить, насколько больше требуется системных ресурсов для выполнения кода прошедшего процесс обфускации, чем для выполнения оригинального кода программы.

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

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

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

Виды обфускации

В статьях [4] и [5] предлагается следующая классификация преобразований, производимых в процессе обфускации:

1. Преобразования форматирования, или лексические преобразования.

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

2. Преобразования структур данных.

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

3. Преобразования потока управления программы.

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

4. Преобразования потока данных программы.

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

Лексическая обфускация или преобразования форматирования

Обфускация такого вида включает в себя следующие методы:

• удаление всех комментариев в коде программы, или изменение их на дезинформирующие;

• удаление различных пробелов, отступов, которые обычно используют для лучшего визуального восприятия кода программы;

• замену имен идентификаторов (имен переменных, массивов, структур, функций, процедур и т.д.), на произвольные длинные наборы символов, которые трудно воспринимать человеку.

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

Обфускация управления

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

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

Определение. Непрозрачной переменной V назовем переменную, вводимую при обфускации программы и удовлетворяющую следующим свойствам:

1. На стадии обфускации программы значение переменной V можно определить по самой программе и по дополнительной информации, доступной на этой стадии, но отсутствующей в тексте программы;

2. По тексту обфусцированной программы установить значение переменной V сложно.

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

Обозначим через PT

• непрозрачный предикат, возвращающий всегда истинное значение, через PF

• непрозрачный предикат, возвращающий всегда ложное значение, и через P?

• непрозрачный предикат, который может вернуть как истинное, так и ложное значение.

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

{

Int v,a=5,b=6;

v=a+b;

if (b>5) …//всегда истина

if (random(1,5)<0) … //всегда ложно

}

Внесение недостижимого кода.

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

Внесение мёртвого кода.

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

Клонирование кода.

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

A2=A2`

A2?A3

Клонирование кода (слева) и внесение недостижимого кода (справа).

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

Блок-диспетчер.

Обфускация данных

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

Изменение интерпретации значения бит.

Изменение времени жизни и области видимости переменных.

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

Разделение и объединение переменных.

Для усложнения используемых структур данных в программе можно применять соединение независимых данных или разделение зависимых. Две или более переменных v1,...,vk могут быть объединены в одну переменную V, если их общий размер (v1,...,vk) не превышает размер переменной V. При этом создастся новый поток данных, соответствующий переменной V, на который влияют отношением типа «запись» потоки данных переменных v1,...,vk. Обратно, переменные фиксированного диапазона могут быть разделены на две и более переменных. Для этого переменную V, имеющую тип Xtype, разделяют на k переменных v1,...,vk типа Ytype, то есть V = v1,...,vk. Потом создается набор функций, позволяющих извлекать переменную типа Xtype из переменных типа Ytype и записывать переменную типа Xtype в переменные типа Ytype. При этом создаются несколько потоков данных, на которые поток данных переменной V влияет отношением «запись».

Реструктурирование массивов данных.

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

А Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

B1Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

B2Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Метод шифрования программного кода

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

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

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

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

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

Метод проверки целостности кода программы.

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

1. обнаружить, что программа была модифицирована,

2. если это возможно, исправить обнаруженные изменения, в противном случае обеспечить аварийное завершение программы.

В идеальном случае обнаружение изменения в коде и соответствующее этому обнаружению аварийное завершение программы должны быть широко распределены во времени и коде программы, чтобы запутать потенциального атакующего. Очевидно, что код < if (tampered()) i = 1 / 0 > не обеспечивает достаточной защиты в связи с возможностью тривиального обнаружения и устранения.

Следующие два способа являются наиболее используемыми для проверки программы на наличие внесенных изменений:

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

2. исследовать правильность промежуточных результатов, получаемых в программе. Этот подход известен как проверка программы (program verification). Кроме рассматриваемых целей, этот метод также может использоваться для верификации или тестирования программы на этапе разработки и является самым применяемым для решения задачи обнаружения изменений в программе.

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

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

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

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

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

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

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

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

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

Электронная цифровая подпись

Основные определения, обозначения и алгоритмы.

Для реализации схем электронной цифровой подписи (или просто цифровой подписи) требуются три следующих эффективно функционирующих алгоритма:

Ak - алгоритм генерации секретного и открытого ключей для подписи кода программы, а также проверки подписи, - s и p соответственно;

As - алгоритм генерации (проставления) подписи с использованием секретного ключа s;

Ap - алгоритм проверки (верификации) подписи с использованием открытого ключа p.

Алгоритмы должны быть разработаны так, чтобы выполнялось основное принципиальное свойство, - свойство невозможности получения нарушителем алгоритма As из алгоритма Ap.

Таким образом, если Ak - алгоритм генерации ключей, тогда определим значения (s,p)=Ak(б,в) как указанные выше сгенерированные ключи, где б - некоторый параметр безопасности (как правило, длина ключей), а в - параметр, характеризующий случайный характер работы алгоритма Ak при каждом его вызове.

Ключ s хранится в секрете, а открытый ключ p делается общедоступным. Это делается, как правило, путем помещения открытых ключей пользователей в открытый сертифицированный справочник. Сертификация открытых ключей справочника выполняется некоторым дополнительным надежным элементом, которому все пользователи системы доверяют обработку этих ключей. Обычно этот элемент называют Центром обеспечения безопасности или Центром доверия.

Непосредственно процесс подписи осуществляется посредством алгоритма As. В этом случае значениеc=As(m) - есть подпись кода программы m, полученная при помощи алгоритма As и ключа s.

Процесс верификации выполняется следующим образом. Пусть m* и c* - код программы и подпись для этого кода соответственно, Ap - алгоритм верификации. Тогда, выбрав из справочника общедоступный открытый ключ p, можно выполнить алгоритм верификации: b=Ap(m*,c*), где предикат b{true,false}. Если b=true, то код m* действительно соответствует подписи c*, полученной при помощи секретного ключа s, который, в свою очередь, соответствует открытому ключу p, то есть m=m*, c=c* и наоборот еслиb=false, то код программы ложен и (или) код подписан ложным ключом.

Примеры схем электронной цифровой подписи. К наиболее известным схемам цифровой подписи с прикладной точки зрения относятся схемы RSA, схемы Рабина-Уильямса, Эль-Гамаля, Шнорра и Фиата-Шамира, а также схема электронной цифровой подписи отечественного стандарта ГОСТ Р 34.10-94.

Рассмотрим несколько подробнее наиболее известные схемы цифровой подписи:

Подпись RSA: Вход: Числа , где и - большие l-разрядные простые числа, . Открытый ключ , секретный ключ , такой, что и наибольший общий делитель , где

Генерация ключей:(d,(e,n))=Ak(l,b).

Подпись:As: 1.md(mod n)c, где c - подпись кода программы m.

Верификация:Ap: 1.[c*]e(mod n)m**,

2.если m**=m*, подпись верна.

Подпись Эль-Гамаля: Вход: Числа и , где - простое l-разрядное число, а - первообразный корень по модулю . Секретный ключ , открытый ключ , такой, что , m - подписываемый код программы.

Генерация ключей:(d,(e,g,p))=Ak(l,b).

Подпись: As: 1.rgk(mod p), где kRZp-1;

2.находится такое c, что m[kc+dr](mod p-1),

где (c,r) - подпись кода m.

Верификация:Ap: 1.если gm*=errc*(mod p), то подпись верна.

Подпись стандарта ГОСТ Р 34.10-94.

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

Генерация ключей:(x,(g,p,q,y))=Ak(l,b).

Подпись: Ax: 1.r[gk(mod p)](mod q), где kRZq.

2.s[xr+km)](mod q), где m=h(M) -рассматривается как значение хэш-функции h, соответствующей отечественному стандарту на функцию хэширования сообщений ГОСТ Р 34.11-94. Таким образом, пара (r,s) - есть подпись кода программы M.

Верификация: Ay: 1.vm-1(mod q).

2.u[gsvy-rv(mod p)](mod q);


Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.