Обычно нет проблем, если вы хотите фаззить безголовое приложение. Безголовое приложение может быть запущено только в терминале и не имеет графического интерфейса. Вы можете выбрать свой любимый фаззер и передать фаззинговые данные в приложение. Обычно безголовое приложение просто обрабатывает данные, а затем сразу же завершает работу или аварийно завершает работу. Но может быть по-другому, если вы пытаетесь фаззить приложение с графическим интерфейсом. Давайте попробуем фаззить текстовый редактор с открытым исходным кодом AbiWord.

Может возникнуть несколько проблем с фаззингом приложения с графическим интерфейсом. Приложение с графическим интерфейсом может продолжать работать после того, как вы введете в него неверные данные. Обычно это нормальное поведение для приложения с графическим интерфейсом. Например, вы можете попробовать открыть неверный документ в AbiWord. Затем он просто сообщает, что не может открыть документ, и продолжает работать (главное окно AbiWord все еще существует). Это ожидаемое и правильное поведение.

Но во время фаззинга мы хотим передать приложению много неверных данных. Если нам приходится закрывать приложение вручную каждый раз, когда оно открывает недопустимый документ, возможно, это не то, что нам нужно.

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

Некоторые приложения имеют пакетный режим. По сути, это приложение можно использовать как инструмент командной строки. Например, AbiWord поддерживает преобразование документа в другой формат:

$ abiword --help
Usage:
  abiword [OPTION...] [FILE...] - commandline options

Help Options:
  -h, --help                  Show help options
  --help-all                  Show all help options
  --help-gtk                  Show GTK+ Options

Application Options:
  -g, --geometry=GEOMETRY     Set initial frame geometry
  -t, --to=FORMAT             Target format of the file 
                              (abw, zabw, rtf, txt, utf8, html, ...), 
                              depends on available filter plugins

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

Есть несколько решений вышеперечисленных проблем.

Используйте виртуальный дисплей

Во-первых, давайте избавимся от надоедливых окон. В *nix есть команда Xvfb, которая создает виртуальный дисплей. Он точно доступен в Ubuntu. Вот пример того, как вы можете создать виртуальный дисплей «:1»

Xvfb :1 -screen 0 1024x768x16

Затем вы можете заставить свое приложение использовать этот дисплей. Некоторые приложения могут иметь параметр командной строки, который указывает используемый дисплей. Или вы можете попробовать поставить «: 1» в переменную DISPLAY.

Чтобы убедиться, что ваше приложение действительно использует виртуальный дисплей и отображает на нем свои данные, вы можете сделать снимок экрана с помощью таких команд:

import -display :1 -window root image.png
display image.png

Первая команда делает снимок экрана и сохраняет результат в файл «image.png». Вторая команда просто отображает «image.png».

Использовать команду тайм-аута

Теперь мы не увидим надоедливых окон, создаваемых нашим приложением во время фаззинга. Но он все равно будет ждать, пока мы его закроем. Наверное, самое простое решение — убить приложение по таймауту. Это можно легко сделать с помощью команды «тайм-аут» в * nix. Вот пример скрипта для фаззинга AbiWord с помощью zzuf fuzzer, который использует команду «timeout» для уничтожения AbiWord по тайм-ауту:

#!/bin/bash

seed=${1}
while [ ${seed} != 10000000 ]; do
    echo "seed: ${seed}"
    ${ZZUF_HOME}/bin/zzuf -c -s${seed} -r0.001:0.003 < test.doc > corrupted.doc
    ASAN_OPTIONS="detect_leaks=0" timeout 2s ${ABIWORD} \
        --display=:1 corrupted.doc > log 2>&1
    if grep AddressSanitizer log > /dev/null 2>&1 ; then
        # ASan found something

        echo "Game over"
        break
    fi
    seed=`expr ${seed} + 1`
done

Кратко опишу, что здесь происходит:

  1. Сценарий запускает цикл while, в котором увеличивается начальное значение для zzuf fuzzer. Он печатает начальное значение для каждой итерации, чтобы затем можно было легко воспроизвести все сбои (просто запустите zzuf с этим начальным числом).
  2. «test.doc» является действительным документом.
  3. Чтобы сгенерировать нечеткий документ, мы передаем действительный «test.doc» в zzuf и сохраняем нечеткий документ в «corrupted.doc».
  4. Параметр «-r» сообщает zzuf, какую часть исходного документа следует изменить.
  5. Затем мы передаем нечеткий файл «corrupted.doc» в AbiWord. Мы запускаем AbiWord с командой «timeout». Он убьет процесс AbiWord через две секунды, если он не завершится сам по себе.
  6. Мы используем опцию «-display», чтобы попросить AbiWord использовать виртуальный дисплей. Мне также нравится использовать AddressSanitizer всякий раз, когда это возможно, потому что этот отличный инструмент помогает обнаруживать больше повреждений памяти. Меня обычно не интересуют утечки памяти, поэтому я передаю «detect_leaks=0» в ASAN_OPTIONS, чтобы отключить проверку утечек памяти. Позже я покажу, как можно построить AbiWord с помощью ASan.
  7. Наконец, мы перенаправляем весь вывод AbiWord в файл журнала и ищем там строку «AddressSanitizer». Если мы нашли строку, мы сразу прекращаем фаззинг, потому что кажется, что мы нашли потенциальную проблему.

Когда скрипт останавливается с сообщением «Игра окончена», нам просто нужно заглянуть в журналы, чтобы выяснить, в чем проблема. Это самая веселая часть. Когда скрипт останавливается, «corrupted.doc» содержит нечеткие данные, вызвавшие сбой, поэтому мы можем легко воспроизвести его. Скрипт может быть обновлен, чтобы не прекращать фаззинг в случае обнаружения сбоя. В этом случае нам нужно сохранить поврежденный файл под другим именем, чтобы затем использовать его для воспроизведения сбоя.

Используйте сторожевой скрипт

zzuf — очень простой фаззер. Он просто случайным образом изменяет входные данные. Есть более умный фаззер, о котором, наверное, все знают. Это американский нечеткий лоп (AFL). Этот фаззер намного умнее. Он инструментирует приложение перед фаззингом, чтобы иметь возможность получить информацию о пройденных путях в нем. Затем он использует эту информацию для создания новых нечетких данных.

Итак, сначала вам нужно создать приложение с графическим интерфейсом с помощью AFL. В случае AbiWord это можно сделать с помощью следующих команд:

./autogen.sh
CC="/home/artem/tools/afl/afl-gcc" \
CXX="/home/artem/tools/afl/afl-g++" \
AFL_USE_ASAN=1 \
    ./configure --prefix=/path/to/abiword/directory \
make
make install

Обычно вы можете запустить фаззинг с помощью AFL с помощью следующей команды:

afl-fuzz -m 1000 -t 10000+ -i in -o out \
    /path/to/abiword/directory/bin/abiword --display=:1 @@

Но теперь вы не можете использовать команду «тайм-аут». Если вы попробуете следующую команду, то AFL будет жаловаться, что вы используете неинструментированные двоичные файлы (и это действительно так, потому что мы инструментировали AbiWord, а не «тайм-аут»):

afl-fuzz -m 1000 -t 10000+ -i in -o out \
    timeout 2s /path/to/abiword/directory/bin/abiword --display=:1 @@

Таким образом, мы больше не можем использовать команду «тайм-аут», потому что нам нужно использовать инструментированные двоичные файлы с AFL. Есть еще одно решение, которое может помочь здесь. Мы можем запустить сторожевой скрипт, который каждую секунду будет пытаться закрыть окно AbiWord. Это может быть что-то простое, например:

#!/bin/bash

while true ; do
    sleep 1
    DISPLAY=:1 xdotool key alt+F4
done

Здесь мы используем команду «xdotool», чтобы отправить ALT+F4 в окно AbiWord на виртуальном дисплее.

Теперь мы можем запустить этот скрипт как отдельный процесс, а затем начать фаззинг с помощью ALF.

Как собрать AbiWord с AddressSanitizer

Я использовал следующие команды для сборки AbiWord с ASan:

CFLAGS="-g -fsanitize=address -fno-omit-frame-pointer -O0" \
CPPFLAGS="-g -fsanitize=address -fno-omit-frame-pointer -O0" \
LDFLAGS="-fsanitize=address" \
    ./configure \
        --prefix=/path/to/abiword/directory \
        --disable-shared
make
make install

Я слышал, что теперь вы можете включить ASan в AbiWord, настроив его с опцией «-with-sanitizer=address», но я не пробовал.

Обнаруженные ошибки в AbiWord

Во время фаззинга с AFL я обнаружил пару ошибок в AbiWord. Они не похожи на проблемы с безопасностью, но все же лучше их исправить (на момент написания этого поста большинство из них уже исправлены). Вот список:

http://bugzilla.abisource.com/show_bug.cgi?id=13807: AbiWord аварийно завершает работу, если указана опция -display

http://bugzilla.abisource.com/show_bug.cgi?id=13826: Неверный доступ к памяти в IE_Imp_RTF::HandleStyleDefinition

http://bugzilla.abisource.com/show_bug.cgi?id=13827: функция _png_read() может получить доступ к массиву pBytes за его пределами

http://bugzilla.abisource.com/show_bug.cgi?id=13828: Ошибка сегментации в fp_ContainerObject::getDocSectionLayout()

Вывод

Есть еще одна проблема с этими двумя решениями выше. Проблема в том, что фаззинг будет очень медленным. По сути, одна итерация фаззинга занимает 1–2 секунды, что не очень быстро. С другой стороны, приложениям с графическим интерфейсом может потребоваться некоторое время для запуска и отображения содержимого документа. Я не измерял, сколько времени требуется AbiWord для точного запуска, но мне кажется, что на моем старом ноутбуке это около 1 секунды. Итак, похоже, что на моем оборудовании невозможно сделать лучше, чем один запуск в секунду.

Любые другие идеи или комментарии очень приветствуются!

Первоначально опубликовано на https://blog.gypsyengineer.com 26 ноября 2016 г.