Главная » Песочница 2 » Автоматизация поиска уязвимостей в программах
Автоматизация поиска уязвимостей в программах

Автоматизация поиска уязвимостей в программах

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

Еще по теме: Лучшие программы для реверс-инжиниринга

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

Техники фаззинга

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

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

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

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

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

Одним из первых прототипов фаззеров считается программа The Monkey, созданная в далеком 1983 году. В названии очевидна отсылка к теореме о бесконечных обезьянах, которые пытаются напечатать «Войну и мир». Несмотря на свою практическую бесполезность, теорема популярна в массовой культуре (например, упоминается в романе «Автостопом по галактике» и сериале «Симпсоны») и даже получила собственный RFC 2795.

Типы фаззеров

С техниками фаззинга более-менее разобрались, теперь перейдем к типам фаззеров.

Форматы файлов

Будем считать входными данными пользователя любой файл любого формата, который наше тестируемое приложение возьмется обработать. Это значит, что мы можем подсунуть файл «неправильного» формата и посмотреть, как справится с ним подопытная программа. Первое, что приходит в голову, — антивирус. Антивирусный сканер должен определять формат файла, как-то с ним взаимодействовать: пытаться распаковать, включить эвристический анализ и так далее.

Чем обернется простая проверка, если антивирусный сканер решит, что перед ним файл PE, упакованный UPX, а при распаковке выяснится, что это вовсе не UPX, а что-то, что лишь притворяется им? Естественно, алгоритм распаковки будет другой, но поведение сканера при этом предугадать сложно. Может быть, он обрушится. Может быть, просто повесит на файл флаг «поврежден» и пропустит. И это далеко не полный перечень возможных исходов. Фаззеры форматов файлов помогут протестировать подобные вещи.

Аргументы командной строки и переменные окружения

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

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

Запросы IOCTL

Достаточно полезная штука, когда нужно посмотреть, как реагируют на запросы IOCTL различные драйверы режима ядра. Помимо устройств и периферии, драйверами зачастую пользуются некоторые программы для взаимодействия с системой. Конечно, структура IRP-запроса почти всегда неизвестна, но перехваченные пакеты можно использовать в качестве основы для корпуса.

Сетевые протоколы

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

Браузерные движки

Да, даже для поиска дыр в браузерах есть специальные фаззеры. На сегодняшний день современные браузеры очень сложны и содержат множество движков: они обрабатывают различные версии документов, протоколов, CSS, COM, DOM и многое другое. Так что участники различных bug bounty ищут дыры не только голыми руками.

Оперативная память

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

Проблема покрытия

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

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

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

Кроме всего этого, существует простая проблема совместимости с различными версиями операционных систем, как Windows, так и *nix. Чем сложнее фаззер и чем пристальней он смотрит на поток выполнения приложения, тем крепче он привязывается к особенностям ОС.

Как видишь, фаззеров очень много, и под каждую задачу можно найти специально разработанный инструмент поиска уязвимостей. Многие фаззеры написаны под *nix-подобные ОС, но есть и такие, которые работают с Windows. Давай поближе рассмотрим парочку разнотипных фаззеров: WinAFL и MiniFuzz.

WinAFL

WinAFL — это форк популярного фаззера AFL (American fuzzy lop), портированный под Windows корпорацией Google. Он использует инструментацию тестовых файлов, как статическую, когда есть исходные коды приложения, так и динамическую, когда инструментирование происходит «на лету». В этом помогает библиотека для анализа бинарников DynamoRIO.

Фаззер WinAFL
Фаззер WinAFL

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

  • -i [каталог] — каталог входных тестовых кейсов. Примечательно, что вместе с фаззером «из коробки» идет несколько простых тестовых кейсов для различных типов файлов.
  • -o [каталог] — каталог выходных данных, куда будут помещаться результаты работы фаззера.
  • -D [каталог] — опция, которая говорит фаззеру использовать динамическую инструментацию на базе DynamoRIO. Для этого мы должны дополнительно указать каталог, где установлен этот инструмент.
  • -Y — опция для статической инструментации.

Если с динамической инструментацией все понятно, то со статической могут возникнуть проблемы: например, программа instrument.exe, которая призвана инструментировать файлы в Windows, пока еще не понимает последние версии Visual Studio SDK и не работает с программами, собранными в Visual Studio 2019.

Когда все готово и файл инструментирован, достаточно выполнить команду afl-fuzz.exe -Y -i input -o output — test.exe. Эта команда запустит процесс поиска уязвимостей для тестовой программы со статической инструментацией.

MiniFuzz

Теперь давай рассмотрим еще один фаззер под названием MiniFuzz. Он разработан в компании Microsoft и достаточно дружелюбен по отношению к пользователю (да, тут даже есть графический интерфейс!). Также доступна интеграция с Visual Studio.

Фаззер MiniFuzz
Фаззер MiniFuzz

Разработчики рекомендуют делать не менее 100 000 файлов на каждый файловый формат. При этом каждый поданный на вход файл — это отдельная итерация фаззинга. Следовательно, требуется набор эталонных файлов. Например, если ты решил протестировать поведение приложения в ходе обработки архивов *.zip, в папку шаблонов тебе необходимо будет сложить около ста таких файлов-образцов. Можно положить и больше, фаззер будет только рад! А вот если положить меньше, то эффективность процесса заметно упадет.

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

Все настройки фаззера хранятся в файле minifuzz.cfg в формате XML. Немного пробежимся по самым интересным опциям (очевидные я опустил для краткости).

  • Command line args — в этом поле можно добавить недостающие параметры командной строки, если эти данные нужны во время фаззинга.
  • Allow process to run for — определяет время работы экземпляра тестируемого приложения. Не следует устанавливать маленькое значение, ведь тогда фаззер может не успеть отработать.
  • Shutdown method — метод завершения запущенного экземпляра тестового приложения. Поддерживаются методы ExitProcess (завершает процесс корректно), WM_CLOSE (корректное завершение для оконных приложений) и TerminateProcess (завершает процесс аварийно).
  • Aggressiveness — параметр определяет, насколько сильно будут искажаться образцы файлов перед тем, как попадут в приложение. Если ты безуспешно ждешь результата вот уже долгое время, стоит подумать над увеличением этого значения.

Практика

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

Как именно ее вызывать — тут уже на усмотрение программиста. Я буду передавать в качестве аргумента командной строки «волшебный» параметр, который вызывает функцию по условию if (argc == 2 && !strcmp(argv[1], «key»)). Кроме того, для ускорения фаззинга можно «обернуть» тестируемую функцию в цикл:

Управляющая функция цикла находится в файле winafl-master\afl-staticinstr.h, который необходимо будет подключить к проекту. Кроме того, это добавит в проект диагностические сообщения.

Подготовка файла к дальнейшей инструментации
Подготовка файла к дальнейшей инструментации

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

Инструментация
Инструментация

Кроме того, в свойствах компоновщика необходимо добавить два параметра: /PROFILE — включит поддержку профилирования и /SAFESEH — безопасная обработка исключений. После этого все готово и можно запускать фаззер:

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

Работа фаззера WinAFL
Работа фаззера WinAFL

Тут стоит напомнить, что фаззинг в реальных условиях может длиться неделями. К счастью, у нас все пойдет быстрее. После того как мы обнаружим падение приложения, в каталоге out можно будет найти файлы с названиями вида id:000003,src:000001,op:flip1,pos:1. Внутри содержится диагностическая информация с пояснениями, примерно такими:

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

Заключение

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

Еще по теме: Отладка программ с помощью WinDbg

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *