(Покрытие-)направляемый фаззер для интерпретаторов динамических языков, основанный на пользовательском промежуточном языке («FuzzIL»), который можно мутировать и транслировать в JavaScript.
Использование
Основные шаги для использования этого фаззера:
Загрузите исходный код одного из поддерживаемых движков JavaScript. Список поддерживаемых движков JavaScript см. в директории Targets/.
Примените соответствующие патчи из директории целевого движка. Также см. README.md в этой директории.
Скомпилируйте движок с инструментацией покрытия (требуется clang >= 4.0), как описано в README.
Скомпилируйте фаззер: swift build [-c release].
Запустите фаззер: swift run [-c release] FuzzilliCli --profile=<profile> [other cli options] /path/to/jsshell. См. также swift run FuzzilliCli --help.
Сборка и запуск Fuzzilli и поддерживаемых движков JavaScript внутри Docker и на Google Compute Engine также поддерживаются.
Хакинг
Посмотрите main.swift, чтобы увидеть пример использования библиотеки Fuzzilli и поиграть с различными параметрами конфигурации. Затем ознакомьтесь с для высокоуровневой логики фаззинга. Оттуда погружайтесь в любую интересную часть.
Будет очень признательно, если вы отправите короткое уведомление (возможно, с номером CVE) на [email protected] или откроете pull request для любой уязвимости, найденной с помощью этого проекта, чтобы она могла быть включена в раздел галерея багов. В остальном вы, конечно, можете претендовать на любые вознаграждения за баги, кредиты CVE и т.д. за эти уязвимости :)
Концепция
При фаззинге на предмет основных ошибок интерпретатора, например в JIT-компиляторах, семантическая корректность генерируемых программ становится проблемой. Это отличается от большинства других сценариев, например фаззинга API времени выполнения, где семантическую некорректность можно легко обойти, обернув сгенерированный код в конструкции try-catch. Существуют разные способы достижения приемлемого уровня семантически корректных образцов, один из них — мутационный подход, при котором все образцы в корпусе также семантически валидны. В этом случае каждая мутация имеет лишь небольшой шанс превратить валидный образец в невалидный.
Для реализации мутационного JavaScript-фаззера необходимо определить мутации кода JavaScript. Вместо мутации AST или других синтаксических элементов программы определяется пользовательский промежуточный язык (IL), на котором мутации потока управления и данных программы могут выполняться более непосредственно. Затем этот IL транслируется в JavaScript для выполнения. Промежуточный язык выглядит примерно так:
Инструкция FuzzIL — это операция вместе с входными и выходными переменными и, возможно, одним или несколькими параметрами (обозначается в одинарных кавычках в записи выше).
Входными данными для инструкций всегда являются переменные, никаких непосредственных значений.
Каждый выход инструкции — это новая переменная, и существующие переменные могут быть переприсвоены только через специальные операции, такие как инструкция Reassign.
Каждая переменная определена до её использования.
Над этими программами могут выполняться различные мутации:
InputMutator: заменяет входные переменные инструкций на другие, мутируя поток данных программы.
CodeGenMutator: генерирует код и вставляет его в мутированную программу. Код генерируется либо путем выполнения генератора кода, либо копированием некоторых инструкций из другой программы в корпусе (сплайсинг).
CombineMutator: вставляет программу из корпуса в случайную позицию мутированной программы.
OperationMutator: мутирует параметры операций, например заменяет целочисленную константу на другую.
и другие...
Более подробное обсуждение того, как работает Fuzzilli, можно найти здесь.
Реализация
Фаззер реализован на Swift, некоторые части (например, измерение покрытия, сокетные взаимодействия и т.д.) реализованы на C.
Архитектура
Экземпляр фаззера (реализован в Fuzzer.swift) состоит из следующих центральных компонентов:
MutationFuzzer: создает новые программы из существующих, применяя мутации. Затем выполняет полученные образцы и оценивает их.
Фаззер управляется событиями, причем большинство взаимодействий между различными классами происходит через события. События генерируются, например, при краше, при нахождении интересной программы, при выполнении новой программы, при генерации лог-сообщения и т.д. Полный список событий см. в Events.swift. Механизм событий эффективно развязывает различные компоненты фаззера и упрощает реализацию дополнительных модулей.
Программа FuzzIL может быть построена с помощью экземпляра ProgramBuilder. ProgramBuilder предоставляет методы для создания и добавления новых инструкций, добавления инструкций из другой программы, получения существующих переменных, запроса контекста выполнения в текущей позиции (например, находится ли он внутри цикла) и многое другое.
Выполнение
Fuzzilli использует пользовательский режим выполнения под названием REPRL (read-eval-print-reset-loop). Для этого целевой движок модифицируется для приёма входного скрипта через каналы и/или разделяемую память, его выполнения, затем сброса своего внутреннего состояния и ожидания следующего скрипта. Это устраняет накладные расходы на создание процесса и в значительной степени на инициализацию движка.
Масштабируемость
Существует один экземпляр Fuzzer на целевой процесс. Это обеспечивает синхронное выполнение программ и тем самым упрощает реализацию различных алгоритмов, таких как последовательные мутации и минимизация. Кроме того, это избавляет от необходимости реализовывать потокобезопасный доступ к внутреннему состоянию, например к корпусу. Каждый экземпляр фаззера имеет свою собственную DispatchQueue, концептуально соответствующую одному потоку. Как правило, любое взаимодействие с экземпляром Fuzzer должно происходить в очереди этого экземпляра. Это гарантирует потокобезопасность, так как очередь последовательная. Более подробно см. документацию.
Для масштабирования экземпляры фаззера могут образовывать иерархию деревьев, в которой они сообщают об обнаруженных новых интересных образцах и крашах родительскому узлу. В свою очередь, родительский узел синхронизирует свой корпус с дочерними узлами. Связь между узлами в дереве может осуществляться разными способами, каждый из которых реализован в виде модуля:
Межпоточная связь: синхронизирует экземпляры в одном процессе путем постановки задач в очередь DispatchQueue другого фаззера.
Межмашинная связь: синхронизирует экземпляры по простому протоколу на основе TCP.
Такая конструкция позволяет фаззеру масштабироваться на множество ядер на одной машине, а также на множество разных машин. Поскольку один родительский узел может быстро перегрузиться, если слишком много экземпляров отправляют ему программы, можно настроить несколько уровней экземпляров, например один корневой экземпляр, 16 промежуточных узлов, подключенных к корню, и 256 «листьев», подключенных к промежуточным узлам. См. директорию Cloud/ для получения дополнительной информации о распределенном фаззинге.
Ресурсы
Дополнительные ресурсы об этом фаззере:
Презентация о Fuzzilli, представленная на Offensive Con 2019.
Дипломная работа, в рамках которой была выполнена первоначальная реализация.
Статья в блоге от Sensepost об использовании Fuzzilli для поиска бага в v8.
Статья в блоге от Doyensec о фаззинге движка JerryScript с помощью Fuzzilli.
Статья с симпозиума NDSS 2023 о Fuzzilli и его сравнении с другими фаззерами.
Галерея багов
Ниже приведен список некоторых багов, найденных с помощью Fuzzilli. В этот список должны включаться только баги с влиянием на безопасность, которые присутствовали как минимум в бета-версии соответствующего ПО. Поскольку Fuzzilli часто используется для непрерывного фазз-тестирования во время разработки, многие проблемы, найденные им, не включены в этот список, так как они обычно обнаруживаются до того, как уязвимый код достигает бета-версии. Список всех проблем, недавно найденных Fuzzilli в V8, можно найти здесь.
Особая благодарность всем пользователям Fuzzilli, сообщившим о найденных с его помощью багах!
WebKit/JavaScriptCore
Issue 185328: DFG Compiler использует неправильный выходной регистр для операции NumberIsInteger
CVE-2018-4299: performProxyCall раскрывает внутренний объект скрипту
CVE-2018-4359: compileMathIC генерирует некорректный машинный код
CVE-2019-8518: Выход за границы в FTL JIT из-за того, что LICM перемещает доступ к массиву перед проверкой границ
CVE-2019-8558: Use-after-free CodeBlock из-за зависших Watchpoints
CVE-2019-8611: Оптимизация AIR некорректно удаляет присваивание регистру