Хакер - JavaScript аль денте. Фаззим JS-движки при помощи Fuzzilli
hacker_freisploitem
Содержание статьи
- Стенд
- JavaScript-движки
- Подготовка фаззера
- Сборка и фаззинг V8
- Теория
- Сборка
- Фаззинг
- Сборка и фаззинг SpiderMonkey
- Теория
- Сборка
- Фаззинг
- Сборка и фаззинг JavaScriptCore
- Теория
- Сборка
- Фаззинг
- Выводы
Сегодня в меню макароны! Точнее, наглядная демонстрация того, как использовать фаззер Fuzzilli, чтобы искать уязвимости в движках JavaScript. Теории будет всего чуть‑чуть, сосредоточимся на практике. Быстренько соберем необходимый инструментарий, а затем приступим к поиску багов при помощи фаззинга.
Раньше фаззить движки JavaScript (те самые, что позволяют делать в браузере падающий снег или разрабатывать бэкенды на Node.js) было сложно. Мутации JS-кода приводили к синтаксическим ошибкам, что серьезно замедляло работу. Семплы отбрасывались движком, и приходилось генерировать новые и новые. На помощь пришли фаззеры на основе грамматики, но их применение тоже не назовешь легким.
В 2019 году исследователь безопасности saelo публично открыл свою разработку — фаззер Fuzzilli. Идея была в том, чтобы вместо JavaScript генерировать подобие байт‑кода, которое будет проще подвергать мутациям. Собственно, хоть в названии и обыгран сорт пасты, происходит оно от FuzzIL — Fuzzing Intermediate Language, промежуточный язык для фаззинга.
СТЕНД
Для стенда нам понадобится виртуальная машина на Linux. Можно скачать готовую виртуалку с сайта osboxes.org, выбрав дистрибутив по вкусу. Я в статье буду использовать Ubuntu 22.
Чем больше ты выделишь виртуалке ресурсов, тем лучше. Фаззер показывает покрытие кода, и в зависимости от мощности машины на весь движок может уйти от одного дня до нескольких недель.
Из инструментов понадобится Git, язык программирования Swift (не путать с певицей), а также весь тулчейн, нужный для сборки JS-движка. Но об этом поговорим чуть позже.
Пока же запускай виртуалку и вводи свой пароль.
Password=osboxes.org
echo $Password | sudo -S apt update
sudo apt upgrade -y
Какие бывают фаззеры
Фаззинг — это такой метод тестирования, при котором в ПО вводят неправильные, неожиданные или рандомизированные данные, а фаззер отслеживает падения, срабатывания встроенных утверждений (assert) и утечки памяти.
Важный параметр в фаззинге — это покрытие кода. По сути это процент задействованного кода программы при выполнении определенного набора тестов.
Фаззинг можно разделить на «тупой», или неструктурированный, и «умный», или структурированный. Когда фаззер ничего не знает о структуре входных данных программы, то это тупой фаззинг. Если знает — умный.
Создание входных данных делится на генерацию и мутацию. Генерация — это когда данные создают полностью с нуля, мутация — когда изменяют имеющиеся.
Еще фаззеры можно разделить на тестирующие методом черного и белого ящика — в зависимости от того, какие у нас есть знания об исходном коде.
Тестирование методом черного ящика означает полное отсутствие данных о структуре программы, в таком случае фаззер создает рандомизированные входные данные.
Фаззинг методом белого ящика подразумевает анализ программы для повышения покрытия кода. Например, символическое исполнение для обхода разных частей программы. Но анализ программы занимает больше времени, чем при фаззинге методом черного ящика.
Еще «ящик» может быть серым. В таком случае мы применяем инструментацию кода вместо анализа программы. Это позволяет получать информацию о программе без анализа. То есть что‑то среднее между белым и черным ящиком. Получается, можно быстро генерировать входные данные, но при этом узнать информацию о покрытии кода.
Фаззер Fuzzilli относится как раз к третьему виду. По типу генерации входных данных он совмещает в себе генерацию и мутацию. По типу фаззинга он скорее «умный».
JavaScript-движки
Основные части движка JavaScript — это парсер, интерпретатор и компилятор.
Все начинается с парсинга исходного кода на JavaScript. Строится абстрактное синтаксическое дерево (AST). На его основе создается байт‑код. Затем интерпретатор выполняет байт‑код.
Во время выполнения записывается разная информация — profiling data. В дальнейшем она используется при компиляции байт‑кода в машинный. Этим занимается компилятор.
Машинный код генерируется в тех случаях, когда какой‑то участок часто используется. Например, функция выполняется в цикле. Тогда выгоднее потратить время на его компиляцию и в дальнейшем выиграть во времени выполнения (ведь интерпретация идет медленнее).
Обычно применяется несколько компиляторов, и выбор происходит в зависимости от уровня оптимизации кода.
WWW
Подробнее о работе движков JS — в презентации «JavaScript engines: The Good Parts» (PDF, WebArchive).
ПОДГОТОВКА ФАЗЗЕРА
Fuzzilli поставляется в виде исходного кода, написанного на языке Swift.
Для скачивания исходников понадобится Git, для сборки Fuzzilli — пакеты GCC, Binutils и, конечно, исходники фаззера. Ставим зависимости и клонируем репозиторий Fuzzilli.
Password=osboxes.org
echo $Password | sudo -S apt update
sudo apt install git binutils gcc -y
git clone https://github.com/googleprojectzero/fuzzilli
Теперь переходим на сайт Swift в раздел Download и ищем релиз для своего дистрибутива. Для Ubuntu 22 качаем релиз Ubuntu 22.04 x86_64.
Распаковываем архив и копируем папку usr
, чтобы установить Swift. После этого убеждаемся, что все корректно настроено.
Вот мини‑скрипт для ленивых. Если читаешь эту статью спустя много лет, поменяй переменные SwiftUrl
на соответствующий URL со страницы Swift.
# Установка Swift
Password=osboxes.org
SwiftUrl=https://download.swift.org/swift-5.8.1-release/ubuntu2204/swift-5.8.1-RELEASE/swift-5.8.1-RELEASE-ubuntu22.04.tar.gz
SwiftTar=$(echo $SwiftUrl | sed 's:.*/::')
SwiftFolder=${SwiftTar%.tar.gz}
# Переходим домой
cd $HOME
# Качаем архив
wget $SwiftUrl
# Извлекаем
tar -xzf $SwiftTar
# Устанавливаем
echo $Password | sudo -S cp -r $SwiftFolder/usr /
# Удаляем архив
rm $SwiftTar
# Удаляем папку
rm -rf $SwiftFolder
# Тестовый запуск
swift --version
Теперь мы готовы к сборке фаззера. Переходим в папку Fuzzilli и запускаем сборку.
cd fuzzilli && swift build -c release
Фаззер готов. Можно почитать раздел помощи, если есть желание.
swift run -c release FuzzilliCli --help
Переходим к подготовке JS-движков. На главной странице репозитория инструкция гласит: «Скачайте исходный код движка. Скомпилируйте его, как описано в инструкции к нему в папке Targets». Для каждого движка там есть отдельная папка, в которой указано, как собрать движок для фаззинга.
СБОРКА И ФАЗЗИНГ V8
Теория
Начнем с движка браузера Google Chrome, он называется V8. Это движок JavaScript и WebAssembly, разработанный в Google, распространяется с открытым исходным кодом, написан на C++. Используется в Chrome, Node.js и множестве дериватив Chrome.
Разработали этот движок в датском городе Орхус, а ведущего разработчика зовут Ларс Бак. Бак занимался разработкой языка Self, а также HotSpot — виртуальной машины Java. Поэтому многое из наработок Self перекочевало в V8. Например, та же JIT-компиляция или «карты» объектов (maps).
WWW
Подробнее — в научной работе, которая легла в основу Self: «An Efficient Implementation of SELF, a Dynamically-Typed Object-Oriented Language Based on Prototypes» (PDF, WebArchive).
Движок состоит из интерпретатора Ignition, неоптимизирующего компилятора Sparkplug и оптимизирующего компилятора TurboFan.
Сборка
В папке Targets/V8 нам предлагают следовать инструкциям с сайта. Но я не буду утомлять ими, а скомпилирую все в один мини‑скрипт.
# Подготовка и сборка V8
Password=osboxes.org
echo $Password | sudo -S apt update
# Переходим домой
cd $HOME
# Качаем репозиторий depot_tools
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# Добавляем в PATH
echo "PATH=$HOME/depot_tools:$PATH" >> ~/.bashrc
source ~/.bashrc
# Качаем исходники
gclient
fetch v8
cd v8/
gclient sync
# Ставим зависимости (update — так как скрипт попросит ввести пароль)
echo $Password | sudo -S apt update
./build/install-build-deps.sh
# Билдим при помощи скрипта из папки фаззера
$HOME/fuzzilli/Targets/V8/fuzzbuild.sh
Скрипт переходит в домашний каталог пользователя, затем ставит depot_tools
(для скачивания и сборки V8), качает и компилирует исходный код движка V8. Сборка займет какое‑то время, зависит от ресурсов машины. Можешь пока сделать небольшую разминку.
Фаззинг
Приступаем наконец к самому фаззингу. Для этого нужно выполнить следующую команду:
# Запускаем Fuzzilli
# Отключаем дампы
echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'
# Переходим в папку Fuzzilli
cd $HOME/fuzzilli
# Запускаем
swift run -c release FuzzilliCli --profile=v8 --resume --storagePath=$HOME/fuzzilli-storage-v8 $HOME/v8/out/fuzzbuild/d8
Основные настройки — это профиль движка (--profile=v8
), возврат к предыдущей сессии фаззинга (--resume
) и хранилище, куда фаззер будет сохранять свои данные (--storagePath
). Там будут храниться найденные краши, корпус семплов и прочая информация.
Периодически он будет выводить статистику фаззинга. В основном интересны количество найденных крашей (Crashes Found) и процент покрытия кода (Coverage).
СБОРКА И ФАЗЗИНГ SPIDERMONKEY
Теория
Переходим к JavaScript-двиглу Firefox.
SpiderMonkey — это потомок первого в мире движка JavaScript. Его релиз состоялся аж в 1995 году! Изначально он разработан Бренданом Айком в компании Netscape. Первые версии были написаны на C, но в дальнейшем код переписали на C++.
Вот структура SpiderMonkey. Парсер производит байт‑код. Интерпретатор JavaScript этот байт‑код выполняет. Baseline-интерпретатор занимается созданием инлайнового кеша. Baseline-компилятор создает неоптимизированный машинный код. WarpMonkey — оптимизированный машинный код.
Сборка
В инструкции пишут, что нужно просто клонировать репозиторий Gecko и запустить fuzzbuild.
Но на самом деле надо применить патчи из папки Patches. И только после этого билдить. К тому же запускать fuzzbuild нужно не из js/src
, а из рута gecko-dev
.
Что ж, приступаем. Нам понадобится curl и компилятор Rust для сборки движка.
# Подготовка и сборка SpiderMonkey
Password=osboxes.org
cd $HOME
# Ставим curl
echo $Password | sudo -S apt install curl -y
# Ставим Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o install.sh
sh ./install.sh -y
source "$HOME/.cargo/env"
# Клонируем
git clone https://github.com/mozilla/gecko-dev.git
# Заходим
cd gecko-dev/js/src
# Патчим
git apply $HOME/fuzzilli/Targets/Spidermonkey/Patches/*
cd $HOME/gecko-dev
# Собираем
$HOME/fuzzilli/Targets/Spidermonkey/fuzzbuild.sh
Фаззинг
После того как движок собран, можем запустить фаззинг из папки fuzzilli
. Команды запуска и параметры фаззера те же, кроме профиля и исполняемого файла.
# Запуск Fuzzilli
# Отключаем дампы
echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'
# Переходим в папку Fuzzilli
cd $HOME/fuzzilli
# Запускаем
swift run -c release FuzzilliCli --profile=spidermonkey --resume --storagePath=$HOME/fuzzilli-storage-sm $HOME/gecko-dev/obj-fuzzbuild/dist/bin/js
СБОРКА И ФАЗЗИНГ JAVASCRIPTCORE
Ну и наконец, JavaScriptCore — JavaScript-движок браузера Safari.
Теория
Вообще, можно сказать, что это потомок JavaScript-движка KJS из браузера Konqueror, входящего в KDE. Проект WebKit стартовал в 2001 году как форк от KHTML и KJS.
Пайплайн у него самый сложный, четырехуровневый.
- LLint или Low Level Interpreter — это просто интерпретатор байт‑кода, сгенерированного из исходного кода JavaScript.
- Далее идет немного оптимизирующий компилятор Baseline. Оба они собирают информацию, необходимую для дальнейших оптимизаций машинного кода.
- Следующий компонент — DFG JIT (Data Flow Graph Just In Time). Он отвечает за повышение оптимизации машинного кода.
- Ну и FTL JIT (Faster Than Light), генерирует наиболее оптимизированный машинный код.
Сборка
Смотрим, что пишут в Targets по поводу JavaScriptCore.
Нужно клонировать код из репозитория WebKit, накатить патчи и запустить fuzzbuild.sh
. Для сборки потребуется установить Clang и зависимости. В папке Tools
есть готовые скрипты для этого. Качаем, ставим все необходимое, патчим, билдим. Как обычно, вот скрипт:
# Подготовка и сборка JavaScriptCore
Password=osboxes.org
cd $HOME
# Качаем исходный код
git clone https://github.com/WebKit/WebKit.git
# Ставим зависимости
cd WebKit
echo $Password | sudo -S apt update
sudo apt install clang -y
Tools/gtk/install-dependencies
# Патчим
git apply ../fuzzilli/Targets/JavaScriptCore/Patches/*
# Билдим
$HOME/fuzzilli/Targets/JavaScriptCore/fuzzbuild.sh
Фаззинг
Когда сборка движка будет закончена, можем запускать фаззинг. Поехали!
# Запуск Fuzzilli
# Отключаем дампы
echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'
# Переходим в папку Fuzzilli
cd $HOME/fuzzilli
# Запускаем
swift run -c release FuzzilliCli --profile=jsc --resume --storagePath=$HOME/fuzzilli-storage-jsc $HOME/WebKit/FuzzBuild/Debug/bin/jsc
Все работает. Осталось дождаться интересных крашей!
ВЫВОДЫ
Я постарался рассказать всю необходимую теорию о фаззинге, а также мы подготовили платформу для фаззинга трех основных движков JavaScript. Однако Fuzzilli умеет работать и с другими движками (достаточно заглянуть в папку Targets):
- JerryScript;
- QuickJS;
- Qt QJSEngine;
- XS;
- duktape.
Их можешь попробовать пофаззить самостоятельно.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei