
Фаззер ядра Windows на основе снапшотов с управлением покрытием
Rewind — это фаззер, основанный на снапшотах и управляемый покрытием, нацеленный на компоненты ядра Windows.
Идея заключается в том, чтобы начать со снапшота живой работающей системы. Этот снапшот состоит из страниц физической памяти вместе с состоянием процессора.
Это состояние используется для настройки начального состояния виртуального процессора. Используя подкачку по требованию, из снапшота считываются только те страницы, которые нужны для выполнения целевой функции.
Поскольку мы используем выделенную виртуальную машину только с теми страницами физической памяти, которые полезны для выполнения целевой функции, восстановление снапшота происходит быстро.
На данный момент доступны два бэкенда:
WHVP использует API WHVP (Windows Hypervisor Platform) для
предоставления доступа к разделу Hyper-V. См.
https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform
для более подробной информации.Bochs использует эмулятор Bochs
(https://bochs.sourceforge.io/)Бэкенд KVM находится в разработке и должен появиться в ближайшее время.
Rewind предоставляет 2 основные возможности:
Также предоставляется базовый TUI (Terminal User Interface) для отображения полезной информации о процессе фаззинга.
Протестировано на Windows и Linux (пока только бэкенд bochs для Linux).
Мне всегда нравилось заниматься исследованием уязвимостей ядра, особенно ядра Windows. Процесс всегда включает в себя сочетание статического и динамического анализа. Динамический анализ может быстро стать утомительным. Цикл отладка / крах / перезагрузка / сброс всех точек останова — медленный и мучительный. Когда вы хотите заняться фаззингом, часто требуется настроить одну или несколько виртуальных машин плюс отладчик ядра и написать какие-то "гаражные" скрипты для обработки обнаружения крахов...
Снапшоты с помощью виртуальных машин помогают, но это медленно.
В 2018 году Microsoft представила новый набор API под названием Windows Hypervisor Platform (WHVP). Эти API позволяют настроить раздел (VM в терминологии Hyper-V) с несколькими виртуальными процессорами и получить контроль над VM-exit, происходящими в виртуальной машине. Это почти как иметь собственный обработчик VM-exit в пользовательском режиме. Весьма удобно для полезных вещей, например Simpleator или applepie.
Поэтому я начал играться с WHVP и сделал первый PoC, позволяющий мне выполнять в разделе Hyper-V некий шеллкод. Он был написан на Python и довольно медленный. Этот первый PoC довольно быстро превратился в нечто вроде трассировщика на основе снапшотов. Мне хотелось иметь что-то для инициализации виртуального процессора и достаточно простое в настройке. Поскольку я уже использовал отладчик ядра для работы с моей целью, я решил использовать дампы ядра, сделанные с помощью WinDbg, в качестве снапшота. С этим мне просто нужно было настроить раздел с виртуальным процессором. Контекст виртуального процессора устанавливается из контекста, взятого из дампа. Когда виртуальному процессору требуется физическая страница, я использую страницы из дампа.
С этим я смог разветвить состояние дампа в раздел, а затем возобновить выполнение. Это позволило мне легко трассировать выполнение моей целевой функции. Изменяя аргументы и откатывая состояние памяти раздела, было также очень легко фаззить цель.
Инструмент реализует 2 возможности получения покрытия. Первая использует классический TF (Trap Flag) для генерации прерываний INT1 на каждой инструкции. Это требует модификации цели и работает медленно. Я бы предпочёл использовать MONITOR trap flag. Но WHVP не предоставляет такой возможности.
Чтобы добиться должной производительности (необходимой для фаззинга), я решил снизить точность покрытия и добавить режим, в котором вы знаете только то, когда инструкция выполняется впервые.
Для этого я патчу страницы, полученные из снапшота, байтами 0xcc (только для исполняемых страниц). Когда процессор выполняет эти пропатченные инструкции, гипервизор перехватывает исключение и перезаписывает инструкции исходным кодом.
Это похоже на установку уникальной программной точки останова на каждой инструкции. Это работает в 95% случаев, но в особых фрагментах кода (например, с таблицами переходов) это может не сработать, потому что данные будут заменены.
Чтобы преодолеть это, можно было бы дизассемблировать код перед его отображением и патчить только то, что нужно (может быть, в следующий раз).
Во время моих экспериментов я столкнулся с несколькими ограничениями при использовании WHVP. Он медленный, очень медленный. В исходниках VirtualBox есть интересные комментарии :)
Поэтому для нормальной производительности действительно нужно ограничивать количество VM-exit, и это несовместимо, если вы хотите использовать Hyper-V в качестве трассирующего гипервизора (поскольку это требует большого количества VM-exit).
В то же время я начал использовать bochs (особенно часть с инструментированием), чтобы проверить, корректны ли трассы, полученные инструментом. Bochs был своего рода оракулом для проверки расхождений в трассах.
Bochs быстрее, чем WHVP при полной трассировке, и у вас также есть преимущества в виде доступа к памяти и других полезных вещей.
Я решил добавить bochs в качестве ещё одного бэкенда. whvp больше не было подходящим названием, и я остановился на rewind.

