-
Notifications
You must be signed in to change notification settings - Fork 36
Directed fuzzing for golang image (Go) project with sydr‐fuzz (LibAFL‐DiFuzz backend) (rus)
В этой статье рассматривается методика направленного фаззинга Go-кода с использованием интерфейса Sydr-Fuzz, построенного на базе фаззера LibAFL-DiFuzz. Инструмент Sydr-Fuzz обеспечивает удобный способ запуска гибридного фаззинга, комбинируя динамическое символьное выполнение на основе инструмента Sydr с современными фаззерами. Кроме того, Sydr-Fuzz поддерживает минимизацию корпуса, сбор покрытия в удобно читаемых форматах, обнаружение ошибок через проверку предикатов безопасности и анализ сбоев с помощью Casr.
Следующим шагом в развитии Sydr-Fuzz стала поддержка направленного фаззинга с использованием инструмента LibAFL-DiFuzz. Этот подход позволяет сосредоточить анализ на выбранных точках кода, что делает его особенно полезным для детального исследования отдельных компонентов. LibAFL-DiFuzz, построеный на модульной архитектуре LibAFL, предоставляет возможность указывать одну или несколько целевых точек, определяющих направление анализа. Для корректной работы требуется этап статической предобработки программы, на котором строятся специальные метрики, используемые в процессе фаззинга. В ходе анализа LibAFL-DiFuzz отслеживает текущее состояние выполнения программы и управляет распределением энергии входов, увеличивая шансы достижения целевых точек.
Для демонстрации возможностей гибридного направленного фаззинга LibAFL-DiFuzz в рамках инструмента Sydr-Fuzz будем использовать библиотеку image, написанную на языке Go.
Для подготовки фаззинг цели необходимо подготовить целевые точки, которые требуется достичь и проанализировать, а также одна или несколько оберток, позволяющих достичь этих точек с помощью фаззинга. Для image было написано целых 5 оберток, поэтому рассмотрим только одну: FuzzWebp, описанной в файле-обертке.
Далее необходимо подготовить специальный файл сборки цели Makefile.toml. Makefile.toml можно сгенерировать по шаблону при помощи скрипта gen_target.py, указав значения аргументов, специфичных для фаззинг цели. Чтобы посмотреть необходимые аргументы, можно выполнить следующую команду:
$ python3 gen_target.py -h
В полученном файле определены цели сборки для символьного исполнения Sydr (debug) (также она используется для анализа аварийных завершений), для фаззинга LibAFL-DiFuzz (target), а также дополнительные цели сборки для анализа покрытия (coverage). Конкретное описание файла Makefile.toml будет в секции "Сборка для LibAFL-DiFuzz". А сейчас про целевые точки и сборку фаззинг цели.
Целевые точки указываются в файле config.toml в следующем формате:
[[target]]
file = "/image/webp/decode.go"
line = 73
[[target]]
file = "/image/webp/decode.go"
line = 175
[[target]]
file = "/image/webp/decode.go"
line = 210
Фаззинг цель для удобства мы собираем в Docker-образ, в котором будет установлено необходимое окружение и будут собраны фаззинг цели. Рассмотрим файл Dockerfile_libafl:
ARG BASE_IMAGE="sydr/ubuntu22.04-sydr-fuzz"
FROM $BASE_IMAGE
ARG SYDR_ARCHIVE="./sydr.zip"
WORKDIR /
RUN git clone https://github.com/golang/image.git
RUN git clone --depth=1 https://github.com/dvyukov/go-fuzz-corpus.git
WORKDIR /image
RUN git checkout c574db581976698ac047466629eeeb7b17bb49dd
RUN go get github.com/dvyukov/go-fuzz/go-fuzz-dep
COPY fuzz.go ./
# Move target and additional libs to main project.
RUN cp -r /root/.go/src/image/png . && \
cp -r /root/.go/src/image/jpeg . && \
cp -r /root/.go/src/image/gif . && \
cp -r /root/.go/src/internal/byteorder . && \
cp -r /root/.go/src/image/internal/imageutil .
RUN mkdir cmd/webp && \
mkdir cmd/tiff && \
mkdir cmd/png && \
mkdir cmd/jpeg && \
mkdir cmd/gif
COPY sydr_webp.go cmd/webp/main.go
COPY sydr_tiff.go cmd/tiff/main.go
COPY sydr_png.go cmd/png/main.go
COPY sydr_jpeg.go cmd/jpeg/main.go
COPY sydr_gif.go cmd/gif/main.go
# Copy LibAFL-DiFuzz target template.
COPY directed_target /directed_target
WORKDIR /directed_target
# Build image for LibAFL-DiFuzz.
ADD ${SYDR_ARCHIVE} ./
RUN unzip -o ${SYDR_ARCHIVE} && rm ${SYDR_ARCHIVE}
RUN OUT_DIR=/ cargo make all
WORKDIR /
Сначала клонируется проект golang/image с GitHub, затем фиксируем определенную версию для анализа. Затем копируем в Docker необходимые файлы. Далее обратим внимание, что необходим специальный архив ${SYDR_ARCHIVE}. Это архив, содержащий бинарные файлы и библиотеки, необходимые для работы LibAFL-DiFuzz, путь до него установлен по умолчанию как ./sydr.zip, но путь до него можно указать во время сборки докера. Команда OUT_DIR=/ cargo make all
выполняет сборку целей difuzz, target, debug и coverage из Makefile.toml. Теперь соберём образ при помощи команды:
$ sudo docker build --build-arg SYDR_ARCHIVE="difuzz.zip" -t oss-sydr-fuzz-libafl-image-go -f ./Dockerfile_libafl .
Рассмотрим содержимое файла Makefile.toml. В нем задаются цели сборки, а также зависимости между ними, а для каждой цели можно задать скрипт, выполняющий ту или иную семантику. В нашем случае первым делом выполняется статический анализ программы:
[tasks.difuzz_unix]
script_runner = "@shell"
script = '''
# Fix additional packages import paths.
cd ${EXAMPLE_DIR}
sed -i 's|"internal/byteorder"|"golang.org/x/image/byteorder"|' gif/writer.go
sed -i 's|"image/internal/imageutil"|"golang.org/x/image/imageutil"|' jpeg/reader.go
cd ${OUT_DIR_ABS}
${DIFUZZ_DIR_ABS}/difuzz-go -r webp.main -c ${PROJECT_DIR}/config_webp.toml -p ${EXAMPLE_DIR}/cmd/webp/main.go -e ${OUT_DIR_ABS}/ets_webp.toml ${DIFUZZ_ARGS}
${DIFUZZ_DIR_ABS}/difuzz-go -r tiff.main -c ${PROJECT_DIR}/config_tiff.toml -p ${EXAMPLE_DIR}/cmd/tiff/main.go -e ${OUT_DIR_ABS}/ets_tiff.toml ${DIFUZZ_ARGS}
${DIFUZZ_DIR_ABS}/difuzz-go -r png.main -c ${PROJECT_DIR}/config_png.toml -p ${EXAMPLE_DIR}/cmd/png/main.go -e ${OUT_DIR_ABS}/ets_png.toml ${DIFUZZ_ARGS}
${DIFUZZ_DIR_ABS}/difuzz-go -r jpeg.main -c ${PROJECT_DIR}/config_jpeg.toml -p ${EXAMPLE_DIR}/cmd/jpeg/main.go -e ${OUT_DIR_ABS}/ets_jpeg.toml ${DIFUZZ_ARGS}
${DIFUZZ_DIR_ABS}/difuzz-go -r gif.main -c ${PROJECT_DIR}/config_gif.toml -p ${EXAMPLE_DIR}/cmd/gif/main.go -e ${OUT_DIR_ABS}/ets_gif.toml ${DIFUZZ_ARGS}
'''
Изначально поправляем пути импортов для некоторых файлов для правильной компиляции. Бинарный файл собирать для этапа анализа не нужно, достаточно инструменту DiFuzz-Go выполняеть статический анализ, дав в аргумент -p путь до файла с main функцией фаззинг-цели.
Запустим цель difuzz и увидим следующий вывод от инструмента DiFuzz-Go:

