WWW.KONF.X-PDF.RU
БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА - Авторефераты, диссертации, конференции
 


Pages:     | 1 | 2 || 4 |

«МОДЕЛЬ, АЛГОРИТМЫ И ПРОГРАММНЫЙ КОМПЛЕКС ДЛЯ АВТОМАТИЗИРОВАННОГО ПОИСКА УЯЗВИМОСТЕЙ В ИСПОЛНЯЕМОМ КОДЕ ...»

-- [ Страница 3 ] --

( 1) 0 = 1 где S0 — относительная граничная сложность программы, Sa — абсолютная граничная сложность программы, а v — общее число вершин графа программы.

Воспользуемся следующим алгоритмом для расчета степени вершины:

Алгоритм расчета степени вершины Входные данные: n – общее кол-во вершин в функции, индекс вершины a, множество вершин V = {v1, v2 … vn}.

Результат: степень вершины

1. Процедура node_degree(n, a, V)

2. i := 0

3. сложность := - количество адресов перехода из вершины (0|-1|-2)

4. while i n if (индекс вершины перехода 1 для vi == ai || 5.

индекс вершины перехода 2 для vi == ai) сложность := сложность + 1 6.

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

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

А. Метрика спена [87] Определение спена основывается на локализации обращений к данным внутри каждой программной секции. Спен — число утверждений, содержащих данный идентификатор, между его первым и последним появлением в тексте программы.

Следовательно, идентификатор, появившийся в коде n раз, имеет спен равный n-1.

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

Б. Метрика Генри и Кафура [89]

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

Модуль А вызывает модуль Б (прямой локальный поток);

Модуль Б вызывает А и А возвращает Б значение, которое используется в Б (непрямой локальный поток);

Модуль С вызывает модуль А, Б и передает результаты выполнения модуля А в Б.

Информационным поток из А в Б называется глобальным, если модуль А изменяет информацию в глобальной структуре данных С, а модуль Б использует информацию из С. На основе этих понятий введем информационную сложность процедуры:

= ( + )2, где — число локальных потоков внутри функции плюс число глобальных структур данных, из которых процедура считывает информацию.

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

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

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

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

Для расчёта количества входящих параметров достаточно проанализировать количество входящих параметров на стеке (количество инструкций push до вызова call). Для расчета количества операций записи/чтения в глобальные структуры данных необходимо на этапе интерпретации инструкций в Rx = y или [Rx] = Ry, рассчитывать адрес, по которому производится запись/чтение и если этот адрес лежит в диапазоне адресов для хранения глобальных переменных, увеличивать счётчик операций (подробно будет описано в главе 3).

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

В. Метрика Карда и Гласса [87] Д. Кард и Р. Гласс в своей работе определили три метрики для измерения сложности программного кода: структурная сложность, сложность по данным и системную сложность. При этом авторы используют смежные с [89] определения числа локальных потоков и.

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

–  –  –

( ) — число определяющих вхождений переменной vj из множества R(i), а |V(i)| — мощность множества V(i).

Для расчета в бинарном коде будем рассматривать в качестве луча одну вершину в графе. Тогда для расчета R(i) необходимо рассчитать количество локальных переменных, которые впервые определяются в вершине и количество локальных переменных, которые уже были ранее определены.

Д. Метрика Чепина [91] Суть метрики состоит в оценке информационной прочности отдельно взятого программного модуля с помощью анализа характера использования переменных из списка ввода-вывода. Все множество переменных, составляющих список ввода-вывода, разбивается на четыре функциональные группы:

= 1 + 2 + 3 + 4, где где a1, a2, a3, a4 – весовые коэффициенты.

Множество P это входные переменные для расчетов и для обеспечения вывода.

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

Множество М – модифицируемые или создаваемые внутри функции переменные.

Множество C – переменные, участвующие в управлении ГПУ (управляющие переменные). В бинарном коде переменные, которые участвуют в конструкциях типа cmp/test jxx.

Множество Т – не используемые в функции переменные (отсутствуют при анализе бинарного кода).

Автор в своей работе вводит следующие весовые коэффициенты (группа T исключена, так как равна нулю при анализе бинарного кода):

= + 2 + 3, где Наибольший вес, равный трем, имеет функциональная группа С, так как она влияет на поток управления программы. Весовые коэффициенты остальных групп распределяются следующим образом: a1=1; a2=2.

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

