
Фаззинг AFL/QEMU с полно-системной эмуляцией.
Новое: для тех, кто хочет поиграться с TriforceAFL и TLSF, Richard Johnson создал Dockerfile, который устанавливает оба компонента (и даже собирает ядро Linux за вас). Он доступен здесь https://hub.docker.com/r/moflow/afl-triforce/tags/.
Ещё новое: afl-tmin теперь работает с fork-сервером!
https://github.com/nccgroup/TriforceAFL Jesse Hertz [email protected] Tim Newsham [email protected]
Это модифицированная версия AFL с поддержкой полносистемного фаззинга с использованием QEMU. Включённый QEMU обновлён, чтобы обеспечить трассировку ветвлений при запуске системного эмулятора для x86_64. Добавлены дополнительные инструкции для запуска fork-сервера AFL, задания параметров фаззинга и отметки начала и конца тестовых примеров.
Примечание: не все инструменты AFL были протестированы с новыми изменениями. Некоторое тестирование прошли следующие инструменты:
Для сборки:
make
Чтобы получить карту покрытия:
echo hello > /tmp/hello
./afl-showmap -o coverage.txt -QQ --
./afl-qemu-system-trace -kernel ../bzImage
-initrd ../initramfs.cpio.gz -m 1G -nographic
-append "console=ttyS0" -aflFile /tmp/hello
cat coverage.txt
Чтобы запустить фаззинг:
egrep ' (panic|log_store)$' ../mykern/kallsyms ffffffff8108e570 t log_store ffffffff8181064b T panic
mkdir inputs echo hello > inputs/hello ./afl-fuzz -i inputs -o outputs -QQ -- afl-qemu-system-trace -kernel bzImage -initrd root.cpio.gz -m 1G -nographic -append "console=ttyS0" -aflPanicAddr ffffffff8181064b -aflDmesgAddr ffffffff8108e570 -aflFile @@
(Примечание: в отличие от использования опции "-Q", при использовании опции "-QQ" необходимо указывать полную командную строку для afl-qemu-system-trace).
Более подробную информацию об использовании этой модифицированной версии AFL см. в нашем фаззере системных вызовов Linux: https://github.com/nccgroup/TriforceLinuxSyscallFuzzer.
Новые флаги AFL: -QQ - использовать qemu в режиме полносистемной эмуляции вместо пользовательского режима (-Q)
Новые флаги QEMU: -aflFile - Имя файла, содержащего входные данные фаззера -aflPanicAddr - Адрес функции kernel panic для обнаружения паники -aflDmesgAddr - Адрес в ядре Linux функции журналирования dmesg для обнаружения журналирования и перехвата сообщений журнала
Новые инструкции QEMU: 0f 24 - aflCall edi=1 startForkserver(esi=enableTicks) Запускает fork-сервер AFL. После этой точки каждый тест будет выполняться в отдельном дочернем процессе, созданном через fork. Если enableTicks отличен от нуля, QEMU повторно включит таймер ЦП после создания дочернего процесса, в противном случае он не будет включён. edi=2 getWork(esi=ptr, edx=sz) Заполняет ptr[0..sz] следующим входным тестовым примером. Возвращает фактический заполненный размер (<= sz). edi=3 startWork(esi=ptr) Сообщает AFL о начале трассировки. Аргумент указывает на буфер с двумя квадрословами, задающими начальный и конечный адреса трассируемого кода. Инструкции вне этого диапазона не трассируются. edi=4 doneWork(esi=exitCode) Сообщает AFL, что тестовый пример завершён. Если обнаружена паника, AFL немедленно остановит тестовый пример. В противном случае он будет выполняться до вызова doneWork. Указанный exitCode возвращается в AFL. (Код может, но в настоящее время не делает этого, выполнять OR со значением 64 для всех кодов возврата, если во время тестового примера были обнаружены какие-либо журналы dmesg.)
Новый блочный драйвер QEMU: -drive filename=privmem: Этот блочный драйвер хранит образ диска в памяти с копированием при записи (copy-on-write), поэтому изменения никогда не сохраняются на диск. Изменения, внесённые одним тестовым примером, изолированы от других тестовых примеров.
Автор и сопровождение: Michal Zalewski [email protected]
Copyright 2013, 2014, 2015, 2016 Google Inc. Все права защищены. Выпущено на условиях Apache License, Version 2.0.
Новые версии и дополнительную информацию смотрите на: http://lcamtuf.coredump.cx/afl/
Чтобы обменяться опытом с другими пользователями или получать уведомления о крупных новых возможностях, отправьте письмо на [email protected].
** Если у вас нет времени читать этот файл, обратитесь к QuickStartGuide.txt. **
Фаззинг — одна из самых мощных и проверенных стратегий выявления проблем безопасности в реальном программном обеспечении; именно с ним связано подавляющее большинство обнаруженных на сегодняшний день ошибок удалённого выполнения кода и повышения привилегий в критически важном для безопасности ПО.
К сожалению, фаззинг также относительно поверхностен: слепые случайные мутации делают крайне маловероятным достижение некоторых путей выполнения в тестируемом коде, оставляя ряд уязвимостей совершенно недосягаемыми для этой техники.
Было предпринято множество попыток решить эту проблему. Один из ранних подходов, пионером которого стал Tavis Ormandy, — дистилляция корпуса (corpus distillation). Метод опирается на сигналы покрытия, чтобы выбрать подмножество интересных затравочных файлов из массивного высококачественного корпуса файлов-кандидатов, а затем фаззить их традиционными средствами. Подход работает исключительно хорошо, но требует наличия такого корпуса под рукой. Кроме того, измерения покрытия блоков дают лишь очень упрощённое понимание состояния программы и менее полезны для направления фаззинговых усилий в долгосрочной перспективе.
Другие, более сложные исследования были сосредоточены на таких техниках, как анализ потока программы («конколическое исполнение»), символьное исполнение или статический анализ. Все эти методы чрезвычайно многообещающи в экспериментальных условиях, но на практике страдают от проблем с надёжностью и производительностью — и в настоящее время не предлагают жизнеспособной альтернативы «слепым» техникам фаззинга.
American Fuzzy Lop — это фаззер переборного типа (brute-force), сочетающийся с чрезвычайно простым, но исключительно надёжным генетическим алгоритмом, направляемым инструментированием. Он использует модифицированную форму покрытия рёбер (edge coverage), чтобы без усилий улавливать тонкие локальные изменения потока управления программы.
Если немного упростить, общий алгоритм можно свести к следующему:
Загрузить предоставленные пользователем начальные тестовые примеры в очередь,
Взять следующий входной файл из очереди,
Попытаться сократить тестовый пример до минимального размера, не изменяющего измеряемое поведение программы,
Неоднократно мутировать файл, используя сбалансированный и хорошо изученный набор традиционных стратегий фаззинга,
Если какая-либо из полученных мутаций привела к новому переходу состояния, зафиксированному инструментированием, добавить мутированный результат как новую запись в очередь.
Перейти к пункту 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.
При тестировании библиотек вам нужно найти или написать простую программу, которая читает данные из stdin или из файла и передаёт их тестируемой библиотеке. В таком случае крайне важно слинковать этот исполняемый файл со статической версией инструментированной библиотеки либо убедиться, что во время выполнения загружается правильный файл .so (обычно путём установки LD_LIBRARY_PATH). Самый простой вариант — статическая сборка, обычно возможная через:
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
Установка AFL_HARDEN=1 при вызове 'make' заставит обёртку CC автоматически включить параметры ужесточения кода (hardening), которые упрощают обнаружение простых ошибок памяти.
PS. Пользователям 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/, который поставляется с этим инструментом.
PS. Если для отбора доступен большой корпус данных, вы можете использовать утилиту afl-cmin, чтобы выделить подмножество функционально различных файлов, которые задействуют разные пути выполнения в целевом бинарном файле.
Сам процесс фаззинга выполняется утилитой afl-fuzz. Эта программа требует каталог только для чтения с начальными тестовыми примерами, отдельное место для хранения результатов, а также путь к тестируемому бинарному файлу.
Для целевых бинарных файлов, которые принимают входные данные напрямую из stdin, обычный синтаксис таков:
$ ./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 или honggfuzz, добавьте опцию -d в командную строку.
Информацию о том, как интерпретировать отображаемую статистику и следить за здоровьем процесса, см. в файле status_screen.txt. Обязательно обратитесь к этому файлу, особенно если какие-либо элементы интерфейса подсвечены красным.
Процесс фаззинга будет продолжаться, пока вы не нажмёте Ctrl-C. Как минимум стоит позволить фаззеру завершить один цикл очереди, который может занять от пары часов до недели и более.
Внутри выходного каталога создаются и обновляются в реальном времени три подкаталога:
queue/ - тестовые примеры для каждого отличительного пути выполнения, а также все стартовые файлы, предоставленные пользователем. Это синтезированный корпус, упомянутый в разделе 2.
Прежде чем использовать этот корпус для каких-либо других
целей, вы можете уменьшить его с помощью инструмента afl-cmin.
Инструмент найдёт меньшее подмножество файлов с эквивалентным
покрытием рёбер.
crashes/ - уникальные тестовые примеры, которые приводят к получению тестируемой программой фатального сигнала (например, SIGSEGV, SIGILL, SIGABRT). Записи сгруппированы по полученному сигналу.
hangs/ - уникальные тестовые примеры, которые приводят к таймауту тестируемой программы. Обратите внимание, что при действующих настройках таймаута по умолчанию (агрессивных) здесь может быть некоторый шум из-за всплесков задержек и других естественных явлений.
Сбои (crashes) и зависания (hangs) считаются «уникальными», если связанные с ними пути выполнения содержат переходы состояния, не наблюдавшиеся в ранее зафиксированных ошибках. Если к одной и той же ошибке можно прийти несколькими способами, на раннем этапе процесса будет некоторая инфляция счётчика, но она должна быстро сойти на нет.
Имена файлов для сбоев и зависаний связаны с родительскими, не вызывающими ошибок записями очереди. Это должно помочь при отладке.
Если вам не удаётся воспроизвести сбой, найденный 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-fuzz оптимизирован для компактных форматов данных — например, изображений, мультимедиа, сжатых данных, синтаксиса регулярных выражений или сценариев оболочки. Он несколько хуже подходит для языков с особенно многословной и избыточной лексикой — в частности, HTML, SQL или JavaScript.
Чтобы избавить вас от хлопот по созданию инструментов, понимающих синтаксис, afl-fuzz предоставляет возможность затравливать процесс фаззинга опциональным словарём языковых ключевых слов, магических заголовков или других специальных токенов, связанных с целевым типом данных, — и использовать его для восстановления лежащей в основе грамматики на лету:
Чтобы воспользоваться этой функцией, сначала нужно создать словарь в одном из двух форматов, описанных в testcases/README.testcases, а затем указать фаззеру на него через опцию -x в командной строке.
Предоставить более структурированные описания лежащего в основе синтаксиса невозможно, но фаззер, скорее всего, сам разберётся с некоторыми из них, основываясь лишь на обратной связи от инструментирования. Это действительно работает на практике, например:
PS. Даже когда явный словарь не задан, afl-fuzz попытается извлечь существующие синтаксические токены во входном корпусе, очень внимательно наблюдая за инструментированием во время детерминированных перестановок байтов. Это работает для некоторых типов парсеров и грамматик, но далеко не так хорошо, как режим -x.
Группировка сбоев на основе покрытия обычно даёт небольшой набор данных, который можно быстро разобрать вручную или с помощью очень простого сценария GDB или Valgrind. Каждый сбой также можно проследить до его родительского не приводящего к сбою тестового примера в очереди, что упрощает диагностику ошибок.
При всём при этом важно признать, что некоторые сбои, найденные фаззингом, бывает трудно быстро оценить на предмет эксплуатируемости без большого объёма отладки и анализа кода. Чтобы помочь с этой задачей, afl-fuzz поддерживает весьма уникальный режим «исследования сбоев» (crash exploration), включаемый флагом -C.
В этом режиме фаззер принимает на вход один или несколько приводящих к сбою тестовых примеров и использует свои стратегии фаззинга, управляемые обратной связью, чтобы очень быстро перечислить все пути выполнения, достижимые в программе, пока она находится в состоянии сбоя.
Мутации, не приводящие к сбою, отклоняются; отклоняются также любые изменения, не влияющие на путь выполнения.
На выходе получается небольшой корпус файлов, которые можно очень быстро изучить, чтобы понять, какую степень контроля атакующий имеет над адресом, вызвавшим ошибку, или можно ли пройти за пределы первоначального чтения вне границ — и увидеть, что находится под ним.
Ах да, ещё кое-что: для минимизации тестовых примеров попробуйте afl-tmin. Инструмент используется очень просто:
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
Инструмент одинаково хорошо работает как с приводящими к сбою, так и с не приводящими к сбою тестовыми примерами. В режиме сбоя он с готовностью принимает как инструментированные, так и неинструментированные бинарные файлы. В режиме без сбоя минимизатор полагается на стандартное инструментирование AFL, чтобы упростить файл, не изменяя путь выполнения.
Минимизатор принимает синтаксис -m, -t, -f и @@ способом, совместимым с afl-fuzz.
Ещё одно недавнее дополнение к AFL — инструмент afl-analyze. Он принимает входной файл, пытается последовательно переворачивать байты и наблюдает за поведением тестируемой программы. Затем он цветовым кодом размечает входные данные, показывая, какие участки выглядят критичными, а какие нет; хотя это и не панацея, он часто позволяет быстро понять сложные файловые форматы. Дополнительную информацию о его работе можно найти ближе к концу technical_details.txt.
Имейте в виду, что, как и многие другие вычислительно интенсивные задачи, фаззинг может создавать нагрузку на ваше оборудование и операционную систему. В частности:
Ваш процессор будет сильно нагреваться и потребует адекватного охлаждения. В большинстве случаев, если охлаждения недостаточно или оно перестаёт работать должным образом, скорость процессора автоматически снижается. Тем не менее, особенно при фаззинге на менее подходящем оборудовании (ноутбуках, смартфонах и т. п.), нельзя полностью исключить, что что-то выйдет из строя.
Целевые программы могут начать беспорядочно захватывать гигабайты памяти или заполнять дисковое пространство мусорными файлами. AFL пытается обеспечивать базовые лимиты памяти, но не может предотвратить любую возможную неприятность. Суть в том, что не следует заниматься фаззингом на системах, где перспектива потери данных не является приемлемым риском.- Фаззинг включает миллиарды чтений и записей в файловую систему. На современных системах это обычно активно кэшируется, что приводит к довольно скромному «физическому» вводу-выводу, но существует множество факторов, которые могут изменить это уравнение. Вам необходимо следить за возможными проблемами; при очень интенсивном вводе-выводе срок службы многих HDD и SSD может сократиться.
Хороший способ отслеживать дисковый ввод-вывод в 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 от Michael Rash: https://github.com/mrash/afl-cov
Помимо этого, советы для конкретных платформ см. в INSTALL.
Многие улучшения 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
Спасибо!
Вопросы? Проблемы? Сообщения об ошибках? С автором обычно можно связаться по адресу [email protected].
Также у проекта есть список рассылки; чтобы присоединиться, отправьте письмо на [email protected]. Или, если вы предпочитаете сначала просмотреть архивы, попробуйте:
P.S. Если вы хотите отправить исходный код для включения в проект, учтите, что авторские права на большую часть AFL принадлежат Google. Вы сохраняете авторские права на свой вклад, но они просят сначала согласиться с простым CLA:
Извините за хлопоты. Разумеется, для запросов новых функций или сообщений об ошибках CLA не требуется.