После анализа на выходе будут получены файлы ets_webp.toml и другие ets.toml_ файлы, а также графы вызовов и ГПУ целевых функций программы вместе с их деревьями доминаторов в формате .DOT. Файлы ets.toml_ используются при инструментации проекта.
Теперь выполним сборку фаззинг цели с инструментацией, необходимой для фаззера LibAFL-DiFuzz:
[tasks.target_unix]
script_runner = "@shell"
script = '''
${GOINSTR_DIFUZZ} -a insert -i ${EXAMPLE_DIR} -o / -e ${OUT_DIR_ABS}/ets_webp.toml -l info -j 8
${GOINSTR_SANCOV} -a insert -i ${EXAMPLE_DIR} -o / -l info -j 8
cd ${EXAMPLE_DIR_INSTR}/cmd/webp
CGO_LDFLAGS="-L${LIBFORKSERVER_DIR_ABS}" go build -o ${OUT_DIR_ABS}/difuzz_target_image_webp
${GOINSTR_DIFUZZ} -a remove -i ${EXAMPLE_DIR} -o / -e ${OUT_DIR_ABS}/ets_webp.toml -keep-ets -l info
...
'''
dependencies = ["difuzz"]
Здесь мы инструментируем весь исходный код проекта с помощью двух инструментаторов: goinstr_difuzz и goinstr_sancov. На вход для goinstr_difuzz нужно дать путь до корня проекта (содержащего файл go.mod) через -i, опцию -a insert, выходную директорию, куда будет скопирован весь проект с инструментацией (к имени директории проекта будет добавлено -instr
), -e - путь до ets.toml, и, наконец, -l info режим лога и -jN - количество параллельных потоков. Инструментатор вставляет в исходный код проекта вызовы difuzz.InstrumentHook(BBID)
в необходимых местах. Инструментатору goinstr_sancov даются аналогичные опции, кроме -e. Важно отметить, если сначала запустить goinstr_sancov, а затем goinstr_difuzz, то инструментация sancov
не сохранится! Теперь можно приступать к сборке цели! Переходим в директорию с файлом с main функцией (но это необязательно, в go build
можно передать сразу путь до main.go), а далее выполняем команду CGO_LDFLAGS="-L${LIBFORKSERVER_DIR_ABS}" go build -o ${OUT_DIR_ABS}/difuzz_target_image_webp
, передав в переменной окружения CGO_LDFLAGS флаг -L
с путем до библиотеки liblibforkserver.a
.
Выставив в инструментаторах опцию -l debug, проинструментируем нашу фаззинг цель. Увидим следующий вывод, означающий, что инструментация была выполнена на примере одной из функций.

