среда, 29 июня 2011 г.

Обфускация, взгляд изнутри


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

Введение

В последнее десятилетие все большую долю рынка программного обчеспечения стали занимать программные продукты, рапространяемые в промежуточном архитектурно-независимом коде или иначе в байт-коде. Это спровоцировало увеличение рисков получения и несанкционированного использования исходного кода из исполняемых модулей. Подбная проблема беспокоит большое количество комапний-разработчиков программного обеспечения, они заинтересованы в защите своей интелектуальной собственности и своих продуктов от несанкционированного реверс-инжиниринга. Хотя закон об авторском праве и препятствует нелицензионному использованию программного продукта, разработчики обеспокоены возможностью воровства проприетарных структур данных и алгоритмов. Несмотря на наличие некоторых методов защиты программных продуктов, таких как шифрование, выполнение на стороне сервера и использование машинного кода, обфускация является пожалуй самым дешевым и простым способом решения данной проблемы [6]. Обфускация кода – это процесс создания нечитаемого кода, или, сильно затрудняющий его анализ. Процесс обфускации включает в себя трансформацию кода, которая меняет представление кода оставляя его логику прежней. В данной статье будет проведен обзор различных примеов обфускации, используемых в настоящее время в существующих специализированных инструментах.
Промимо всего прочего обфускация кода также может быть использована не только для защиты интелектуальной собственности. Обфускация может быть использована авторами вредоностного кода для сокрытия и маскировки небезопасного кода. Многие вирусы используют различные техники обфускации для защиты от антивирусных сканеров. С помощью всевозможных преобразований они постоянно изменяют сигнатуры своего кода. В данной статье будут рассмотрены типы вирусов, которые используют обфускацию и всевозможные приемы преобразования кода.
Статья состоит из следующих разделов:
  • «Общие методы обфускации» - обзор общих базовых методов используемых повсеместно в инструментах обфускации;
  • «Обфускация препятствующая статическому анализу программ» - обсущаются приемы блокирующие статический анализ.
  • «Обфускация в фазе дизасемблирования» - обзор подходов обфускации на этапе дизассемблирования.
  • «Обфускация относящаяся к вирусам» - обзор примеов используемых создателями вирусов.

Общие методы обфускации

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

Качество обфускации

Качество преобразований осуществляемых в процессе обфускации определяется с помощью следующих критериев:
  1. Эффективность: Насколько программа становится нечитабельной.
  2. Устойчивость: Насколько сложно взломать программу автоматизированными средствами деобфускации.
  3. Прозрачность: Насколько хорошо обфусцированный код взаимодействует с остальным кодом.
  4. Стоимость: Насколько велик объем избыточного кода в обфусцированном проиложении.

Преобразования управляющей логики

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

Преобразования вычислений

Есть строгая зависимость между сложностью кода и числом конструкций, которое содержится в коде. С увеличиением числа конструкций в коде, добавить дополнительный запутывающий код становиться проще. Поэтому, наличие конструкций, сложных для восприятия предоставляет возможность для последующей обфускации программы. Например, базовый блок кода, B можно разделить на две части путем вставки некоторого запутывающего условия PT (которое всегда возвращает True) в середину блока B. После этого для каждого из получившихся блоков можно применять различную технику обфускации. Также можно обфусцировать цикл путем изменения условия выхода из цикла некоторым сложно воспринимаемым выражением. Это может быть осуществлено с помощью добавления сложных блоков условий PT (всегда возвращает True) и PF (всегда возвращает False) которые не влияют на то, сколько раз будет выполняться тело цикла.

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

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

Модификация связей наследования

