Автоматическое обнаружение уязвимостей в исходном коде программ

Определение оценки 3 * () является основой для дальнейшего формирования оценки информационного образа:

Выражения (13) - (15) представляют собой математическую модель оценки информационного образа исследуемого объекта. Реализация данной модели и создание на ее основе программных и программно-аппаратных комплексов позволит выйти на новый качественный уровень решения задач информационной безопасности.

1. Величкин А.И. Передача аналоговых сообщений по цифровым каналам. - М.: Радио и связь, 1983. - 240 с.

2. Величкин А.И., Котенко В.В. Сглаживание непрерывного сообщения на выходе системы связи с кодоимпульсной модуляцией // Радиотехника, 1980, Т. 35. № 3. С..38-41.

О.Р. Маликов

Россия, г. Москва, МГУ им. М.В.Ломоносова

АВТОМАТИЧЕСКОЕ ОБНАРУЖЕНИЕ УЯЗВИМОСТЕЙ В ИСХОДНОМ КОДЕ ПРОГРАММ

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

По данным сайта SecurityFocus.com, отслеживающего найденные уязвимости защиты в программах, за период в полтора года с января 2000 г. было найдено более трех тысяч уязвимостей. Наиболее частыми ошибками являются ошибки переполнения буфера (в разные периоды от 23 % до 50 %) и форматной строки.

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

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

0

БИБЛИОГРАФИЧЕСКИМ СПИСОК

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

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

Переполнение буфера (buffer overflow) возникает как следствие отсутствия контроля или недостаточного контроля над выходом за пределы массива в памяти. Языки Си/Си++, чаще всего используемые для разработки программного обеспечения системного уровня, не реализуют автоматического контроля выхода за пределы массива во время выполнения программы. По месту расположения буфера в памяти процесса различают переполнения буфера в стеке (stack buffer overflow), куче (heap buffer overflow) и области статических данных (BSS buffer overflow). Все три вида переполнения буфера могут быть с успехом использованы для выполнения произвольного кода уязвимым процессом.

Ошибки неконтролируемой форматной строки (format string) возникают из-за недостаточного контроля параметров при использовании функций форматного ввода-вывода семейства printf/scanf стандартной библиотеки языка Си. Эти функции принимают в качестве одного из параметров символьную строку, задающую формат ввода или вывода последующих аргументов функции. Если пользователь программы может влиять на содержимое форматной строкой (например, форматная строка вводится в программу пользователем), то возможно сформировать её таким образом, что по некоторым ячейкам памяти (адресами которых он может управлять) окажутся записанными указанные пользователем значения, что открывает возможности, например, для переписывания адреса возврата функции и исполнения кода, заданного пользователем.

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

Предлагаемая модель анализируемой программы основана на понятии абстрактной ячейки памяти (abstract memory location, AML). Абстрактная ячейка памяти (АЯП) ставится в соответствие каждому объекту в памяти, который может быть создан во время работы программы. С ней также связан ряд атрибутов, отражающих свойства создаваемых объектов. В частности, такими атрибутами являются размер объекта в памяти, длина строки (в случае, если объект может содержать строку) и другие. Уязвимость защиты понимается как нарушение определенных ограничений на атрибуты АЯП. Например, переполнение буфера возможно, когда значение атрибута “длина” в некоторой точке программы может превысить значение атрибута —размер” для АЯП, соответствующей этому буферу.

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

Статическими атрибутами АЯП являются следующие:

- Size - размер, занимаемый АЯП в байтах. Размер вычисляется при создании АЯП по известному размеру стандартных типов для массивов - количеству элементов, для записей - суммарному размеру полей, для объединений - максимальному размеру поля, для динамически выделяемых объектов - параметрам функций выделения памяти. В последнем случае размер АЯП может измениться в ходе анализа, например в результате более точной оценки параметров функций семейства malloc() либо при вызове функции realloc(). Тем не менее, размер АЯП остается одним и тем же для всех анализируемых точек программы.

- Overlap - множество пар (AML, Offset), АЯП из которых могут перекрываться с этой АЯП с некоторым смещением Offset (диапазонного типа). Этот атрибут, как было отмечено выше, используется для описания того, как размещаются в памяти подобъекты объекта агрегатного типа, в частности поля записей и объединений. При создании АЯП для переменной, имеющей тип записи, во множество overlap для этой АЯП заносятся пары, для которых абстрактная ячейка памяти AML соответствует некоторому полю этой записи, а смещение Offset - смещению этого поля в записи. Если же АЯП создается для переменной типа объединения, то во всех парах из множества overlap смещения будут равны нулю. Как и размер АЯП, этот атрибут также может уточняться при последующих итерациях анализа. Это происходит при динамическом создании объектов агрегатных типов.

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

Динамические атрибуты АЯП включают в себя следующие:

- Aset - множество тех АЯП, на которые может указывать эта АЯП с некоторым смещением (так называемое points-to множество для данной АЯП). Это основной атрибут, используемый при анализе указательных АЯП.

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

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