rewind был спроектирован вокруг моего собственного рабочего процесса, когда я провожу
оценки безопасности для драйверов ядра на платформе Windows.
Первый шаг — установить целевое программное обеспечение внутри виртуальной машины. Поскольку я использую сочетание статического и динамического анализа, я также настрою отладчик ядра.
Открыв несколько случайных драйверов в IDA, я быстро начну
нацеливаться на некоторые функции. Для этого я обычно ставлю несколько точек останова с помощью
windbg и в сочетании с ret-sync
я могу начать играть.
Вот здесь в игру вступает rewind. Вместо того чтобы редактировать случайные
буферы в памяти, выполнять одиночный шаг и аннотировать IDB, чтобы получить приблизительное
представление о происходящем, я сниму снапшот с помощью windbg и использую
rewind.
Это значительно упростит процесс. Наличие снапшота даёт множество преимуществ. Всё детерминировано. Вы можете бесконечно воспроизводить вызов функции. Вы можете запустить фаззер, если целевая функция выглядит интересной. Вы даже можете закрыть виртуальную машину, поскольку она больше не нужна.
Очевидно, вам понадобится Rust (установка протестирована на Windows и Linux с Rust 1.50). CMake также требуется некоторыми зависимостями.
Сначала клонируйте репозиторий:
$ git clone [email protected]:quarkslab/rewind.git
Продолжите установкой бэкенда bochs
Склонируйте репозиторий bochscpu (https://github.com/yrp604/bochscpu) в каталог vendor:
$ cd vendor
$ git clone https://github.com/yrp604/bochscpu
Загрузите предварительно собранные артефакты bochs из bochscpu-build (https://github.com/yrp604/bochscpu-build)
$ curl.exe -L --output bochs-x64-win.zip [artifact_url]
Извлеките папки lib и bochs в клонированный bochscpu.
$ Expand-Archive -Path .\bochs-x64-win.zip -DestinationPath .\
$ copy -Recurse .\bochs-x64-win\msvc\* .\bochscpu\
На Windows WHVP также будет собран как бэкенд.
В сеансе PowerShell с правами администратора используйте следующую команду, чтобы проверить, включён ли WHVP:
Get-WindowsOptionalFeature -FeatureName HypervisorPlatform -Online
FeatureName : HypervisorPlatform
DisplayName : Windows Hypervisor Platform
Description : Enables virtualization software to run on the Windows hypervisor
RestartRequired : Possible
State : Enabled
CustomProperties :
Если он не включён, вы можете использовать командлет Set-WindowsOptionalFeature для включения. Также потребуется включить Hyper-V.
Вам также понадобится установленный Windows SDK (10.0.19041.0). Вы можете загрузить его с https://developer.microsoft.com/fr-fr/windows/downloads/windows-10-sdk/.
Вам нужно установить LLVM и задать переменную окружения LIBCLANG_PATH (требуется для bindgen).
См. https://rust-lang.github.io/rust-bindgen/requirements.html для подробного объяснения.
$ $env:LIBCLANG_PATH="C:\Program Files\LLVM\bin"
После этого вы должны суметь собрать rewind (требуется nightly из-за unwind_attributes в крейте bochscpu):
$ cd rewind_cli
$ cargo +nightly build --release
Бинарник rewind будет доступен в каталоге target/release.
Вы также можете использовать cargo для локальной установки:
$ cd rewind_cli
$ cargo +nightly install --path .
> error: failed to run custom build command for `zydis v3.1.1`
whvp-sys не соберётсяБазовое руководство с использованием CVE-2020-17087 приведено в каталоге examples
См. TODO.md
hit трассировщик может некорректно работать с некоторыми функциями (так происходит с некоторыми switch-таблицами). Причина в том, что каждый байт заменяется программными точками останова (включая данные, если они находятся на исполняемой странице). Лучшим способом было бы получить список всех базовых блоков, например, от дизассемблера.Этот инструмент в настоящее время разрабатывается и спонсируется Quarkslab под лицензией Apache 2.0.
Слава @yrp604, @0vercl0k, Alexandre Gazet за их помощь, отзывы и мысли. Также спасибо всем моим коллегам из Quarkslab!