Недавно появился фаззер What The Fuzz, который (кроме названия) интересен тем, что это: То есть он может исследовать бинарь без исходников на любом интересном участке кода. Например, сам автор фаззера натравил WTF на Ida Pro и нашел там кучу багов. Благодаря подходу с snapshot'ами, WTF умеет работать с самыми тяжелыми приложениями. Ключевые особенности WTF, на которые стоит обратить внимание: Получается, нельзя фаззить ELF? На самом деле, можно. Просто нет инструкции, как сделать snapshot для Linux. Все-таки главная мишень — это программы для Windows. Эта статья появилась из желания обойти это ограничение. 1. Чем WTF лучше AFL
2. Как работает WTF с таргетом для Windows
3. Как сделать снимок через gdb
3.1. Виртуальная память
3.2. Физическая память
3.3 Процессор
4. Пример
4.1. Границы фаззинга symbol-store.json
4.2. Дамп памяти mem.dmp
4.3. Дамп процессора regs.json
4.4. Покрытие stackoverflow.cov
4.5. Фаззер
5. Вывод Прежде всего вспомним, что в blackbox умеет и AFL. Тогда зачем мучиться, когда есть готовое решение?  У WTF есть выгодное отличие — это snapshot-based фаззер, то есть он использует виртуальную машину на основе снимка всей системы с запущенным внутри бинарем в нужном состоянии. Из этого следуют два вывода: А кроме этого, WTF дает полный контроль над процессором и памятью. Представим, что есть функция vuln(void* buf, int size), и в снимке сохранено состояние всей ОС на момент вызова функции в исследуемом процессе. Контроль над процессором и памятью позволяет подменять данные в буффере buf и наблюдать, что будет происходить. Для этого нужно написать фаззер, который по адресу rdi будет засовывать мусор, а в регистр rsi — писать размер этих данных. После чего WTF снимет систему из снимка с паузы и узнает, что изменилось. Раз уж начали, посмотрим тогда, как работать с WTF. Процесс работы выглядит так: Юзер делает очень много работы, а WTF просто создает виртуальную машину через Hyper-V/KVM/Bochscpu на основе дампа и состояния процессора и запускает ее в тех границах и с теми модификациями памяти и процессора, которые определил юзер. Тут, кстати, и становится понятно, что WTF все-таки справится с ELF: WTF можно запустить на Linux, и на борту есть поддержка KVM. Автор ограничился Windows, потому что есть понятный способ сделать снимок системы — через KD. А на Linux есть gdb, можно ли сделать снимок через него? Что потребуется: gdb запустит qemu, а qemu — виртуальную машину. Такая схема и позволит сделать снимок. Чтобы не запутаться, надо помнить, что есть две виртуальные машины — одна (qemu) для того, чтобы сделать снимок, ее создает юзер, и вторая (KVM) — для фаззинга, ее создает WTF. Отладочная информация нужна, чтобы можно было вытягивать информацию из gdb о ядре и процессоре.  От qemu нужно получить значения всех регистров CPU на момент исполнения интересующей инструкции. От ядра — получить структуру task_struct, которая описывает процесс. Эта структура поможет решить проблему с виртуальной памятью.  Не все страницы виртуальной памяти процесса находятся в RAM, часть свопнута на диск. А когда WTF создает свою виртуалку, то там есть только два девайса — CPU и RAM, больше ничего, диска нет. При обращении к отсутствующей странице произойдет исключение, ядро не найдет ее на диске, потому что никакого диска вообще нет, и завершит процесс. При фаззинге это наблюдалось бы в росте таймаутов. Поэтому перед дампом необходимо "прокликать" все страницы всех мэппов, показав ядру, что их стоит держать в RAM. Для этого нужно: Возможно, есть более простой путь. Например, сискол mlockall вроде бы предназначен для этой же задачи, судя по описанию. Но через него не получается решить вопрос даже с нужной capability CAP_IPC_LOCK. Поэтому — цикл. Реализовать подход с циклом позволит кастомный брейк в gdb на адресе call vuln. Обработчик брейка должен будет: Вот таким образом можно добиться, чтобы все страницы виртуальной памяти процесса были в RAM. После этого уже можно делать дамп памяти всей системы. Сохранить физическую память в qemu очень просто — нужно воспользоваться монитором qemu: Однако есть нюанс — WTF будет ожидать дамп в формате dmp — все-таки WTF заточен под Windows — и надо что-то с этим сделать. Формат dmp не очень сложный в контексте WTF, сделать конвертер оказалось простой задачей. Просто несколько захардкоженных значений, единичный битмап размером количество страниц памяти / 8 и дальше уже чистый дамп из qemu. Результатом всех манипуляций будет дамп памяти в формате dmp со всеми нужными для процесса страницами. Остается разобраться с процессором. Состояние процессора легко найти в процессе qemu. Оно описывается через стуктуру CPUState*(поле env_ptr), и переменную такого типа можно найти в функции cpu_exec. Если сделать одноразовый кастомный брейк на этой функции и запомнить CPUState* cpu, то по этому адресу можно будет найти любые регистры в любой момент времени. Неожиданно получилось так, что WTF считает невалидными значения некоторых регистров, а некоторые вообще не видит. Поэтому пришлось пропатчить WTF (коммит e278c942848f2e211904320ff804df4ccb6fd7f8). Функция bool SanitizeCpuState(CpuState_t &CpuState), удалить проверку: Метод bool KvmBackend_t::LoadSregs(const CpuState_t &CpuState), проблема с cs, заменить SEG(cs, Cs) на: В чем именно проблема — неизвестно, но в qemu точно правильные значения, поэтому все под нож. Правки можно не вносить самостоятельно, все есть в этом форке, там же и пример фаззинга ELF. Стоит заметить, что это user-space, поэтому скрипты заточены под это. У WTF такая организация рабочего процесса: Все папки должны быть, это захардкоженные пути. В качестве примера будет бинарь example/stackoverflow. Все необходимые скрипты, бинари, образы — в том же репозитории. Тяжелые файлы лежат отдельно тут: Чего в репозитории нет, так это собранного с отладкой qemu, оно несложно собирается. Как упоминалось выше, необходимо определить границы потока исполнения, чтобы WTF различал, какое поведение нормальное, а какое — аварийное, и где вообще остановить фаззинг. В случае с переполняхой на стеке границы будут такие: Адреса записывают в state/symbol-store.json. Выглядит так: "stop":"0x555555555272", "stack_chk_failed":"0x5555555551ca" Границы очень важны, нужно учесть все варианты. Если этого не сделать, поток убежит, фаззер не остановится до таймаута. Это простой пример, для чего-то большего и границ будут больше: Более того, учитывая, что ядро собирается с оптимизациями, придется экспериментальным путем выяснять актуальные адреса для всех этих функций. Интересный таргет будет того стоить. Понадобятся два инстанса gdb, первый — в режиме удаленной отладки, он запускает qemu, и через второй подключаемся к первому. Удобно сделать это через tmux и эти скрипты: В qemu запускаем бинарь, вводим 123, срабатывает брейк, который готовит виртуальную память. Далее — переход на системный монитор qemu, который вызывается через ctrl+alt+2, и команда pmemsave 0 0xffffffff сохранит дамп в файл raw. Этот скрипт вызовет наколеночный конвертер raw2dmp, который сделает mem.dmp из raw файла. Сейчас виртуальная машина замерла на выбранной инструкции call vuln, осталось сдампить процессор. В первом инстансе gdb жмем ctrl+c, набираем кастомную команду cpu. Эта команда найдет CPUState* структуру в памяти qemu и вытащит все регистры в regs.json. Теперь есть все файлы, которые описывают состояние ОС — regs.json, symbol-store.json, mem.dmp. Можно останавливать qemu, gdb. В качестве инструментации WTF использует относительные адреса базовых блоков бинаря. Их можно получить из Ida с помощью скрипта gen_cov.py: Появится файл stackoverflow.cov, его место — в example/stackoverflow/fuzzer/state. Фаззер можно посмотреть здесь. Ничего сложного. Здесь границы: Тут вставляется новая порция мусора: Неприятная особенность WTF: модуль с фаззером становится частью инструмента. WTF нужно пересобирать каждый раз, когда появляются правки. Чтобы запустить фаззер: Мастер запускает сокет-сервер на выбранном порту, воркеры подлкючаются к нему за новыми тест-кейсами и отправляют статистику по ним обратно мастеру. It works! What The Fuzz — мощный инструмент, и требует от юзера значительных усилий по настройке. Еще больше нужно для настройки фаззинга Linux, потому что WTF по дефолту его не поддерживает. Все равно WTF стоит попробовать в случае изучения тяжелых приложений, ядра или драйверов. Эта статья была о том, как преодолеть ограничение по Linux и что такое snapshot-based подход в фаззинге. Program received signal SIGSEGV