- Input - логический атрибут, который показывает, что в данной точке программы значение данной АЯП может зависеть от ввода пользователя (либо от

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

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

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

Межпроцедурный анализ должен заниматься решением двух задач:

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

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

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

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

- анализ начинается со всех функций main;

- анализ начинается со всех «корневых», т. е. таких, которые ниоткуда не вызываются, вершин графа вызовов;

- со всех специально указанных вершин графа вызовов.

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

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

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

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

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

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

Экспериментальные результаты

Подготовка программы к анализу состоит из трех частей. Сначала программа на языке Си транслируется во внутреннее представление среднего уровня. В настоящее время транслятором поддерживается стандарт ISO C90, а также отдельные возможности стандарта ISO C99 (в частности, массивы переменного размера) и некоторые расширения GNU. На уровне стандартной библиотеки полностью поддерживается стандарт ISO C90 и некоторые заголовочные файлы POSIX.

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

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

В табл. 1 показаны результаты анализа 11 пакетов свободно распространяемого ПО. Для каждого пакета приведены его название и номер версии, количество строк исходного кода пакета без учета пустых строк (колонка LOC), количество функций в пакете (колонка NOF), количество истинных, ложных предупреждений и общее количество предупреждений (колонки TP, FP и Tot соответственно). Также приведена доля истинных предупреждений в процентах (колонка TP %) и время работы анализа и объем памяти, занимаемый системой (колонки Time и Mem).

Таблица 1.

Результаты поиска уязвимостей

LOC NOF TP TP% FP Total Time, sec Mem, mb

bftpd-1.0.24 3126 114 20 37 34 54 30,9 76,2

lhttpd-0.1 934 17 5 23 17 22 3,6 8

muh-2.05d 4942 95 12 26 35 47 39,2 101,5

pgp4pine-1.76 4003 68 16 36 29 45 23,7 57,6

polymorph-0.4.0 605 15 6 75 2 8 2,1 2,8

popclient-2.21 1701 36 7 21 27 34 3,8 7,9

sharutils-4.2.1 6015 70 11 22 38 49 48,3 77,7

ssmtp-2.60 2224 33 2 13 13 15 8,4 1,8

surfboard-1.1.8 718 18 16 70 7 23 3,8 8,2

telnetd-1.0 5149 68 3 10 26 29 32,4 70,8

troll-ftpd-1.26 2354 50 2 6 33 35 48,7 51,8

Total 31771 584 100 28 261 361 244,9 464,3

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1. Bush W.R., Pincus J.D., and Sielaff D.J. A static analyzer for finding dynamic programming errors. In Proceedings of Software Practice and Experience, Р. 775-802, 2000.

2. Cousot P. and Cousot R. Static determination of dynamic properties of programs. In Proceedings of the Second International Symposium on Programming, Р.106 - 130. Dunod, Paris, France, 1976.

3. Dor N., Rodeh M., and Sagiv M. CSSV: Towards a Realistic Tool for Statically Detecting All Buffer Overflows in C. In Proceedings of the ACM SIGPLAN 2003 conference on Programming language design and implementation, pages 155—167, San Diego, California, 2003.

4. Emami M., Ghiya R., and Hendren L. Context-Sensitive Interprocedural Points-to Analysis in the Presence of Function Pointers. In Proceedings of the SIGPLAN '94 Conference on Program Language Design and Implementation, Orlando, US, 1994.

5. Haugh E. and Bishop M. Testing C Programs for Buffer Overflow Vulnerabilities. In Proceedings of the Network and Distributed System Security Symposium, San Diego, CA, February 2003.

6. Wagner D., Foster J. S., Brewer E. A., and Aiken A. A First Step Towards Automated Detection of Buffer Overrun Vulnerabilities. In Proceedings of 7th Network and Distributed System Security Symposium, Feb. 2000.

П.В. Сундеев Россия, г. Краснодар, КГТУ

МОДУЛЬНО-КЛАСТЕРНЫЙ АНАЛИЗ: АСПЕКТЫ ИНФОРМАЦИОННОЙ БЕЗОПАСНОСТИ

Информационная безопасность (ИБ) является одним из основных аспектов функциональной стабильности критичных информационных систем (ФС КИС). Для исследования свойств безопасности информационной архитектуры КИС необходим комплекс методов и средств моделирования и анализа. В работе предлагается подход к автоматизации анализа свойств функциональной стабильности КИС в аспекте ИБ на основе модульно-кластерного анализа.

Актуальность проблемы

В результате повсеместной информатизации критичные функции управления передаются под контроль автоматизированных систем (АС). Этот процесс порождает проблему обеспечения ФС КИС, обеспечивающих функционирование систем управления опасными производствами и объектами атомной энергетики; систем управления космическими полетами, воздушным или железнодорожным движением; систем управления военного назначения; систем управления органов государственной власти и других. Большинство систем этого класса характеризуются критичностью решаемых функциональных задач, территориальной и информационной распределенностью, концентрацией информации ограниченного доступа, использованием биологических, бумажных и электронных технологий обработки информации, семантической доступностью для информационного воздействия, временными ограничениями цикла управления и другими свойствами, которые определяют сложность технологических процессов обработки информации и потенциальную опасность нарушения их ФС.

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