2.4.2.4. Гибридные метрики Гибридные метрики позволяют сочетать несколько элементарных метрик и представляют собой их взвешенную сумму.

А. Метрика Кокола [87]

Данная метрика записывается как:

+ 1 (1 ) + + ( ) =, 1 + 1 … где M – базовая метрика, Mi – другие значимые метрики, Ri – корректно подобранные коэффициенты, ( ) – функции. Функции и коэффициенты вычисляются с помощью регрессионного анализа или анализа задачи для конкретной программы.

Автор метрики выделяет три модели для мер: Мак-Кейба, Холстеда и LOC, причем в качестве базовой используется метрика Холстеда. Эти модели получили название «наилучшая», «случайная» и «линейная».

Б. Экспериментальная метрика Необходимо отметить, что уязвимости при работе с памятью могут возникать по причине использования небезопасных функций работы с памятью (strcpy, strcat lstrcat, memcpy и т.п). Данные функции не рекомендуются к использованию, так как они могут приводить к переполнениям в памяти по адресу назначения.

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

=. ( + 1), где n – количество уникальных функций, не рекомендованных к использованию, которые используются в анализируемой процедуре. В свою очередь рассчитывается как количество процедур определенного типа (strcpy, strcat и т.д) умноженное на коэффициент опасности функций данного типа. Данный коэффициент рассчитывается с помощью таблицы, предложенной Microsoft, в рамках обеспечения цикла безопасной разработки ПО, и может принимать значение от 0.5 до 1 [92].

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

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

2.5. Алгоритм расчета полноты и повышения эффективности тестирования Рассмотрим программу как связный граф, вершины которого представляются в виде базового блока, а дуги описывают передачу управления на тот или иной базовый блок [94]. Будем называть такой граф графом потока управления (ГПУ), а упорядоченное множество вершин в этом графе как = {0, 1, …, }, где – вершина в ГПУ и представляет собой общее количество вершин в ГПУ.

Ключевым критерием эффективности системы тестирования является оценка покрытия кода. Рассмотрим это понятие. Представим массив тестовых данных как = [0, 1, …, ], – размер массива, а – один экземпляр тестовых данных (файл, сетевой пакет и т.п.) для осуществления одной тестовой итерации.

Определим покрытие кода, получаемое в результате обработки программой одной цепочки тестовых данных ( [0; ]) как:

= {, +1, …, }, где

– упорядоченное подмножество выполненных вершин и ;

– выполненная вершина и index - номер вершины во множестве ;

max – индекс последней выполненной вершин за одну тестовую итерацию.

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

= [0, …, ]

Перейдём к описанию алгоритма расчета полноты и повышения эффективности тестирования. Для этого рассмотрим рисунок 2.14.:

Рисунок 2.14.

Алгоритм расчета полноты и повышения эффективности тестирования Опишем алгоритм на рисунке 2.14. На первом этапе производится считывание массива тестовых данных и массива покрытий Cover, а затем вступает в работу алгоритм анализа покрытий (АП). Рассмотрим его на рисунке 2.15.

–  –  –

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

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