Цели debug и coverage делаются проще, их можно посмотреть в том же Makefile.toml.
Чтобы запустить гибридный направленный фаззинг через Sydr-Fuzz для двух оберток необходимо составить конфигурационный файл webp-libafl.toml. Файлы должены содержать:
- параметр exit-on-time, задающий время до завершения фаззинга при отсутствии нового покрытия (опционально),
- таблицу [sydr] с указанием аргументов Sydr (args, jobs) и строки запуска целевой программы (target),
- таблицу [difuzz] с указанием пути до фаззера LibAFL-DiFuzz (path), его аргументов (args), строки запуска целевой программы (target) и пути до бинарного файла программы (если необходимо выполнить анализ аварийных завершений), собранного для анализа инструментом Casr (casr_bin), в случае Go можно взять тот же бинарный файл, что и для [sydr],
- а также таблицу [cov] с указанием строки запуска целевой программы (target) для сбора покрытия (опционально).
Например, для webp конфигурационный файл будет выглядеть так:
exit-on-time = 7200
[sydr]
target = "/sydr_image_webp @@"
args = "-s 90 --wait-jobs -j2"
jobs = 2
[difuzz]
path = "/directed_target/sydr/difuzz/libafl_difuzz"
target = "/difuzz_target_image_webp @@"
args = "-j4 --sync-limit 200 --sync-jobs 2 --panic-analysis go -l64 -i /go-fuzz-corpus/webp/corpus -e /ets_webp.toml"
casr_bin = "/sydr_image_webp"
[cov]
target = "/coverage_image_webp @@"
source = "/image"
В данном конфиге важно обратить внимание на опцию фаззера --panic-analysis go
. Эта опция включает обработку ошибок panic()
в Go, то есть при включенной опции инпуты, приводящие к панике, будут считаться как crash.
Запускаем гибридный направленный фаззинг через Sydr-Fuzz, указав путь до конфигурационного файла webp-libafl.toml:
$ sydr-fuzz -c webp-libafl.toml run
Фаззинг начался, процессы LibAFL-DiFuzz запускаются и начинают отправлять статистику по текущему состоянию:

