Фаззинг JS-движков с помощью Fuzzilli

fuzzilli – это фаззер для javascript-движков от команды googleprojectzero. Его отличительная черта – это FuzzIL, промежуточный язык, который можно мутировать и затем транслировать в js. Этот язык обеспечивает мутированному js семантическую валидность: даже после нескольких раундов изменений в коде остается логика, с которой движок будет работать.

Движки, которые можно фаззить

В этом списке V8, Spidermonkey, XS и duktape уже поддерживают работу с fuzzilli. А остальные движки можно пропатчить и начать фаззинг, патчи включены в состав инструмента.

С помощью fuzzilli уже найдено много интересных багов типа OOB. Разберемся, как он устроен.

FuzzIL

FuzzIL – это промежуточное представление js, и самое интересное в нем – это мутаторы для FuzzIL. Как они генерируют валидный js-код?

Мутировать js-код можно на следующих уровнях:

  • текст скрипта (изменение случайных байт)
  • синтаксическое дерево AST (вырезать/вставлять случайное поддерево)

Первый способ не сильно отличается:

Движок просто отбросит все предлагаемые варианты.

Способ с деревьями лучше: мутатор производит синтаксически правильный js-код. Например, вот такой:

Но, несмотря на это, в логике ошибка все-таки есть: переменная y здесь используется до своего объявления. Это вызовет исключение, а возможный баг type confusion останется неотловленным.

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

Пример программы на FuzzIL:

У FuzzIL нет синтаксиса, но представлять программу как-то надо, поэтому этот пример написан на некотором псевдокоде.

Транслятор у FuzzIL есть, и этот же пример на js будет выглядеть так:

Или:

fuzzilli умеет транслировать и так и так.

Чтобы описать мутаторы, нужно немного поговорить о реализации FuzzIL.

Реализация

fuzzilli и FuzzIL написаны на языке Swift.

Проиллюстрировать классы, которые реализуют программу на FuzzIL, поможет диаграмма:

Типом данных, отвечающим за итоговый js -скрипт, является class Program.

Программа – это набор операций вместе с их входными и выходными переменными.

Например, операция LoadInteger:

LoadProperty:

Посмотреть все операции можно в Operations.swift

За создание программы отвечают ProgramBuilder и CodeGenerators. Каждый генератор производит свой специфический фрагмент FuzzIL кода. Например, IntegerGenerator создает инструкцию LoadInteger со случайным параметром, а IfElseGenerator – условный блок:

Таких генераторов десятки. Некоторые из них вызывают другие генераторы, как IfElseGenerator, чтобы код внутри блока получился интереснее.

Когда новая программа создана, она подвергается анализу через:

  • VariableAnalyzer
  • ScopeAnalyzer
  • ContextAnalyzer
  • DeadCodeAnalyzer

Проверяются переменные, их типы и области видимости. Учитывается, что переменные внутри конструкций for, while не должны быть видны снаружи. А переменные, определенные снаружи, должны быть видны внутри, если используются:

Проверяются блоки: если есть операция BeginWhileLoop, то должен быть и EndWhileLoop. И наоборот: если есть конец блока EndWhileLoop, то должно быть и начало BeginWhileLoop.

Типы: Unknown, Integer, Float, String, Boolean, Object, Function. Проверка типов не допустит, например, такой код:

Не допускается dead код, чтобы предотвратить бессмысленное разрастание тест-кейсов.

После всех проверок код транслируется в текст. За трансляцию отвечает class JavaScriptLifter. В этот класс заложены правила того, как представлять инструкции в текстовом виде.

Мутаторы

  • input mutator
  • operation mutator
  • combine mutator
  • splice mutator

 

Input mutator

Мутирует входные аргументы-переменные.

Учитывается область видимости; переменная v12 из того же скоупа, что и все остальные.
Если невозможно найти замену, то кандидат вернется на свое место.

 

Operation Mutator

Мутирует параметр операций, если он у них есть.

У объектов операций помимо входных и выходных переменных есть еще и параметры. Например, у LoadInteger это value, у LoadProperty – propertyName, а у CallMethod – methodName.

Combine Mutator

Splice Mutator

Вставка куска кода из одного тест-кейса в другой. Учитывается, что все переменные в коде должны быть определены. Алгоритм такой:

  • выбрать рандомную инструкцию;
  • найти все инструкции, чьи выходные значения используются для этой рандомной; инструкции;
  • скопировать, вставить.

 