| =0 |, где = k – количество достижимых вершин в ГПУ. Вершина является достижимой в том случае, если возможность её исполнения зависит от тестовых данных (алгоритм расчёта количества достижимых вершин в ГПУ будет рассмотрен в разделе 2.5.1.

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

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

2.5.1. Алгоритм расчёта количества достижимых вершин в программе Рассмотрим алгоритм расчета количества достижимых вершин в программе на рисунке 2.16.

Рисунок 2.16.

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

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

–  –  –

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

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

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

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

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

–  –  –

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

1. Выполняется загрузка анализируемого приложения в приостановленном режиме.

2. Точка входа подменяется на загрузочную функцию, которая производит загрузку виртуальной машины PIN и инструментирующего модуля (Pintool на рисунке 3.1) в адресное пространство тестируемого приложения. Управление передается на точку входа в инструментирующем модуле.

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

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

Инструментация может осуществляться с различным уровнем гранулярности:

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

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

–  –  –

Рисунок 3.3.

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

В ходе работы для сравнительной оценки эффективности методик ДБИ и отладки было разработано тестовое приложение, которое выполняет вычисление хеш – суммы по алгоритму SHA - 1 заданное число раз (от 5 до 1000). Было разработано два инструментирующих приложения, которые выполняют подсчет количества исполненных в ходе вычисления инструкций в тестовом приложении.

Сравнение эффективности методик ДБИ и отладки приведено на рисунке 3.4.

0,02

–  –  –

Рисунок 3.4 Сравнение производительности ДБИ и отладки Стоит отметить, что показанная на рисунке 3.

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

Проведем сравнительный анализ производительности ДБИ с запуском приложения в ОС без инструментации (рисунок 3.5). Такое сравнение позволит оценить накладные расходы, которые требуются для оценки покрытия кода в ходе инструментации. Отметим, что в ходе эксперимента использовалась утилита подсчета хеш-суммы по алгоритму SHA-1 из предыдущего примера. Однако в ходе данного эксперимента количество операций по вычислению хеш-суммы было увеличено на 5 порядков, так как ДБИ вносит существенные накладные расходы на запуск приложения, однако в дальнейшем накладные расходы уменьшаются [99].

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

3,75 3,5 3,25

–  –  –

Согласно рисунку 3.5. оценка покрытия с помощью ДБИ вносит в среднем 124.25% накладных расходов по сравнению с запуском приложения без инструментации. Однако ряд предложенных выше оптимизаций позволил снизить влияние подсистемы инструментации на тестируемое приложение в среднем на 46.9% (рисунок 3.6.) по сравнению с инструментацией на уровне каждой инструкции.

–  –  –

Рисунок 3.6.

Сравнение производительности динамической бинарной инструментации с дополнительной оптимизацией и без неё Полученное с помощью ДБИ покрытие кода позволяет выполнять анализ исполняемых в программе инструкций. Согласно алгоритму, предложенному в разделе 2.5., имея тестовые данные и получив с помощью вышеописанного подхода покрытие, необходимо выполнить анализ и приоритизацию тестовых данных на основе оценки сложности порождаемого ими покрытия (будет рассмотрено далее в разделе 3.2.). Затем необходимо в цикле выполнить программу на тестовых данных, рассчитать покрытие на каждой итерации, а затем рассчитать полноту тестирования.

3.2. Модуль расчета сложности покрытия В главе 2 была проведена адаптация метрик оценки сложности исходного текста программы для анализа исполняемого кода. Было выделено 25 метрик, применение которых возможно и оправдано в рамках решаемой задачи. Опишем схему работы модуля расчета сложности покрытия (рисунок 3.7).

–  –  –

Рисунок 3.7.

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

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

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

Определение 3.1. Под ссылкой следует понимать условный или безусловный переход по некоторому адресу в коде процедуры.

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

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

Таблица 3.1.

Инструкции, выполняющие присваивание Группа инструкций общего назначения Группа инструкций для вычислений с плавающей запятой mov*, cmov*, xchg, bswap, xadd, ad*, sub, sbb, fld, fst, fild, fisp, fistp, fbld, fbstp, fxch, fcmove, imul, mul, idiv, div, inc, "dec", neg, da*, aa*, and, fadd, fiadd, fsub, fisub, fmul, fimul, fdiv, fidiv, or, xor, not, sar, shr, sal, shl, shrd, shld, ror, rol, fprem, fabs, fchs, frndint, fscale, fsqrt, fxtract, rcr, rcl, lod, sto, lea fsin, "fcos", fsincos, fptan, fpatan, f2xm, fyl2x, fld, fstcw, fnstcw, fldcw, fstenv, fnstenv, fstsw, fnstsw, fxsave, fxrstop

Тогда общее количество присваиваний будет рассчитываться как:

число присваиваний = | |

–  –  –

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

–  –  –

нок 3.9). Объем выборки составил 21’757’940 инструкций из 104 свободно распространяемых программ скомпилированных различными компиляторами (список программ и подробная статистика находится в приложении 1 и 2 соответственно).

35%

–  –  –

25% 20% 15% 10% 5% 0%

–  –  –

