Первоначально разработан Михалом Залевски [email protected].
См. QuickStartGuide.txt, если у вас нет времени читать этот файл.
Фаззинг — одна из самых мощных и проверенных стратегий выявления проблем безопасности в реальном программном обеспечении; он ответственен за подавляющее большинство ошибок удаленного выполнения кода и повышения привилегий, обнаруженных на сегодняшний день в критически важном для безопасности программном обеспечении.
К сожалению, фаззинг также является относительно поверхностным; слепые случайные мутации делают маловероятным достижение определенных участков кода в тестируемом коде, в результате чего некоторые уязвимости остаются вне досягаемости этого метода.
Предпринимались многочисленные попытки решить эту проблему. Один из первых подходов, впервые предложенный Тэвисом Орманди, — это перегонка тел. Этот метод основан на сигналах покрытия для выбора подмножества интересных начальных чисел из огромного высококачественного корпуса файлов-кандидатов, а затем их фаззинга традиционными способами. Этот подход работает исключительно хорошо, но требует, чтобы такой корпус был легко доступен. Кроме того, измерения покрытия блоков дают лишь очень упрощенное понимание состояния программы и менее полезны для управления фаззингом в долгосрочной перспективе.
Другие, более сложные исследования были сосредоточены на таких методах, как анализ потока программы («конколическое выполнение»), символическое выполнение или статический анализ. Все эти методы чрезвычайно многообещающи в экспериментальных условиях, но, как правило, страдают от проблем с надежностью и производительностью при практическом использовании - и в настоящее время не предлагают жизнеспособной альтернативы «тупым» методам фаззинга.
American Fuzzy Lop — это фаззер грубой силы в сочетании с чрезвычайно простым, но надежным генетическим алгоритмом, управляемым инструментами. Он использует модифицированную форму покрытия границ, чтобы легко улавливать тонкие изменения локального масштаба в потоке управления программой.
Немного упрощая общий алгоритм можно свести к следующему:
Загрузите предоставленные пользователем исходные тестовые примеры в очередь,
Возьмите следующий входной файл из очереди,
Попытайтесь сократить тестовый пример до наименьшего размера, который не изменит измеряемое поведение программы.
Неоднократно мутируйте файл, используя сбалансированное и хорошо изученное разнообразие традиционных стратегий фаззинга.
Если какая-либо из сгенерированных мутаций привела к новому переходу состояния, зафиксированному прибором, добавьте мутированный вывод в качестве новой записи в очередь.
Перейти к 2.
Обнаруженные тестовые случаи также периодически отбраковываются, чтобы исключить те, которые устарели из-за новых находок с более высоким охватом; и пройти несколько других шагов по минимизации усилий с помощью приборов.
В качестве побочного результата процесса фаззинга инструмент создает небольшой автономный корпус интересных тестовых случаев. Они чрезвычайно полезны для запуска других, трудоемких или ресурсоемких режимов тестирования, например, для стресс-тестирования браузеров, офисных приложений, графических пакетов или инструментов с закрытым исходным кодом.
Фаззер тщательно протестирован и обеспечивает готовую к использованию производительность, намного превосходящую инструменты слепого фаззинга или инструментов только для покрытия.
Когда исходный код доступен, инструментарий можно внедрить с помощью сопутствующего инструмента, который работает как замена gcc или clang в любом стандартном процессе сборки стороннего кода.
Инструментарий оказывает довольно скромное влияние на производительность; В сочетании с другими оптимизациями, реализованными с помощью afl-fuzz, большинство программ можно фаззить так быстро или даже быстрее, чем это возможно с помощью традиционных инструментов.
Правильный способ перекомпиляции целевой программы может варьироваться в зависимости от особенностей процесса сборки, но почти универсальный подход будет следующим:
$ CC=/path/to/afl/afl-gcc ./configure
$ make clean all
Для программ C++ вам также потребуется установить CXX=/path/to/afl/afl-g++
.
Обертки clang (afl-clang и afl-clang++) можно использовать таким же образом; Пользователи clang также могут выбрать режим инструментирования более высокой производительности, как описано в llvm_mode/README.llvm.
При тестировании библиотек вам необходимо найти или написать простую программу, которая считывает данные со стандартного ввода или из файла и передает их тестируемой библиотеке. В таком случае важно связать этот исполняемый файл со статической версией инструментированной библиотеки или убедиться, что правильный файл .so загружается во время выполнения (обычно путем установки LD_LIBRARY_PATH
). Самый простой вариант — статическая сборка, обычно это возможно с помощью:
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
Установка AFL_HARDEN=1
при вызове make приведет к тому, что оболочка CC автоматически включит параметры усиления защиты кода, которые упрощают обнаружение простых ошибок памяти. Libdislocator, вспомогательная библиотека, входящая в состав AFL (см. libdislocator/README.dislocator), также может помочь обнаружить проблемы с повреждением кучи.
ПС. Пользователям ASAN рекомендуется просмотреть файл Notes_for_asan.txt на наличие важных предостережений.
Когда исходный код НЕ доступен, фаззер предлагает экспериментальную поддержку для быстрого и оперативного инструментирования двоичных файлов черного ящика. Это достигается с помощью версии QEMU, работающей в менее известном режиме «эмуляции пользовательского пространства».
QEMU — это проект, отдельный от AFL, но вы можете легко создать эту функцию, выполнив следующие действия:
$ cd qemu_mode
$ ./build_qemu_support.sh
Дополнительные инструкции и предостережения см. в qemu_mode/README.qemu.
Этот режим примерно в 2–5 раз медленнее, чем инструментарий времени компиляции, менее благоприятен для распараллеливания и может иметь некоторые другие особенности.
Для правильной работы фаззеру требуется один или несколько стартовых файлов, содержащих хороший пример входных данных, которые обычно ожидаются целевым приложением. Есть два основных правила:
Держите файлы небольшими. Размер менее 1 КБ идеален, хотя и не является строго обязательным. Для обсуждения того, почему размер имеет значение, см. perf_tips.txt.
Используйте несколько тестовых примеров, только если они функционально отличаются друг от друга. Нет смысла использовать пятьдесят разных фотографий из отпуска, чтобы запутать библиотеку изображений.
Вы можете найти много хороших примеров запуска файлов в подкаталоге testcases/, поставляемом с этим инструментом.
ПС. Если для проверки доступен большой массив данных, вы можете использовать утилиту afl-cmin для идентификации подмножества функционально различных файлов, которые используют разные пути кода в целевом двоичном файле.
Сам процесс фаззинга осуществляется утилитой afl-fuzz. Для этой программы требуется каталог только для чтения с исходными тестовыми примерами, отдельное место для хранения результатов, а также путь к двоичному файлу для тестирования.
Для целевых двоичных файлов, которые принимают входные данные непосредственно со стандартного ввода, обычный синтаксис следующий:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
Для программ, которые принимают входные данные из файла, используйте «@@», чтобы отметить место в командной строке цели, где должно быть размещено имя входного файла. Фаззер заменит вас на это:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
Вы также можете использовать опцию -f, чтобы записать измененные данные в определенный файл. Это полезно, если программа ожидает определенное расширение файла или около того.
Неинструментальные двоичные файлы можно фаззить в режиме QEMU (добавьте -Q в командной строке) или в традиционном режиме слепого фаззера (укажите -n).
Вы можете использовать -t и -m, чтобы переопределить время ожидания и ограничение памяти по умолчанию для исполняемого процесса; Редкие примеры целей, которым может потребоваться изменение этих настроек, включают компиляторы и видеодекодеры.
Советы по оптимизации производительности фаззинга обсуждаются в perf_tips.txt.
Обратите внимание, что afl-fuzz начинается с выполнения ряда шагов детерминированного фаззинга, которые могут занять несколько дней, но, как правило, дают аккуратные тестовые примеры. Если вам нужны быстрые и точные результаты прямо сейчас — как в случае с zzuf и другими традиционными фаззерами — добавьте опцию -d в командную строку.
См. файл status_screen.txt для получения информации о том, как интерпретировать отображаемую статистику и отслеживать работоспособность процесса. Обязательно ознакомьтесь с этим файлом, особенно если какие-либо элементы пользовательского интерфейса выделены красным.
Процесс фаззинга будет продолжаться до тех пор, пока вы не нажмете Ctrl-C. Как минимум, вы хотите, чтобы фаззер завершил один цикл очереди, который может занять от пары часов до недели или около того.
В выходном каталоге создаются три подкаталога, которые обновляются в реальном времени:
очередь/ — тестовые примеры для каждого конкретного пути выполнения, а также все начальные файлы, заданные пользователем. Это синтезированный корпус, упомянутый в разделе 2. Прежде чем использовать этот корпус для каких-либо других целей, вы можете уменьшить его до меньшего размера с помощью инструмента afl-cmin. Инструмент найдет меньшее подмножество файлов, обеспечивающее эквивалентное покрытие границ.
сбои/ — уникальные тестовые случаи, из-за которых тестируемая программа получает фатальный сигнал (например, SIGSEGV, SIGILL, SIGABRT). Записи группируются по полученному сигналу.
зависает/ — уникальные тестовые примеры, которые приводят к тайм-ауту тестируемой программы. Ограничение времени по умолчанию, прежде чем что-либо будет классифицировано как зависание, равно 1 секунде и значению параметра -t. Значение можно уточнить, установив AFL_HANG_TMOUT, но это требуется редко.
Сбои и зависания считаются «уникальными», если связанные пути выполнения включают в себя какие-либо переходы состояний, не замеченные в ранее зарегистрированных сбоях. Если одну ошибку можно обнаружить несколькими способами, на ранних этапах процесса произойдет некоторое увеличение количества, но оно должно быстро сойти на нет.
Имена файлов для сбоев и зависаний коррелируют с родительскими, исправными записями очереди. Это должно помочь при отладке.
Если вы не можете воспроизвести сбой, обнаруженный afl-fuzz, наиболее вероятной причиной является то, что вы не устанавливаете тот же предел памяти, который используется инструментом. Пытаться:
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10] ; /path/to/tested_binary ... )
Измените LIMIT_MB, чтобы он соответствовал параметру -m, переданному afl-fuzz. В OpenBSD также измените -Sv на -Sd.
Любой существующий выходной каталог также можно использовать для возобновления прерванных заданий; пытаться:
$ ./afl-fuzz -i- -o existing_output_dir [...etc...]
Если у вас установлен gnuplot, вы также можете создать несколько красивых графиков для любой активной задачи фаззинга, используя afl-plot. Пример того, как это выглядит, см. в http://lcamtuf.coredump.cx/afl/plot/.
Каждый экземпляр afl-fuzz занимает примерно одно ядро. Это означает, что в многоядерных системах для полного использования оборудования необходимо распараллеливание. Советы о том, как фаззить общую цель на нескольких ядрах или нескольких сетевых машинах, см. в файле Parallel_fuzzing.txt.
Режим параллельного фаззинга также предлагает простой способ взаимодействия AFL с другими фаззерами, с механизмами символического или конколического выполнения и т. д.; опять же, советы см. в последнем разделе файла Parallel_fuzzing.txt.
По умолчанию механизм мутаций afl-fuzz оптимизирован для компактных форматов данных, например изображений, мультимедиа, сжатых данных, синтаксиса регулярных выражений или сценариев оболочки. Он несколько менее подходит для языков с особенно многословным и избыточным набором слов, особенно включая HTML, SQL или JavaScript.
Чтобы избежать хлопот с созданием инструментов, учитывающих синтаксис, afl-fuzz предоставляет возможность запустить процесс фаззинга с помощью дополнительного словаря ключевых слов языка, магических заголовков или других специальных токенов, связанных с целевым типом данных, и использовать его для реконструкции. основная грамматика на ходу:
http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html
Чтобы использовать эту функцию, сначала необходимо создать словарь в одном из двух форматов, описанных в разделе dictionaries/README.dictionaries; а затем укажите на него фаззер с помощью опции -x в командной строке.
(В этом подкаталоге также уже имеется несколько распространенных словарей.)
Невозможно предоставить более структурированные описания базового синтаксиса, но фаззер, скорее всего, выяснит некоторые из них, основываясь только на обратной связи с инструментами. Это действительно работает на практике, скажем:
http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html
ПС. Даже если явный словарь не указан, afl-fuzz попытается извлечь существующие синтаксические токены во входном корпусе, очень внимательно наблюдая за инструментированием во время детерминированных переворотов байтов. Это работает для некоторых типов парсеров и грамматик, но далеко не так хорошо, как режим -x.
Если словарь действительно трудно найти, другой вариант — дать AFL поработать некоторое время, а затем использовать библиотеку захвата токенов, которая поставляется в качестве сопутствующей утилиты с AFL. Для этого см. libtokencap/README.tokencap.
Группировка сбоев на основе покрытия обычно дает небольшой набор данных, которые можно быстро сортировать вручную или с помощью очень простого сценария GDB или Valgrind. Каждый сбой также отслеживается до родительского тестового примера, не приводящего к сбою, в очереди, что упрощает диагностику ошибок.
При этом важно признать, что некоторые сбои фаззинга может быть трудно быстро оценить на предмет возможности использования без большой работы по отладке и анализу кода. Чтобы помочь в решении этой задачи, afl-fuzz поддерживает уникальный режим «исследования сбоев», включаемый флагом -C.
В этом режиме фаззер принимает на вход один или несколько тестовых случаев сбоя и использует свои стратегии фаззинга, основанные на обратной связи, для очень быстрого перечисления всех путей кода, которые могут быть достигнуты в программе, сохраняя при этом ее в состоянии сбоя.
Мутации, не приводящие к сбою, отклоняются; так же как и любые изменения, которые не влияют на путь выполнения.
Результатом является небольшой набор файлов, который можно очень быстро просмотреть, чтобы увидеть, какую степень контроля имеет злоумышленник над сбойным адресом или можно ли обойти начальное чтение за пределами допустимого диапазона - и посмотреть, что скрывается за ним. .
И еще: для минимизации тестовых примеров попробуйте afl-tmin. Инструментом можно управлять очень просто:
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
Инструмент одинаково работает как с аварийными, так и с не аварийными тестами. В аварийном режиме он с радостью принимает инструментированные и неинструментированные двоичные файлы. В режиме без сбоев минимизатор использует стандартные инструменты AFL, чтобы упростить файл без изменения пути выполнения.
Минимизатор принимает синтаксис -m, -t, -f и @@, совместимый с afl-fuzz.
Еще одно недавнее дополнение к AFL — инструмент afl-analyze. Он принимает входной файл, пытается последовательно перевернуть байты и наблюдает за поведением тестируемой программы. Затем он кодирует входные данные цветом в зависимости от того, какие разделы кажутся критическими, а какие нет; хотя он и не является пуленепробиваемым, он часто может дать быстрое представление о сложных форматах файлов. Более подробную информацию о его работе можно найти в конце файлаtechnic_details.txt.
Фаззинг — это замечательный и малоиспользуемый метод обнаружения ошибок проектирования и реализации, которые не приводят к сбоям. Довольно много интересных ошибок было обнаружено при изменении целевых программ для вызова abort(), когда, скажем:
Две библиотеки bignum выдают разные выходные данные, когда получают один и тот же входной сигнал, сгенерированный фаззером.
Библиотека изображений выдает разные выходные данные, когда ее просят декодировать одно и то же входное изображение несколько раз подряд.
Библиотека сериализации/десериализации не может обеспечить стабильные выходные данные при итеративной сериализации и десериализации данных, предоставленных фаззером.
Библиотека сжатия выдает выходные данные, не соответствующие входному файлу, когда ее просят сжать, а затем распаковать определенный большой двоичный объект.
Реализация этих или подобных проверок работоспособности обычно занимает очень мало времени; если вы являетесь сопровождающим определенного пакета, вы можете сделать этот код условным с помощью #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
(флаг также используется в libfuzzer) или #ifdef __AFL_COMPILER
(этот флаг предназначен только для AFL).
Имейте в виду, что, как и многие другие вычислительно-интенсивные задачи, фаззинг может вызвать нагрузку на ваше оборудование и операционную систему. В частности:
Ваш процессор будет перегреваться и потребует адекватного охлаждения. В большинстве случаев, если охлаждение недостаточно или перестает работать должным образом, скорость процессора будет автоматически ограничена. Тем не менее, особенно при фаззе на менее подходящем оборудовании (ноутбуках, смартфонах и т. д.), вполне возможно, что что-то взорвется.
Целевые программы могут беспорядочно захватывать гигабайты памяти или заполнять дисковое пространство ненужными файлами. AFL пытается обеспечить соблюдение основных ограничений памяти, но не может предотвратить все возможные неудачи. Суть в том, что вам не следует заниматься фаззингом в системах, где перспектива потери данных не является приемлемым риском.
Фаззинг включает в себя миллиарды операций чтения и записи в файловую систему. В современных системах это обычно сильно кэшируется, что приводит к довольно скромному «физическому» вводу-выводу, но существует множество факторов, которые могут изменить это уравнение. Вы обязаны следить за потенциальными проблемами; при очень интенсивном вводе-выводе срок службы многих жестких и твердотельных накопителей может сократиться.
Хороший способ контролировать дисковый ввод-вывод в Linux — это команда iostat:
$ iostat -d 3 -x -k [...optional disk ID...]
Вот некоторые из наиболее важных предостережений для AFL:
AFL обнаруживает неисправности, проверяя, не умирает ли первый порожденный процесс из-за сигнала (SIGSEGV, SIGABRT и т. д.). Программам, которые устанавливают специальные обработчики для этих сигналов, возможно, потребуется закомментировать соответствующий код. Точно так же ошибки в дочерней обработке, порожденные нечеткой целью, могут избежать обнаружения, если вы вручную не добавите код для их обнаружения.
Как и любой другой инструмент грубой силы, фаззер предлагает ограниченный охват, если для полной упаковки фактического формата данных, подлежащих тестированию, используются шифрование, контрольные суммы, криптографические подписи или сжатие.
Чтобы обойти эту проблему, вы можете закомментировать соответствующие проверки (для вдохновения см. Experimental/libpng_no_checksum/); если это невозможно, вы также можете написать постпроцессор, как описано в Experimental/post_library/.
Есть некоторые неудачные компромиссы с ASAN и 64-битными двоичными файлами. Это не связано с какой-то конкретной ошибкой afl-fuzz; советы см. в файле Notes_for_asan.txt.
Не существует прямой поддержки фаззинга сетевых служб, фоновых демонов или интерактивных приложений, для работы которых требуется взаимодействие с пользовательским интерфейсом. Возможно, вам придется внести простые изменения в код, чтобы заставить их вести себя более традиционно. Preeny также может предложить относительно простой вариант — см. https://github.com/zardus/preeny.
Некоторые полезные советы по изменению сетевых служб можно также найти по адресу: https://www.fastly.com/blog/how-to-fuzz-server-american-fuzzy-lop.
AFL не выводит удобочитаемые данные о покрытии. Если вы хотите отслеживать освещение, используйте afl-cov от Майкла Раша: https://github.com/mrash/afl-cov
Иногда разумные машины восстают против своих создателей. Если это произойдет с вами, обратитесь к http://lcamtuf.coredump.cx/prep/.
Помимо этого, см. раздел «УСТАНОВКА» для получения советов для конкретных платформ.
Многие улучшения afl-fuzz были бы невозможны без отзывов, отчетов об ошибках или исправлений от:
Jann Horn Hanno Boeck
Felix Groebert Jakub Wilk
Richard W. M. Jones Alexander Cherepanov
Tom Ritter Hovik Manucharyan
Sebastian Roschke Eberhard Mattes
Padraig Brady Ben Laurie
@dronesec Luca Barbato
Tobias Ospelt Thomas Jarosch
Martin Carpenter Mudge Zatko
Joe Zbiciak Ryan Govostes
Michael Rash William Robinet
Jonathan Gray Filipe Cabecinhas
Nico Weber Jodie Cunningham
Andrew Griffiths Parker Thompson
Jonathan Neuschfer Tyler Nighswander
Ben Nagy Samir Aguiar
Aidan Thornton Aleksandar Nikolich
Sam Hakim Laszlo Szekeres
David A. Wheeler Turo Lamminen
Andreas Stieger Richard Godbee
Louis Dassy teor2345
Alex Moneger Dmitry Vyukov
Keegan McAllister Kostya Serebryany
Richo Healey Martijn Bogaard
rc0r Jonathan Foote
Christian Holler Dominique Pelle
Jacek Wielemborek Leo Barnes
Jeremy Barnes Jeff Trull
Guillaume Endignoux ilovezfs
Daniel Godas-Lopez Franjo Ivancic
Austin Seipp Daniel Komaromy
Daniel Binderman Jonathan Metzman
Vegard Nossum Jan Kneschke
Kurt Roeckx Marcel Bohme
Van-Thuan Pham Abhik Roychoudhury
Joshua J. Drake Toby Hutton
Rene Freingruber Sergey Davidoff
Sami Liedes Craig Young
Andrzej Jackowski Daniel Hodson
Спасибо!
Вопросы? Обеспокоенность? Сообщения об ошибках? Пожалуйста, используйте GitHub.
Существует также список рассылки проекта; Чтобы присоединиться, отправьте письмо на адрес [email protected]. Или, если вы предпочитаете сначала просмотреть архивы, попробуйте: https://groups.google.com/group/afl-users.