Инструментация

Между фаззером и процессом движка создается shm область памяти, в которой хранится информация о покрытии.

Покрытие – это битмап, фаззеру интересен только факт посещения базового блока, а сколько раз поток прошел через этот блок уже неинтересно.

В каждом базовом блоке размещается вызов sanitizer_cov_trace_pc_guard:

Здесь guard – указатель на индекс блока, __shmem->edges – массив, с которым работают, как с битмапом.

Как уже упоминалось, часть движков поддерживает инструментацию fuzzilli. Чтобы инструментировать, например, v8, нужно указывать флаг v8_fuzzilli=true в процессе сборки v8. К счастью, сборка всех движков автоматизирована и помнить о флагах не нужно – в составе есть скрипты для этого. А если инструментация не поддерживается, то там же есть и патчи.

Исполнение скрипта

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

  • форк-сервер, как в AFL: размещается в процессе таргета, получает очередную порцию мусора от мастера и делает форк, в котором идет обработка. В этом случае переиспользуется все, что до форк-сервера.
  • REPRL(read-eval-print-reset-loop), как в libfuzzer: в бесконечном цикле исследуемый код ждет и получает данные, обрабатывает, потом сбрасывает состояние на начальное, и по-новой. Здесь переиспользуется все, что до цикла.

Преимущество форк-сервера в том, что его можно разместить где угодно (пример из AFL). REPRL-цикл постоянно на одном месте: (пример V8).

Но производительность вроде бы за REPRL.

Авторы fuzzilli провели измерения для движка JavaScriptCore и оказалось, что REPRL опережает форк-сервер на 6 мс за одну итерацию. Тестом был скрипт, который просто ждет 1 секунду: результат REPRL – 1,001 c, а форк-сервер – 1,007 c. Тест кажется простым, но тем не менее был выбран REPRL.

Как и инструментация, реализация REPRL включена в состав конкретного движка, если он поддерживает работу с fuzzilli.

CVE-2019-8518

Это OOB—баг, найденный в JavaScriptCore/webkit:

Вот как JIT-компилятор оптимизирует код:

  • нода CheckStructure изначально проверяет тип массива в теле цикла, однако ее можно вынести, потому что тип массива один и тот же;
  • CheckBounds – остается, потому что есть зависимость от длины массива;
  • GetByVal – выносится, так как не зависит от переменных, определенных внутри цикла.

Поэтому можно читать и писать за пределы массива.

Эксплуатация багов в js-движках – это отдельный жанр. Баг выше было легко описать, поэтому он здесь. Другие находки fuzzilli тоже интересны, но они слишком заковыристые, и их не уместить в пару абзацев.

Вывод

fuzzilli – это фаззер для js-движков, который обеспечивает большее покрытие кода, потому что генерирует и синтаксически и семантически валидный код, то есть настоящие js-скрипты. В этой статье мы разобрали, как оно работает.

Еще больше деталей есть в трудах:

Если занимаетесь поиском багов в js, может пригодиться.

Поэтому можно читать и писать за пределы массива.

Эксплуатация багов в js-движках – это отдельный жанр. Баг выше было легко описать, поэтому он здесь. Другие находки fuzzilli тоже интересны, но они слишком заковыристые, и их не уместить в пару абзацев.

Вывод

fuzzilli – это фаззер для js-движков, который обеспечивает большее покрытие кода, потому что генерирует и синтаксически и семантически валидный код, то есть настоящие js-скрипты. В этой статье мы разобрали, как оно работает.

Еще больше деталей есть в трудах:

Если занимаетесь поиском багов в js, может пригодиться.

 

Марат Гаянов 

 

Подпишитесь на наши обновления
Узнавайте все новости первыми

    Нажимая «Отправить заявку», вы даете согласие на обработку своих
    данных согласно политике обработки персональных данных.

    Напишите нам!

      Свяжитесь с нами, и мы поможем выстроить надежную защиту ваших
      информационных систем

      Согласие на получение последних новостей компании, сообщений рекламного и информационного характера.

      Нажимая «Отправить», вы даете согласие на обработку своих данных согласно политике обработки персональных данных.

      Мы используем куки-файлы, которые помогают сделать сайт удобнее для вас. Подробнее