-
Notifications
You must be signed in to change notification settings - Fork 36
Directed fuzzing for xlnt project with sydr‐fuzz (LibAFL‐DiFuzz backend) (rus)
В этой статье мы рассмотрим подход к направленному фаззингу с помощью интерфейса Sydr-Fuzz на основе фаззера LibAFL-DiFuzz. Sydr-Fuzz предоставляет удобный интерфейс для запуска гибридного фаззинга, задействуя возможности динамического символьного выполнения на базе инструмента Sydr в сочетании с современными фаззерами. Помимо фаззинга, Sydr-Fuzz предлагает набор возможностей для минимизации корпуса, сбора покрытия, поиска ошибок в программах посредством проверки предикатов безопасности, а также анализа аварийных завершений при помощи Casr.
Новым этапом развития Sydr-Fuzz стала интеграция направленного фаззинга на базе инструмента LibAFL-DiFuzz. Направленный фаззинг позволяет проводить анализ с фокусировкой на заданных точках кода и подходит для более узконаправленного анализа отдельных областей. LibAFL-DiFuzz основывается на модульной архитектуре библиотеки LibAFL и позволяет задавать "направление" анализа при помощи одной или сразу нескольких целевых точек в коде. Для осуществления направленного фаззинга инструменту необходим этап статической предобработки программы, позволяющий построить специальные метрики, использующиеся в дальнейшем в процессе анализа. Во время фаззинга LibAFL-DiFuzz отслеживает текущее состояние и занимается планированием энергии входных данных, повышая вероятность приближения к целевым точкам.
Для демонстрации возможностей гибридного направленного фаззинга с Sydr-Fuzz будем использовать библиотеку xlnt.
Сборка производится с помощью cargo make, который автоматически собирает все фаззинг-цели и выполняет нужную подготовку для направленного фаззинга.
Первым делом подготовим фаззинг цель. Это понятие для направленного фаззинга несколько отличается от стандартного понимания фаззинг цели. Задача направленного фаззинга - обеспечить достижение заданных точек в коде - требует наличия некоторой общей точки входа в программу, из которой эти точки должны быть достижимы. Обычно в качестве точки входа берётся функция main, поскольку из неё можно добраться в любую точку программы. Однако в случае библиотеки необходимо написать обёртку на одну или несколько её функций, учитывая достижимость целевых точек.
Для направленного фаззинга с LibAFL-DiFuzz необходимо подготовить:
- Код фаззинг обертки
- Скрипт сборки фаззинг обертки
- Makefile.toml для сборки целей
- config.toml с целевыми точками
Для нашего примера вполне подойдёт функция load и обёртка, используемая в этом гайде. Нам понадобится сборка с функцией main, что отражено в соответствующем скрипте сборки. Скрипт сборки - исполняемый файл, который используется в Makefile.toml для сборки целей с различной инструментацией (для фаззинга LibAFL-DiFuzz, для символьной интерпретации Sydr, для анализа аварийных завершений Casr, для сбора покрытия). Соответственно, скрипт сборки фаззинг-цели должен: осуществлять переход в репозиторий проекта, очищать артефакты предыдущей сборки, выполнять конфигурацию и сборку проекта и фаззинг-цели. Сборка различных целей в Makefile.toml определяется через переменные окружения CC/CXX/CFLAGS/CXXFLAGS, поэтому в скрипте сборки эти переменные не должны перезаписываться. Примеры различных скриптов сбокри для направленного фаззера: xlnt, cxxfilt.
Cпециальный Makefile.toml осуществляет сборку фаззинг-цели с различной инструментацией, а также производит предобработку для направленного фаззинга (построение графов и целевых последовательностей). Makefile.toml можно сгенерировать по шаблону при помощи скрипта gen_target.py (sydr/difuzz/template/gen_target.py), указав значения аргументов, специфичных для фаззинг цели:
- -p/--project: название проекта
- -s/--script: путь до скрипта сборки фаззинг цели
- -t/--target-dir: путь до директории с исходным кодом проекта
- -m/--main-path: путь до файла с реализацией функции main относительно target-dir (или абсолютный путь)
- -b/--bin-path: путь до бинарного файла фаззинг-цели, получающегося в результате сборки, относительно target-dir
- -a/--bin-args: аргументы для запуска бинарного файла (с "@@" вместо имени файла входных данных)
- -c/--config-dir (опционально): директория для сохранения подготовленных конфигурационных файлов (по умолчанию ".")
- -r/--rep-clone: (опционально) bash-команда для клонирования репозитория проекта
- -v/--version: (опционально) bash-команда для перехода на определённый коммит в проекте
- --mode: режим сборки (debug/release), по умолчанию release
- --root: название функции, которая является точкой входа в программу (main)
- -l/--lang: язык целевой программы (c/rust/go)
Для проекта xlnt все необходимые для сборки файлы уже подготовлены и лежат в поддиректории directed_target, но их также можно сгенерировать с помощью скрипта gen_target.py. К примеру, для генерации Makefile.toml для xlnt можно было бы запустить скрипт с помощью следующей команды:
$ python3 gen_target.py -p xlnt -s directed_target/build_libafl_load.sh -r "git clone https://github.com/tfussell/xlnt" -v "git checkout 3a279fcaab3432bb851c7976d4591f9505c3462a" \
-t xlnt -m /opt/StandaloneFuzzTargetMain.c -b build/load_libafl -a "@@" -c . --mode release --root main -l c
В результате в указанной в -c директории создается три файла: Makefile.toml, скрипт сборки (build_libafl_load.sh) и список целевых точек config.toml. В полученном Makefile.toml определены цели сборки для символьного исполнения Sydr (debug), для фаззинга LibAFL-DiFuzz (target), а также дополнительные цели сборки для анализа покрытия (coverage) и анализа аварийных завершений (casr). Необходимо пройти по всему Makefile.toml и внести исправления, если требуется:
- проверить корректность всех путей в [env], в частности путь до инструментов LibAFL-DiFuzz (
DIFUZZ_DIR). - выставить нужные флаги для сборки фаззинг целей в
[tasks.debug_unix],[tasks.casr_unix],[tasks.coverage_unix],[tasks.target_unix].
Последним этапом подготовки является задание целевых точек. При применении обычного гибридного фаззинга xlnt было найдено несколько крешей - возьмём некоторые из них в качестве целевых точек. Для этого добавим их в виде списка в следующий конфигурационный файл config.toml:
[[target]]
file = "/xlnt/source/detail/cryptography/compound_document.cpp"
line = 975
[[target]]
file = "/xlnt/source/detail/cryptography/compound_document.cpp"
line = 723
[[target]]
file = "/xlnt/source/detail/cryptography/compound_document.cpp"
line = 126
[[target]]
file = "/xlnt/source/detail/serialization/xlsx_consumer.cpp"
line = 2031
[[target]]
file = "/xlnt/source/detail/serialization/zstream.cpp"
line = 269
[[target]]
file = "/xlnt/source/utils/path.cpp"
line = 185
[[target]]
file = "/xlnt/source/worksheet/worksheet.cpp"
line = 1086Перейдём к сборке фаззинг цели. Команда OUT_DIR=/ cargo make all выполняет сборку целей debug, target, coverage и casr из Makefile.toml. С помощью переменной окружения OUT_DIR можно задать путь для сохранения собранных бинарных файлов. Стоит отметить, что при сборке целей под LibAFL-DiFuzz и покрытие, исходный код фаззинг-цели автоматически патчится при помощи скрипта insert_forkserver.py. Поэтому между запусками cargo make all, например, во время отладки, необходимо проверять и откатывать изменения в исходном коде (в частности в main_path - файле с функцией main).
Для проекта xlnt был подготовлен Docker-образ, в котором установлено всё необходимое окружение и выполняется сборка фаззинг-целей. Соответствующий докерфайл называется Dockerfile_libafl:
ARG BASE_IMAGE="sydr/ubuntu22.04-sydr-fuzz"
FROM $BASE_IMAGE
ARG SYDR_ARCHIVE="./sydr.zip"
WORKDIR /
# Clone target from GitHub.
RUN git clone https://github.com/tfussell/xlnt
WORKDIR /xlnt
# Checkout specified commit. It could be updated later.
RUN git checkout 3a279fcaab3432bb851c7976d4591f9505c3462a && git submodule update --init --recursive
# Copy build script and targets.
COPY save.cc load.cc ./
# Copy LibAFL-DiFuzz target template.
COPY directed_target /directed_target
WORKDIR /directed_target
# Build xlnt for LibAFL-DiFuzz.
ADD ${SYDR_ARCHIVE} ./
RUN unzip -o ${SYDR_ARCHIVE} && rm ${SYDR_ARCHIVE}
RUN OUT_DIR=/ cargo make all
# Prepare seed corpus.
RUN mkdir /corpus && find /xlnt -name "*.xlsx" | xargs -I {} cp {} /corpus
RUN cp -r /corpus /save_corpus
RUN for file in /save_corpus/*; do sed -i '1s/^/\x00\x05/' $file; doneВсе предварительно подготовленные файлы (Makefile.toml, скрипт сборки, config.toml) находятся в отдельной директории directed_target. Стоит обратить внимание, что для сборки докера понадобится архив sydr.zip, содержащий бинарные файлы и библиотеки, необходимые для инструментации LibAFL-DiFuzz. Соберём образ при помощи команды:
sudo docker build --build-arg SYDR_ARCHIVE="sydr.zip" -t oss-sydr-fuzz-libafl-xlnt -f ./Dockerfile_libafl .
Рассмотрим подробнее каким образом в Makefile.toml производится сборка цели для LibAFL-DiFuzz. Сборка программы непосредственно для фаззера LibAFL-DiFuzz выполняется в несколько этапов. Сначала производится предварительная сборка с добавлением отладочной информации при помощи компиляторов wllvm/wllvm++. Далее - статический анализ полученного бинарного файла инструментом DiFuzz. Для запуска статического анализа программы необходимо указать путь до конфигурационного файла config.toml и до бинарного файла программы, полученного в результате сборки wllvm/wllvm++, а также аргументы, специфичные для анализа. Эти этапы описаны целью difuzz в файле Makefile.toml:
[tasks.difuzz_unix]
script_runner = "@shell"
script = '''
cd ${PROJECT_DIR}
export LLVM_COMPILER=clang
export CC=wllvm; export CXX=wllvm++
export CFLAGS="-g -fsanitize=address,integer,bounds,null,undefined,float-divide-by-zero"; export CXXFLAGS="$CFLAGS"
python3 ${DIFUZZ_DIR_ABS}/insert_forkserver.py -a insert -l c -f /opt/StandaloneFuzzTargetMain.c
python3 ${DIFUZZ_DIR_ABS}/insert_forkserver.py -a comment -l c -f /opt/StandaloneFuzzTargetMain.c
cd ${OUT_DIR_ABS}
${PROJECT_DIR}/build_libafl_load.sh
${DIFUZZ_DIR_ABS}/difuzz -c ${PROJECT_DIR}/config.toml -b ${EXAMPLE_DIR}/build/load_libafl -e ${OUT_DIR_ABS}/ets_load.toml ${DIFUZZ_ARGS}
${PROJECT_DIR}/build_libafl_save.sh
${DIFUZZ_DIR_ABS}/difuzz -c ${PROJECT_DIR}/config.toml -b ${EXAMPLE_DIR}/build/save_libafl -e ${OUT_DIR_ABS}/ets_save.toml ${DIFUZZ_ARGS}
'''При запуске цели difuzz увидим следующий вывод от инструмента DiFuzz:
После этапа статического анализа создаётся вспомогательный конфигурационный файл ets.toml, а также сохраняются в формате DOT граф вызовов и ГПУ целевых функций программы вместе с их деревьями доминаторов. Файл ets.toml используется при повторной сборке программы инструментирующими компиляторами libafl_cc/libafl_cxx. Для сборки необходимо добавить вызов функции инициализации фаззинга в код функции main при помощи скрипта insert_forkserver.py, а также обработать ets.toml при помощи специального менеджера ETS_SHARED_MANAGER (обеспечивает параллельную компиляцию модулей, задействуя разделяемую память). Эти действия описаны целью target:
[tasks.target_unix]
script_runner = "@shell"
script = '''
cd ${PROJECT_DIR}
export CC=${LIBAFL_CC}
export CXX=${LIBAFL_CXX}
export CFLAGS="-g -fsanitize=address,integer,bounds,null,undefined,float-divide-by-zero"; export CXXFLAGS="$CFLAGS"
${ETS_SHARED_MANAGER} -a remove -n xlnt_load
${ETS_SHARED_MANAGER} -a create -n xlnt_load
${ETS_SHARED_MANAGER} -a parse -n xlnt_load -i ${OUT_DIR_ABS}/ets_load.toml
python3 ${DIFUZZ_DIR_ABS}/insert_forkserver.py -a uncomment -l c -f /opt/StandaloneFuzzTargetMain.c
export LIBAFL_SHARED_NAME="xlnt_load"
${PROJECT_DIR}/build_libafl_load.sh
mv ${EXAMPLE_DIR}/build/load_libafl ${OUT_DIR_ABS}/load_libafl
${ETS_SHARED_MANAGER} -a dump -n xlnt_load -o ${OUT_DIR_ABS}/ets_load.toml
${ETS_SHARED_MANAGER} -a remove -n xlnt_load
python3 ${DIFUZZ_DIR_ABS}/insert_forkserver.py -a remove -l c -f /opt/StandaloneFuzzTargetMain.c
'''
dependencies = ["difuzz"]При компиляции увидим следующие логи (выставив значение переменной LIBAFL_DEBUG_PASS=2):
После запуска этих целей, а также цели debug (и, при необходимости, coverage, casr) сборка программы для направленного фаззинга готова.
Для запуска гибридного направленного фаззинга через Sydr-Fuzz необходимо составить конфигурационный файл load-libafl.toml. Файл должен содержать:
- параметр exit-on-time, задающий время до завершения фаззинга при отсутствии нового покрытия,
- таблицу
[sydr]с указанием аргументов Sydr (args, jobs) и строки запуска целевой программы (target), - таблицу
[difuzz]с указанием пути до фаззера LibAFL-DiFuzz (path), его аргументов (args), строки запуска целевой программы (target) и (при необходимости) пути до бинарного файла программы, собранного для анализа инструментом Casr (casr_bin), - а также (при необходимости) таблицу
[cov]с указанием строки запуска целевой программы (target) для сбора покрытия.
Получим следующий конфигурационный файл для обёртки load:
exit-on-time = 7200
[sydr]
args = "--wait-jobs -s 90 -j2"
target = "/load_sydr @@"
jobs = 2
[difuzz]
path = "/directed_target/sydr/difuzz/libafl_difuzz"
target = "/load_libafl @@"
args = "-j4 -l64 -i /corpus -e /ets_load.toml"
casr_bin = "/load_casr"
[cov]
target = "/load_cov @@"В аргументах для [difuzz] в данном конфигурационном файле указывается:
- -j4: параллельный запуск в 4 процесса
- -l64: ограничение размера стека в 64Гб
- -i /corpus: путь до начального корпуса фаззинга
- -e /ets.toml: путь до файла с целевыми последовательностями
Подробнее о настройке LibAFL-DiFuzz в sydr-Fuzz в документации.
Запустим гибридный направленный фаззинг через Sydr-Fuzz для обёртки load, указав путь до load-libafl.toml:
sydr-fuzz -c ./load-libafl.toml run
В начале фаззинга можно увидеть, как запускаются процессы LibAFL-DiFuzz и начинают отправлять статистику по текущему состоянию:
Некоторое время спустя в логах появляется информация о достижении целевых точек, заданных в config.toml:
Для каждой точки указывается время её достижения. Некоторые точки могут быть достигнуты по несколько раз - это нормально, так как при мутации инпутов может получаться множество файлов со схожими путями выполнения.
Также в процессе фаззинга можно увидеть подобные логи:
По ним можно увидеть количество файлов, которые были импортированы фаззером от Sydr. Эти файлы оказались полезны по одной из двух причин: либо они помогают в достижении целевых точек, либо открывают новое покрытие целевой программы.
По окончании анализа выводится общая статистика по всем процессам фаззера, а также список всех достигнутых целевых точек:
Здесь уже для каждой точки выводится минимальное время её достижения - по этим измерениям можно оценить метрику Time to Exposure (TTE).
Результатами направленного фаззинга являются objective-инпуты, которые приводят по крайней мере к одному из трёх состояний:
- аварийное завершение программы (crash),
- достижение целевой точки (target),
- зависание программы (timeout).
По окончании фаззинга производится минимизация и сортировка результатов. При минимизации из всех файлов с одинаковыми характеристиками для достижения целевых точек сохраняется только один -- файл, который был сгенерирован первым. Это позволяет измерять метрику TTE по минимизированному набору objective'ов.
Опция конфига xmin позволяет отключать минимизацию путём установки в значение false. Это может быть полезно в случаях, когда требуется сохранить все objective-файлы.
Оставшиеся после минимизации файлы переименовываются в соответствии с их влиянием на целевую программу и сортируются по достигнутым целевым точкам. В результате сортировки в директории load-libafl-out формируются отдельные директории-кластеры для каждой из целевых точек. При этом если некоторый objective приводит к достижению сразу двух целевых точек, он будет добавлен в оба кластера. Название кластера содержит локацию целевой точки:
Здесь мы видим, что в результате запуска гибридного направленного фаззинга было достигнуто 6 целевых точек. При этом, к примеру, для compound_document.cpp:125 было найдено 2 креша, 1 objective, достигающий точки без креша, и 2 зависания.
Для направленного фаззинга сбор покрытия производится не только по файлам итогового корпуса, но и по всем objective-файлам, оставшимся после минимизации. Это позволяет также увидеть покрытие кода, содержащего целевые точки.
Воспользуемся следующей командой:
$ sydr-fuzz -c load-libafl.toml cov-html
По полученному HTML-отчёту можно увидеть, к примеру, что точка compound_document.cpp:125 была покрыта:
При помощи следующей команды можно осуществить анализ полученных в результате фаззинга аварийных завершений программы с помощью Casr:
$ sydr-fuzz -c load-libafl.toml casr
Здесь уже рассматриваются только те objective'ы, которые приводят к крешу программы. В результате запуска Casr получим отдельную кластерную иерархию в директории load-libafl-out/casr с шестью кластерами, один из которых содержит PROBABLY_EXPLOITABLE креш:
Посмотрим отчёт для этого креша при помощи команды:
$ casr-cli load-libafl-out/casr/cl1/crash-4b9fda7296ce518d-10.casrep
Мы видим часть ASAN-отчёта и несколько строк кода вокруг ошибки -- похоже на неверно вычисленный адрес для записи. Составление таких отчётов позволяет существенно упростить анализ результатов фаззинга и сэкономить время.
В этой статье был рассмотрен подход к гибридному направленному фаззингу на основе фаззера LibAFL-DiFuzz и символьного интерпретатора Sydr с помощью интерфейса Sydr-Fuzz. Направленный фаззинг требует специальной подготовки целевой программы, которую мы подробно рассмотрели на примере проекта xlnt. А вот сам фаззинг с последующей минимизацией и сортировкой результатов, сбор покрытия и анализ аварийных завершений с помощью Casr можно легко и удобно запустить с использованием Sydr-Fuzz.