В соответствии с метрикой Чидамбера (Метрика Чидамбера определяет сложность класса основываясь на количестве методов класса, глубине дерева наследования класса, количества прямых производных классов, количество прочих классов, с которыми данный класс связан, количества методов, которые могут быть выполнены в результате получения некоторого сообщения классом и т.д.) сложность программы увеличивается с увеличением глубины дерева наследования. Вместе с этим, можно исскуственно увеличить сложность программы либо путем разделения класса на несколько частей, либо путем добавления дополнительного фиктивного класса. Полагая, что класс C – это класс который будет разделен на C1 и C2, необходимо убедиться в том, что данное разделене не повредит область видимости всех переменных класса C. Другими словами, если разделить класс на две части, в одной части может ошибочно нехватать каких то переменных, которые оказались только во второй части. Кроме того, связи наследования могут быть искажены после ложного рефакторинга. Рефакторинг имеет место в следующих случаях. Во-первых, при идентификации двух классов, реализующих одинаковую логику. Во-вторых, перемещение логики, общей для двух классов в родительский класс. Ложный рефакторинг похож на обычный рефакторинг, за тем исключением, что ложный рефакторинг выполняется над двумя незвисимыми классами, которые не имеют общей логики.

Реструктуризация массивов

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

Обфускация процедурных абстракций

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

Табличная интерпретация

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

Встраиваемые и невстраиваемые методы

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

Клонирующие методы

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

Обфускация встроенных типов данных

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

Разделение переменных

Некоторые переменные могут быть разделены на две или несколько переменных. В процессе разбивки переменной V типа T на две переменные p и q типа U, необходимо обеспечить реализацию следующих элементов:
  1. Функция f(p,q), которая отображает значения p и q на соответствующее значение переменной V.
  2. Функция g(V) которая отображает значение V на соответствующие значения p и q.
  3. Новые операции приведения в терминах операций над p и q.
Эффективность, надежность и стоимость данного вида преобразований увеличиваются с увеличением количества переменных, на которые осуществляется разбивка исходных переменных.

Конвертация статических и процедурных данных

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

Склеивание скалярных переменных

Этот прием обфускации основан на объединении двух или более скалярных переменных в одну. Переменные v1, v2…vk могут быть объединены в одну переменную Vm, которая содержит в себе все диапазоны значений переменных v1, v2…vk. Арифметические операции которые выполнялись над переменными также подвергнутся преобразованию, сфокусированном на операциями над Vm. Однако степень эффективность подобных преобразований так же невысока, а стало быть подобные преобразование так же не являются панацеей.

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

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

Преобразования управляющей логики

Преобразования управляющей логики осуществляются в два этапа. Первый этап осуществляет декомпозицию высокоуровневых переходов в серию выражений «if-then-goto». Следующий этап подразумевает модификацию выражений goto таким образом, что бы адресс перехода вычислялся динамически а не был задан в коде явно. Это может быть выполнено с помощью разветвления кода в соответствии с используемыми переменными. В процессе выполнения преобразований будет осуществлена замена непосредственных адресов переходов на динамически вычисляемые исходя из значений переменных, которые доступны в данном блоке кода и в результате упрощения графа управляющей логики. После проведения преобразований, явно указаные адреса переходов будут отсутствовать, а стало быть задача построения дерева переходов усложнится – теперь придется анализировать и запоминать последнее определение каждой переменной до выполнения каждого перехода.

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

После применения преобразований управляющей логики, проблема построения дерева напрямую зависит от анализа потока данных, что также является трудоемкой задачей. Фундаментальная сложность  анализа потока данных свяазана с наличием иерархий данных в программе. Целью данного типа преобразований является создание дополнительных уровней данных в программе, которые будут определять вычисление, и соответственно анализ адресов переходов. Преобразования выполняются в следующем порядке:
  1. В каждый метод добавить произвольное число переменных указателей.
  2. Вставить исскуственные блоки или кода в существующих блоках, которые устанавливают значения для этих переменных-указателей.
  3. Заменить ссылки на переменные и элементы массива этими указателями. Вычислния, использующие данные переменные должны быть обновлены таким образом, что бы они осуществлялись через указатели.
  4. Определение указателей должно находится в других блоках, а не в тех где происходит их использование.
