Skip to content

Fuzzing FreeImage project with Sydr and AFLplusplus (rus)

Daniel Kuts edited this page Jan 29, 2024 · 1 revision

Введение

"Mistakes are also important to me. I don’t cross them out of my life, or memory. And I never blame others for them."

― Geralt of Rivia.

В этой статье я расскажу, как использовать инструмент гибридного фаззинга sydr-fuzz, который совмещает в себе преимущества Sydr (инструмента динамического символьного выполнения) и AFLplusplus. Sydr-fuzz также поддерживает другой движок фаззинга: libFuzzer, вот гайд, посвященный фаззингу с помощью sydr-fuzz с движком libFuzzer'а. В этом гайде мы сосредоточимся на подготовке фаззинг-целей для AFLplusplus и Sydr, гибридном фаззинге, сборе покрытия по коду. Также мы воспользуемся нашим инструментом сортировки аварийных завершений сasr, и применим Sydr для проверки предикатов безопасности с целью поиска интересных ошибок технологиями символьного выполнения.

Как и для предыдущего гайда, найти один проект с открытым исходным кодом для показа всех заявленных возможностей - задача не из лёгких. Мне удалось найти подходящий проект FreeImage. Давайте начнём наше путешествие в мир фаззинга вместе с Sydr и AFLplusplus!

Подготовка фаззинг цели

"If I have to choose between one evil or another, I'd rather not choose at all"

― Geralt of Rivia.

В гайде по фаззингу проекта xlnt я раскрыл некоторые аспекты создания фаззинг целей и как добавить новый проект в oss-sydr-fuzz. Здесь я хочу напомнить основные моменты и объяснить разницу между фаззинг целями для libFuzzer и AFLplusplus.

Перед тем, как мы начнём фаззинг, нам необходимо выполнить следующие шаги:

  • Создание фаззинг цели для AFLplusplus в persistent mode. Если Вы не знакомы с AFLplusplus, то можете посмотреть на этот гайд.
  • Создание фаззинг цели для Sydr и сбора покрытия по коду. Для этих целей вам нужно просто добавить функцию main, которая читает входные данные из файла и отправляет их в функцию LLVMFuzzerTestOneInput.
  • Собрать три фаззинг цели для AFLplusplus, Sydr и сбора покрытия по коду.
  • Подготовка корпуса.

FreeImage уже добавлен в oss-sydr-fuzz. Поэтому можно просто склонировать этот репозиторий, собрать докер контейнер, следуя инструкциям. Мы используем afl-clang-fast/afl-clang-fast++ компилятор для сборки фаззинг целей AFLplusplus (-fsanitize=fuzzer для persistent mode). В этом все отличия от подготовки для фаззинга с помощью libFuzzer. Хорошо, давайте соберём docker контейнер для FreeImage и начнём фаззинг!

Фаззинг

"Sometimes there’s monsters, sometimes there’s money. Rarely both. That’s the life."

― Geralt of Rivia.

Перед началом фаззинга с помощью sydr-fuzz мы должны написать простой конфигурационный файл в TOML формате. Ниже представлен такой файл load_from_memory_tiff-afl++.toml для FreeImage:

exit-on-time = 3600

[sydr]
args = "-l debug"
target = "/load_from_memory_tiff_sydr @@"
jobs = 4

[aflplusplus]
target = "/load_from_memory_tiff_afl"
args = "-t 60000 -i /corpus_tiff"
jobs = 16

[cov]
target = "/load_from_memory_tiff_cov @@"

Давайте окинем взглядом этот файл:

exit-on-time - опциональный параметр, принимающий время в секундах. Если в течение этого времени (1 час в нашем случае) покрытие не увеличилось, то фаззинг завершается автоматически.

[sydr] таблица может содержать следующие параметры:

args - строка аргументов для запуска Sydr. Часть опций выставляется автоматически самим sydr-fuzz.

target - строка запуска фаззинг-цели. Вместо входного файла используем @@.

jobs - число экземпляров Sydr для одновременного запуска. Значение по умолчанию 1.

[aflplusplus] таблица содержит аргументы для AFLplusplus.

[cov] таблица содержит строку запуска для исполняемого файла, собранного с инструментацией llvm-cov.

Таким образом, мы запустим фаззинг, где AFLplusplus займёт 16 ядер, а Sydr 4. Процесс фаззинга закончится, если покрытие кода не будет прирастать в течении 1 часа. Самая важная вещь, что для запуска параллельного фаззинга с движком AFLplusplus sydr-fuzz использует умный алгоритм. Этот алгоритм запускает узлы фаззинга с различными параметрами, которые выбираются на основе рекомендаций от разработчиков AFLplusplus. Так, пришло время собрать docker:

$ sudo docker build -t oss-sydr-fuzz-freeimage .

И запустить контейнер:

$ sudo docker run -v /etc/localtime:/etc/localtime:ro --privileged --network host --rm -it -v $PWD:/fuzz oss-sydr-fuzz-freeimage /bin/bash