В результате проведенного анализа было получено 342 уникальных инструкции, однако для наглядности на графике представлены лишь 24 наиболее популярные из них, вклад которых в общую статистику превышает или равен 1% (суммарный вклад 48 представленных на слайде инструкций составляет 99,9%) [98]. На основании полученной статистики можно сделать вывод, что для интерпретации нет необходимости описывать всё множество инструкций, существующих в спецификации процессора. Достаточно описать лишь те из них, которые чаще всего используются компиляторами.

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

–  –  –

Рисунок 3.10.

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

Рассмотрим последовательность действий, выполняемых модулем для декомпиляции одной инструкции:

Шаг 1. Декомпиляция начинается с подачи на вход дизассемблеру исполняемого кода инструкции.

Шаг 2. Выполняется дизассемблирование инструкции.

Шаг 3. Полученная на шаге 2 информация передается в интерпретатор, который на основе операций выполняемых инструкций выполняет анализ задействованных инструкций и их представление в высокоуровневом виде.

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

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

–  –  –

Рисунок 3.11.

Схема работы модуля анализа помеченных данных Согласно рисунку 3.11., на первом этапе выполняется анализ обработки помеченных данных в точках ввода этих данных в программу, которые задаются пользователем и считываются модулем. Затем программа исполняется, перехватывается каждая выполняемая инструкция (используется система ДБИ Pin), эти инструкции декомпилируются и выполняется анализ продвижения помеченных данных согласно алгоритму в разделе 2.3. Таким образом на выходе имеем адреса в памяти и регистры, в которых обрабатываются помеченные данные после выполнения каждой инструкции в программе, которые позволяют выполнять автоматизированный поиск уязвимостей в трассе программы.

–  –  –

Рисунок 3.12.

Пример функций с различным типом очистки стека в процедуре Как видно на рисунке 3.12. левая функция выполняет очистку стека от передаваемых аргументов при помощи инструкции ret 0x0C в коде функции func_1, а правая при помощи инструкции add esp,0x0C в вызывающей функции. Отметим, что описанная на рисунке 3.12. схема очистки стека встречается не всегда, так как некоторые оптимизирующие компиляторы могут не очищать стек после вызова процедуры, как в правом примере, если, к примеру, функция вызывается последней. Такой подход создает дополнительные трудности для анализа, а иногда и вовсе делает невозможным идентификацию количества аргументов, передаваемых в функцию.

Выделяют следующие типы передачи аргументов в процедуру (таблица 3.4).

Таблица 3.4.

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

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

Аргументы передаются в порядке их объявления на языке высокого Pascal (WinAPI) уровня, а стек очищается вызывающей процедурой.

Аргументы функции передаются через регистры.

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

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

1 int SumFunc (int a, int b) { 2 return a + b;

3 } 4 int main () { 5 int a = 0;

6 int b = 1;

7 a = SumFunc(a, b);

8 printf(a);

9 return 0;

10 } Листинг 3.1. Пример программы на языке С Теперь рассмотрим то, как будут передаваться аргументы функции в программу в листинге 3.1, в зависимости от принятого соглашения (рисунок 3.13).

–  –  –

Рисунок 3.13.

Передача аргументов в функцию в зависимости от соглашения Отметим, что пример дизассемблерного листинга на рисунке 3.13. был упрощен для более наглядного представления механизма передачи аргументов. В примере опущены локальные переменные, используемые в функции SumFunc и Main для работы с передаваемыми аргументами. Таким образом, согласно рисунку 3.13., количество передаваемых аргументов может быть подсчитано путём анализа количества возвращаемых байт инструкцией ret для вызовов _stdcall и Pascal. Для соглашения _cdecl необходимо проанализировать манипуляции с регистром esp после вызова функции. Однако отметим, что некоторые компиляторы могут не очищать стек после вызова процедуры с целью оптимизации. В свою очередь, для анализа количества передаваемых аргументов при типе соглашения _fastcall, необходимо учитывать то, какие регистры используются в процедуре без предварительного объявления (например, такие как ecx и edx в примере на рисунке 3.13).

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

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

через регистры eax:edx оператором return;

через аргументы стека (возвращения аргументов по ссылке);

через память в куче;

через глобальные переменные;

через флаги процессора.

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

а) Через регистры eax:edx оператором ret Данный способ представлен на рисунке 3.7., возвращаемое значение помещается в регистр eax, которое затем используется в вызываемой процедуре. Для значений, размер которых превышает 4 байта, старшие байты помещаются в регистр edx. Отметим также, что компилятор Visual Basic для некоторых системных функций возвращает значение через регистр ecx.