Эффектом подобных преобразований станет ситуация, когда статический анализатор будет не в состоянии определить какой блок выполнять. А поскольку определение указателей и их использование находится в разных местах, анализатор не сможет определить какое определение указателя соотностится с его использованием. Данный подход запутывания статического дизассемблирования приводит к увеличению времени анализа и уменьшению точности анализа до уровня бесполезности.

Обфускация кода на этапе дизассемблирования


Большинство рассмотренных приемов обфускации применяются на этапе декомпиляции. Но мы можем обфускировать код и на этапе дизассемблирования. В данной секции будут рассмотрены два наиболее широко применяемые алгоритма статического дизассемблирования и приемы для предотвращения каждого из них. Существует два метода дизассемблирования: статическое дизассемблирование, когда файл, подвергаемый дизассемблированию с помощью дизассемблера не выполняется в процессе дизассемблирования; и динамическое дизассемблирование, когда файл выполняется с использованием некоторых входных данных в процессе дизассемблирования. В данной секции рассматриваются только приемы, предотвращающие статическое дизассемблирование. Существует два вида статического дизассемблирования: линейная развертка и рекурсивный проход. При линейной развертке дизассемблер осуществляет обработку инструкций программы в порядке их следования в коде. При рекурсивном проходе дизассемблер обрабатывает инструкции в соответствии с управляющей логикой программы. То есть, когда встречается инструкция ветвления, дизассемблер определяет возможные варианты выполнения программы и осуществляет обработку этих ветвей. Методы описанные далее описаны в работе Линна [9].

Предотвращение дизассемблирования

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

Вставка мусора

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

Предотвращение линейной развертки

Дизассемблирование методом линейной развертки не может распознать данные в текстовой секции. Этот факт может быть использован для предотвращения дизассемблирования путем вставки мусорных байтов в определенном месте следования инструкций. Эти байты могут быть вставлены перед блоком, который никогда не будет выполняться в том порядке, в котором он находится относительно других инструкций. В программах, которые оптимизированы компилятором подобные блоки могут содержать до 30 инструкций. Для того, что бы поместить большее число мусорных данных, можно воспользоваться следующим преобразованием инструкций ветвления. Исходный блок условия
bcc Addr
где cc представляет условие, преобразовать к виду:
bcc L’
jmp Addr
L’:
где cc это дополнение к предыдущему условию

Предотвращение рекурсивного прохода

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

Функции ветвления

Предположение, что вызов функции возвращает управления инструкции следующей непосредственно за инструкцией вызова можно использовать в корыстных целях. Предположим имеется конечная карта переходов в программе:
Φ = {a1b1,a2b2 ... anbn}
Функция ветвления ƒΦ – это функция, вызываемая из ai и в результате ее вызова управление передается bi. Данную функцию ветвления можно заменить на выражение с n условиями:
A1:jmp b1
A2:jmp b2
An:jmp bn
А это в свою очередь можно преобразовать следующим образом:
A1:jmp ƒΦ
A2:jmp ƒΦ
An:jmp ƒΦ
Подобная функция ветвтения имеет два предназначения. Первое – это предотвращение определения адреса перехода внутри функции ветвления, и второе – это создание возможности для введения запутывающей логики для дизассемблера. Подобная функция ветвления может быть реализована несколькими способами. Одна реализация может использовать адреса из некой таблицы. Другое решение, предполагает вычисление адреаса перехода через смещение относительно инструкции, следующей за той которая содержит вызов данного метода.

Преобразование вызова

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

Запутывающие предикаты

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

Сокрытие таблице переходов

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

Обфускация кода используемая в вирусах

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

Типы вирусов

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

Полиморфные вирусы