Примерно сразу начинает появляться информация о доостигнутых целевых точках, указанных в файле config.toml:

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

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

Результатом направленного фаззинга становится набор objective-инпутов. Каждый такой инпут характеризуется тем, что вызывает у целевой программы как минимум одно из следующих состояний:
- аварийное завершение (crash),
- достижение заданной точки в коде (target),
- зависание (timeout).
Постобработка результатов включает два этапа:
-
Минимизация: Для группы инпутов, приводящих к одинаковым результатам (например, к одной и той же целевой точке), сохраняется только самый первый сгенерированный файл. Это позволяет точно измерять метрику TTE (Time To Exposure) на основе репрезентативной выборки. Минимизацию можно отключить через опцию конфигурации
xmin = false
, что полезно для сохранения полного набора данных. - Кластеризация: Оставшиеся после минимизации инпуты группируются (кластеризуются) по достигнутым целевым точкам. Для каждой точки создается отдельная директория, название которой указывает на её локацию в коде. Важно, что если один инпут приводит к срабатыванию нескольких точек, он будет присутствовать в каждом из соответствующих кластеров. Структурно выходная директория с кластерами выглядит так:

В результате гибридного направленного фаззинга были достигнуты все целевые точки. Были созданы директории difuzz/crashes_TARGET_NAME:TARGET_LINE, куда были скопированы входные файлы, достигающие эти точки. В данном случае 4 target
и 1 timeout
инпута осталось после минимизации.
В направленном фаззинге сбор покрытия производится не только по итоговому корпусу, но и по всем objective-файлам, оставшимся после минимизации. Это позволяет также увидеть покрытие кода, содержащего целевые точки (и ошибки).
Сбор покрытия через Sydr-fuzz запускается с помощью команды:
$ sydr-fuzz -c webp-libafl.toml cov-html
Получаем следующий лог:

И в HTML-отчете можно убедиться, что целевая точка, например, /image/webp/decode.go:162 была достигнута (чем более зеленый цвет у строк, тем чаще они были покрыты разными инпутами):

Анализ крешей, полученных в результате фаззинга, может быть выполнен с помощью инструмента Casr. Для этого нужно запустить следующую команду:
$ sydr-fuzz -c webp-libafl.toml casr
Здесь уже анализируются только те objective'ы, которые приводят к крешу программы. В результате запуска Casr получим отдельную кластерную иерархию в директории webp-libafl-out/casr
. Запускать анализ имеет смысл, когда получилось найти не только инпуты, достигающие те или иные точки, но и инпуты-креши (после завершения фаззинга они начинаются на crash_*
.
В этой статье был представлен подход к гибридному направленному фаззингу Go-кода, объединяющий фаззер LibAFL-DiFuzz и символьный интерпретатор Sydr через инструмент Sydr-Fuzz. Подробно рассматривается этап подготовки целевой программы, продемонстрированный на примере проекта image. Показано, что с помощью Sydr-Fuzz можно автоматизировать ключевые этапы работы: непосредственно направленный фаззинг, последующую минимизацию и кластеризацию результатов, сбор покрытия и анализ аварийных завершений утилитой Casr.