–  –  –

Рисунок 3.14.

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

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

г) Идентификация аргументов возвращаемых через память в куче.

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

Отличие же заключается лишь в том, что память выделяется не группой инструкций mov ebp, esp sub esp,0xX, а функцией malloc или её WinAPI аналогами: HeapAlloc, VirtualAlloc.

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

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

Рассмотрим схему работы модуля детектирования уязвимостей на рисунке 3.15.

–  –  –

Рисунок 3.15.

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

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

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

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

Глава 4. Экспериментальная оценка эффективности комплекса

–  –  –

Рисунок 4.1.

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

Отметим, что система анализа разрабатывалась с учетом возможности её использования не только в Windows, но и в других ОС. Для решения этой задачи было принято решение максимально (где это возможно) использовать кроссплатформенный язык программирования Python, а где невозможно C/C++. Такой выбор был продиктован тем, что, наряду со своими широкими возможностями, язык Python позволяет легко интегрироваться в проекты на C/C++. В результате на языке С были реализованы только модули детектирования уязвимостей, инструментации и частичной декомпиляции.

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

Рисунок 4.2. Визуализация ГПУ с уязвимостми

На рисунке 4.2. представлена визуализация ГПУ для одной тестовой итерации в программе, в ходе которой были найдены уязвимости. Каждый узел в вышеописанном графе представляет собой вызов процедуры. Темными узлами обозначены вызовы процедур, в которых были найдены уязвимости. Отметим, что для повышения производительности генерацию таких графов имеет смысл производить только для тех тестовых итераций, в рамках которых были найдены уязвимости.

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

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

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

Для решения задачи поиска наиболее эффективной метрики была использована открытая база уязвимых приложений exploit-db [101]. Из этой базы случайным образом было отобрано 104 уязвимых приложений различного уровня сложности, решаемых задач и метода взаимодействия с пользователем (список отобранных программ приведен в приложении 1). Можно разделить приложения по следующим классам в зависимости от решаемых задач: видео и аудио проигрыватели, FTP, HTTP, SMTP, IMAP и медиа – серверы, сетевые утилиты для удаленного администрирования, научные приложения, компьютерные игры, вспомогательные утилиты (загрузчики, торрент клиенты, средства разработки ПО и т.п.), вспомогательные библиотеки (конвертеры, обработчики данных и т.п), утилиты для чтения различных форматов файлов (PDF, DJVU, JPEG и т.д.), архиваторы. Для каждой программы был найден эксплойт, позволяющий идентифицировать уязвимость в приложении. Каждое приложение было проанализировано вручную, был установлен адрес инструкции и адрес процедуры, в которой возникает уязвимость (см.

приложение 1).

–  –  –

– ранг уязвимой функции, а – общее количество функций в программе.

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

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

100 94,01 Процентный интервал 87,62 87,5 87,45 87,37 87,31 87,26 87,13 86,86 86,21 85,98 90 85,45 83,57 83,41 83,26 82,78 81,7 81,69 81,53 81,05 80,39 79,25 51,01 50,57 50 43,02

–  –  –

Рисунок 4.3.

Средний показатель эффективности метрик от лучшей к худшей На рисунке 4.3, используются следующие обозначения для метрик: LOC – метрика количества строк кода, BBLS – метрика количества базовых блоков, Condit – метрика количества проверок условий, Calls – метрика количества вызовов процедур, Assign – метрика количества присваиваний переменным значений, СС – цикломатическая сложность, СС_mod – модифицированная цикломатическая сложность, Jilb – метрика Джилба, ABC – метрика ABC, R – метрика отношения количества дуг графа к числу базовых блоков.

H.B, H.D, H.V, H.N*, H.V – метрики Холстеда B, D, V, N и V, Pi – метрика Пивоварского, Harr – метрика Харрисона, Cocol – метрика Кокола, Bound – метрика граничных значений, Span – метрики спена, Global – метрика количества обращений к глобальным переменным, Oviedo

– метрика Овиедо, Chepin – метрика Чепина, C&S – метрика Карда и Гласса, H&С

– метрика Генри и Кафура, Exp – экспериментальная метрика.