Полиморфные вирусы являются расширениями шифрованных вирусов [1, 14]. Шифрованные вирусы просто зашифровывают свое тело и после этого присоединяют к себе ключ для расшифровки, который меняется от генерации к генерации. Полиморфные вирусы также зашифровывают свое тело, однако помимо этого они случайным образом изменяют свой алгоритм шифрования каждый раз, когда осуществляется инфицирование вирусом исполняемого файла-жертвы. А поскольку схема расшифровывания никогда не бывает защищена шифрованием и как правило добавляется всегда в конец файла, то это может послужить хорошую службу антивирусному сканеру. Поэтому, когда полиморфный вирус изменяет свой код дешифрации, он изменяет часть своей сигнатуры, которая является основным инструментов для сканера на распознавание вируса. Сканер в свою очередь может быть достаточно умным и подождать пока вирус осуществит деобфускацию. Также сканер может иметь возможность осуществлять выполнение вируса в «песочнице» или эмуляторе, которые позволяют вирусу выполняться без опасности для остального окружения. Как только вирус начинает выполнятся, он должен осуществить дешифрование своего тела для того что бы иметь возможность заразить жертву и поэтому эмулятор может просканировать исполняемый код на соответствие известным сигнатурам вирусов. Очевидно, данный прием имеет ограничения, одним из которых является незнание того сколько по времени необходимо выполнять код когда станет возможным проводить соспоставление по сигнатуре.

Метаморфные вирусы

Метаморфные вирусы, подобно полиморфным, осуществляют шифрование своего тела для того что бы спятать сигнатуру используемую анивирусным сканером. Метаморфные вирусы, однако, могут изменять свой исходный код и после этого осуществлять перекомиляцию самих себя если на машине-жертве доступен компилятор. Это позволяет вирусу добавить или удалить мусорный запутываюший код, который каждый раз оказвает влияние на сигнатуру кода. Метаморфные вирусы более продвинуты, нежели полиморфные. В то время как полиморфные вирусы дешифруют свое тело и после этого осуществляют заражение жертвы, метаморфные ведут себя иначе. Они никогда не раскрывают свое тело, что делает процес определения вируса практически невозможным для достаточно простого сканера. Как результат, сканеру приходится использовать различные эвристические приемы для идентификации метаморфного вируса. Среди основных приемов по прежнему используются эмуляторы, виртуальные машины которые эмулируют некоторую функциональность операционной системы. Вызовы системы слежения – это техника которая усиленно исследовалась последние годы [12, 8]. М Христодореску и другие недавно разработали прием статического анализа для выявления метаморфных вирусов [3]. Все приемы как правило основаны приемах, известных реверс-инжинерам коммерческого программного обеспечения. Статический анализ, декомпиляция и такогих приемов, котоыре в терминах теории обфускации можно назвать вставками «мертвого» кода.

Приемы обфускации

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

Вставка «мертвого» кода

Этот вид преобразований представляет собой имено то, что гласит его название, он использует вставку бессмысленных инструкций вперемешку с существующими инструкциями. Это могут быть инструкции которые ничего не делают (nop), или которые изменяют состояние программы, но потом возвращают предыдущее состояние обратно, или же инструкции переходов корые не позволяют вставленным инструкциям получить управление во время выполнения программы. С помощью всех этих изменений невозможно запутать человека, осуществляющего реверс инжиниринг, как это было показано М. Христодореску в [3], но вполне возможно воспрепятствовать нормальной работе антивирусных сканеров.

Трансформация кода

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

Перераспределение регистров

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

Замещения инструкций

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

Сравнения

Создатели вирусов осуществляют обфускацию кода для того что бы изменить сигнатуру кода вируса, таким образом, что бы антивирусные сканеры былы не в состоянии их идентифицировать. Это имеет смысл поскольку сканеры могут искать факт заражения вирусом даже там, где его нету. Это очевидное отличие от той обфускации, которая используется для затруднения реверс инжиниринга. В последнем случае обфускация используется для защиты и сокрытия некоторой чувствительной информации, о наличии которой знает человек, занимающийся реверс инжинирингом программы. Вцелом, приемы обфускации используемые вирусописателями не настолько эффективны, как приемы используемые в коммерческом програмном обеспечении. Однако, существуют примеры когда создатели вредоносного кода могут использовать методы обфускации, коорые вполне годятся и для комерческих программ. Таким примером может быть прием используемый вирусом, который называется Zmist, для того что бы спрятать свой полиморфный дешифратор [12]. Код дешифрации помещается в код жертвы небольшими фрагментами, которые соединены между собой инстркуциями безусловных переходов. Такой прием можно использовать в коммерческих решениях. Важные и чувствительные алгоритмы могут быть разбиты на фрагменты и разбросаны по всему остальному коду. Главным образом, преобразования применяемые к вирусам не защищают от атак реверс инжиниринга и поэтому не могут применятся для защиты коммеческих продуктов.

