Что такое Фаззинг и как искать уязвимости в программах

Фаззинг инструмент icon

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

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

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

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

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

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

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

Разумеется, существуют и более продвинутые техники. Например, фаззинг с использованием трассировки и построением уравнений для 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.

Фаззер 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

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. Эта команда запустит процесс поиска уязвимостей для тестовой программы со статической инструментацией.

Еще по теме: Как в Google автоматизируют поиск багов

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

Для того чтобы понять на деле, как именно происходит поиск ошибок при помощи 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

Дима (Kozhuh)

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

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