Таким образом, согласно рисунку 4.3, наименьшую эффективность показывают метрики Джилба и R, а также метрика обращения к глобальным данным, а наибольшую экспериментальная метрика. Кроме того, наряду со средним показателем, экспериментальная метрика имеет также и наименьшее среднеквадратичное отклонение (11,80%) и стандартную ошибку (2.54% в 95% доверительном интервале).

4.3. Экспериментальная оценка эффективности подсистем и программного комплекса Для сравнительной оценки эффективности подсистемы поиска уязвимостей в трассе с аналогами, случайным образом было отобрано 24 уязвимых приложений из международной базы уязвимостей exploit-db на каждый тип уязвимости, порождающий каждый класс угроз к защищаемой информации (таблица 4.1). По 12 приложений для ОС Windows и Linux (подробный список приложений приведен в приложении 4).

Таблица 4.1.

Описание типов отобранных уязвимостей на каждый класс угроз Класс угроз Тип уязвимости Описание уязвимости Нарушение целостно- Переполнение кучи Запись за границами кучи сти защищаемой ин- Переполнение стека Запись за границами стека формации Ошибка форматирования Запись за границами стека строк Нарушение доступно- Использование неинициали- Обращение к неинициализированной сти защищаемой ин- зированной памяти памяти

–  –  –

Дадим пояснения к таблице и эксперименту. В рамках эксперимента каждое приложение запускалось с использованием подсистемы детектирования уязвимостей, а затем в приложение подавались тестовые данные, которые приводили к срабатыванию уязвимости, после чего проводился анализ результата работы подсистемы [102]. В результате системы Memсheck и DrMemory смогли обнаружить большее количество уязвимостей, так как они позволяют детектировать уязвимости, связанные с выходом за границы выделенной памяти, которые не приводят к вызову исключительных ситуаций. Отметим, что некоторые системы не поддерживают ОС Windows, поэтому возможность их тестирования в этой ОС отсутствует.

Таким образом, разработанная подсистема выполнила обнаружения 23 из 24 уязвимостей в приложениях для ОС Windows и Linux, что на 4 типа уязвимостей больше, чем обнаружили системы Memcheck и DrMemory в Linux и на 5 типов уязвимостей больше системы DrMemory в Windows.

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

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

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

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

В качестве приложений для анализа было выбрано ПО из предыдущего эксперимента. К ним также было добавлено 8 случайно отобранных продуктов (4 для Windows и 4 для Linux) с двумя и более уязвимостями для анализа возможности системы снижать временные затраты на детектирование нескольких уязвимостей в одном ПО. Результаты приведены на рисунке 4.4 (детальные результаты см. раздел 5.1. в приложении 5).

–  –  –

373,3 343,4 342,3 400 338,6 314,5 298,3

–  –  –

227,6 224,3 223,6

–  –  –

190,8 174,2 200 164,3 162,0 161,9 137,2

–  –  –

Рисунок 4.5.

Результаты оценки снижения времени необходимого на обнаружение уязвимостей после интеграции разработанной подсистемы

–  –  –

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



Pages:     | 1 | 2 || 4 |

Похожие работы:

«Музалевская Екатерина Николаевна ЭКСПЕРИМЕНТАЛЬНОЕ ОБОСНОВАНИЕ ПРИМЕНЕНИЯ МАСЛА СЕМЯН АМАРАНТА ДЛЯ КОРРЕКЦИИ ОСЛОЖНЕНИЙ, ВЫЗЫВАЕМЫХ ИЗОНИАЗИДОМ 14.03.06 Фармакология, клиническая фармакология ДИССЕРТАЦИЯ на соискание ученой степени кандидата фармацевтических наук Научный руководитель: д.м.н., профессор Николаевский Владимир...»

«Марченко Василий Сергеевич Методика оценки чрезвычайного локального загрязнения оксидами азота приземной воздушной среды вблизи автодорог 05.26.02 – безопасность в чрезвычайных ситуациях (транспорт) Диссертация на соискание учёной степени кандидата технических наук Научный руководитель: к.х.н., доцент Ложкина Ольга Владимировна Санкт-Петербург Оглавление Введение 1 Аналитический обзор...»

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







 
2016 www.konf.x-pdf.ru - «Бесплатная электронная библиотека - Авторефераты, диссертации, конференции»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.