Взгляд под другим улом

На обфускацию можно посмотреть еще под одним углом, который до этого еще  не рассматривался в данной публикации. Это обфускация, которая используется для предотвращения вредоносных встраиваний кода направленных изменения функционирования комерческих прогрудктов..Подобные атаки, как правило реализуются путем вставки вредоносного кода в существующую программу с целью получения управления этим вставленым кодом [10]. Очевидно, что для того что бы вставить подобный вредоносный код и успешно его выполнить необязательно иметь четкое представление обо всех нюансах работы кода программы-жертвы. Для предотвращения подобных атак, имеет смысл запутать программу настолько, что бы было сложно понять куда нужно вставлять вредоносный код для получения управления. И для таких целей обфускация также придет на помощь. В данный момент проводятся исследования вопросов обфускации, основной целью которой является динамическое изменение местоположения данных и выполняемого кода во время выполнения програмы [11, 2]. Подобные приемы призваны не только усложнить восприятие программы, но также и реализовать защиту от повторяющихся атак, которые не смогут успешно завершится изза того, что структура программы будет постоянно изменяться. Это особено интересный прием, так как он может оказывать влияние на то, как вирусы могут мутировать для того что бы избежать идентификации сканерами.

Заключение

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

В основу данной статьи положена публикация Arini Balakrishnan и Chloe Schulze «Code Obfuscation Literature Survey».

Литература

  1. Understanding and managing polymorphic viruses, 1996.
  2. Sandeep Bhatkar, Daniel C. DuVarney, and R. Sekar. Address obfuscation: an efcient approach to combat a broad range of memory error exploits.
  3. M. Christodorescu and S. Jha. Static analysis of executables to detect malicious patterns. In 12th USENIX Security Symposium, pages 169–186, August 2003.
  4. Christian Collberg, Clark Thomborson, and Douglas Low. A taxonomy of obfuscating transformations, July 1997.
  5. Christian Collberg, Clark Thomborson, and Douglas Low. Manufacturing cheap, resilient, and stealthy opaque constructs. In Principles of Programming Languages 1998, POPL’98, pages 184–196, 1998.
  6. Christian S. Collberg and Clark Thomborson. Watermarking, tamper–proofing, and obfuscation — tools for software protection. In IEEE Transactions on Software Engineering, volume 28, pages 735–746, August 2002.
  7. Christian S. Collberg, Clark D. Thomborson, and Douglas Low. Breaking abstractions and unstructuring data structures. In International Conference on Computer Languages, pages 28–38, 1998.
  8. A. Lakhotia and E. U. Kumar. Abstract stack graph to detect obfuscated calls in binaries. In IEEE International Workshop on Source Code Analysis and Manipulation, September 2004.
  9. C. Linn and S. Debray. Obfuscation of Executable Code to Improve Resistance to Static Disassembly, 2003.
  10. C. M. Linn, M. Rajagopalan, S. Baker, C. Collberg, S. K. Debray, J. H. Hartman, and P. Moseley. A multi–faceted defence mechanism against code injection attacks.
  11. M. Madou, B. Anckaert, P. Moseley, S. Debray, B. De Sutter, and K. De Bosschere. Software protection through dynamic code mutation.
  12. Peter Szor and Peter Ferrie. Hunting for Metamorphic, September 2001.
  13. Chenxi Wang, Jonathan Hill, John Knight, and Jack Davidson. Software tamper resistance: Obstructing static analysis of programs. Technical Report CS–2000–12, 12 2000.
  14. T. Yetiser. Polymorphic Viruses: Implementation, Detection, and Protection. VDS Advanced Research Group, January 1993.