Меняем директорию на /fuzz:

# cd /fuzz

Запускаем фаззинг:

# sydr-fuzz -c load_from_memory_tiff-afl++.toml run

fuzz-start

Наконец-то, мы запустили sydr-fuzz. Сперва sydr-fuzz копирует изначальный корпус в директорию корпуса проекта и минимизирует его с помощью afl-cmin. После шага минимизации начинается фаззинг. Давайте подождём, пока фаззинг завершится.

fuzz-end

Исходя из логов, мы нашли 394 аварийных завершения. Фаззинг завершился по exit-on-time (Testing aborted programmatically). Sydr запустился 613 раз. Перед тем, как мы начнём собирать покрытие по коду и проверять предикаты безопасности, давайте минимизируем результирующий корпус:

$ sydr-fuzz  -c load_from_memory_tiff-afl++.toml cmin

cmin-start

Отлично, теперь у нас есть минимизированный корпус (2526 файлов).

cmin-end

Теперь мы можем собрать покрытие и проверить предикаты безопасности.

Сбор покрытия

"Jaskier: Actually, I've always wanted to know, do witchers ever retire?

Geralt: Yeah, when they slow and get killed."

― Geralt of Rivia.

Для сбора покрытия в формате lcov мы можем воспользоваться следующей командой:

# sydr-fuzz -c load_from_memory_tiff-afl++.toml cov-export -- -format=lcov > load.lcov

cov-export

После получения .lcov файла давайте сгенерируем html отчёт:

# genhtml -o load-html load.lcov

source

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

Предикаты безопасности

"Toss a coin to your witcher

O' valley of plenty

O' valley of plenty

O'

Toss a coin to your witcher

O' valley of plenty"

— Jaskier.

Идея, которая стоит за предикатами безопасности, была кратко описана в гайде по фаззингу проекта xlnt. Сейчас давайте проверим предикаты безопасности. Так, результирующий корпус всё ещё достаточно большой, поэтому предлагаю для экономии времени проверить предикаты на подмножестве этого корпуса (скажем 500 файлов), используя 32 задачи для Sydr.

$ sydr-fuzz -c load_from_memory_tiff-afl++.toml security --jobs 32 --runs 500

security-start

Немного подождём...

security-end

После проверки предикатов безопасности мы имеем 398 аварийных завершений (4 новых) и 831 подтверждённое срабатывание на санитайзерах: intoverflow/bounds/zerodiv/null: 811/20/0/0. Подробный анализ одного подтверждённого срабатывания целочисленного переполнения, которое мы нашли в результате этой проверки, можно посмотреть в этом issue.

Так, у нас всё ещё 398 аварийных завершений. Давайте запустим Casr для сортировки.

Сортировка аварийных завершений

"Beware of an old man in a profession where men usually die young."

― Geralt of Rivia.

Давайте запустим сортировку аварийных завершений с помощью этой команды:

$ sydr-fuzz -c load_from_memory_tiff-afl++.toml casr

casr

Сперва создаются отчёты об аварийных завершениях (.casrep) для каждого крэша. Для этого мы используем фаззинг-цель от AFLplusplus с санитайзерами. Основной компонент отчёта для сортировки - это стек вызовов. Стек вызовов используется при сравнении аварийных завершений. Следующим шагом идёт дедупликация отчётов с одинаковыми стеками вызовов. После запускается алгоритм кластеризации отчётов. Затем в полученные кластеры к имеющимся отчётам копируются входные данные, а в отчётах обновляется некоторая информация. На последнем этапе для каждого кластеризованного отчёта запускается caesar, который используется для получения отчёта на исполняемом файле без санитайзеров. Это может быть полезно в рамках анализа, посмотреть, как происходит аварийное завершение (если оно происходит), в отсутствии cанитайзеров.

NOTE: casr доступен на github. caesar заменён на casr-gdb.

casr-tree

Дедупликация уменьшила число аварийных завершений с 398 до 9. Затем последующая кластеризация разложила эти аварийные завершения по 6 кластерам. Давайте посмотрим на какой-нибудь отчёт. Для этого используем casr-cli.

$ casr-cli load_from_memory_tiff-afl++-out/casr/cl2/crash-17492804a7e9699fbbab8e2b92a691b56dde98e6.casrep

casrep

Случилась ошибка сегментации при вызове функции memcpy. Из caesar отчёта я понял, что значение src_tag равно NULL, что является причиной аварийного завершения. Выглядит как реальный баг, отличное попадание!

Заключение

В этой статье мы узнали, как использовать sydr-fuzz с AFLplusplus и применять известные вещи, такие как: сбор покрытия, предикаты безопасности, сортировка аварийных завершений. Мы нашли несколько интересных аварийных завершений в проекте FreeImage. Подводя итог, комбинирование символьных вычислителей с различными движками фаззинга является достаточно перспективным подходом для поиска новых ошибок. Надеюсь, этот небольшой гайд был интересен и полезен вам:).


Андрей Федотов

Clone this wiki locally