Skip to content

Directed fuzzing for golang image (Go) project with sydr‐fuzz (LibAFL‐DiFuzz backend) (rus)

headshog edited this page Sep 26, 2025 · 8 revisions

Введение

В этой статье рассматривается методика направленного фаззинга 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 .

Сборка для LibAFL-DiFuzz

Рассмотрим содержимое файла 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:

difuzz

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

goinstr

Цели 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 запускаются и начинают отправлять статистику по текущему состоянию:

start

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

found

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

Иногда в логах появляются следующие записи:

reload

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

Наконец, по завешении анализа выводится информация о минимальном времени достижения каждой точки, а также информация о времени поиска ошибок и по сортировке крешей:

final

Анализ результатов

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

  • аварийное завершение (crash),
  • достижение заданной точки в коде (target),
  • зависание (timeout).

Постобработка результатов включает два этапа:

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

В результате гибридного направленного фаззинга были достигнуты все целевые точки. Были созданы директории difuzz/crashes_TARGET_NAME:TARGET_LINE, куда были скопированы входные файлы, достигающие эти точки. В данном случае 4 target и 1 timeout инпута осталось после минимизации.

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

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

Сбор покрытия через Sydr-fuzz запускается с помощью команды:

$ sydr-fuzz -c webp-libafl.toml cov-html

Получаем следующий лог:

cov

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

reached

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

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

Clone this wiki locally