Автор swagcat228
Статья написана для Конкурса статей #10
В эпоху информационного общества многие привычные понятия трансформируются, приобретают новую материальную форму - это неизбежное влияние эволюции.
Бытует мнение, что одним из основопологающих принципов новой эпохи является трансформация из количества в качество.
Это весьма резонно, ведь информации становится все больше и больше. Технологический прогресс ускоряется в геометрической прогрессии - а значит времени для усвоения новой информации становится все меньше и меньше.
А из этого следует не хитрый вывод, что дальнейшая судьба человека, общества, нации будет весьма сильно зависить от способности взаимодействия с информационным полем.
Мы не есть исключение. В институтах ученые, академики, доценты и аспиранты пишут научные труды, делятся ими с общественностью и так далее.
То, во что рано или поздно, частично или полностью, трансформируется вышеописанное - это информациооные ресурсы профильных назначений. Например Дамага.
К сожалению, люди забывают о безопасности. Забывают о ней что на уровне человека, что на уровне государства, что на уровне нашего с вами конкурса статей... =)
А жаль.
Введение
Я люблю фаззинг.
Можно смотреть вечно на три вещи: как горит огонь, как течет вода, и как AFL++ собирает карту покрытия =)
В контексте AFL++ режим NYX - это режим фаззинга на уровне гипервизора, который использует аппаратные возможности процессоров intel (а именно - intel_pt) для сбора покрытия базовых блоков.
То есть, если раньше мы использовали либо статическую инструментацию (на этапе компиляции), либо динамическую (на этапе выполнения), то тут инструментация не выполняется вообще.
Точнее в ней нету необходимости, т.к. аппаратные возможности процессора позволяют получить информацию о выполнении процессором инструкций ветвления (так называемые углы (edges)).
Это дает огромное преимущество в производительности.
Кроме того, используется технология отката снапшота к первичному состоянию. Это дает сразу два весомых преимущества.
Во первых - благодоря dirty pages - перезаписываются только те страницы памяти, которые были модифицированы в процессе итерации. То есть перезагружаются только нужные "пиксели", а не все "изображение".
Во вторых - благодоря откату всей системы включая ядро - фаззинг получается 100% детерменированным.
Но есть и ложка дегтя в бочке меда. А именно - так называемые roadblocks (подводные камни).
Смысл в том, что в ходе фаззинга в исследуемом приложении существуют логические числовых и строковых значений, констант, либо динамически сгенерированных.
Константы, теоретически, можно извлечь из приложения и создать словарь для фаззинга. Во всяком случае строчные. С числовыми будет уже сложнее, но тоже можно.
Однако, для динамических паттернов эта техника не подойдет. Прийдется реверсить приложение и патчить те места, которые требуют подобных значений.
Существует технология -> RedQueen <-
Это технология, простая как двери, которая позволяет в процессе выполнения программы инструментировать отдельные мнемоники, отвечающие за интересующие нас операции, и получить в итоге динамически созданный словарь для фаззинга.
Часть первая - анализируем существующее.
И так, начнем мы, пожалуй, с того, что уже есть.
Те, кто читал мои статьи раньше, знет о существовании cmplog. Кто не знает - это имплементация RedQueen для AFL++.
Для начала откроем файл https://github.com/AFLplusplus/qemu...f35b1bf6b4b/target/i386/tcg/translate.c#L1449
Это то, как инструментируются мнемоники сравнения выраженные через SUB:
Как мы видим они вызывают функцию afl_gen_compcov(), передают туда адрес инструкции, аргументы и размер операнда.
github.com
Адрес инструкции хешируется, и в зависимости от размера операнда вызывается нужная вспомогательная функция, которая уже и выполняет взаимодействие с картой cmplog.
github.com
Обратите внимание на поле shape в структуре headers. Она определена в файле
github.com
То есть это просто размер паттерна минус один.
Например для токена размером 1 байт (8 бит) shape будет равно 0.
Для токена в 2 байта - единице.
В 4 байта - трем.
Для 8 байт - семи.
Ну и наконец для строки длинной в 31 байт он будет равен 30.
Теперь посмотрим как инструментируются инструкции класса CALL.
github.com
github.com
Как видим, тут напрямую вызывается вспомогательная функция.
github.com
Внутри нее все тоже самое, хешируется указатель инструкции, в зависимости от архитектуры (и соглашения вызовов) добываются аргументы и их содержимое копируется в карту cmplog.
И так, это то, как работала старая реализация внутри qemu которая инструментировала программы динамечески.
Теперь же взлянем на то, как работает современная реализация в QEMU-NYX.
github.com
Для операций сравнения у нас появилась еще и мнемоника ADD.
Не используемые в моем случае соглашения о вызовах я закоментировал.
Забегая вперед, вот так выглядит у меня функция test_strcmp().
В оригинале она выглядит чуть иначе:
github.com
Разница не велика, но весома.
Вот так вот https://github.com/nyx-fuzz/QEMU-Ny...e912a2809fcba58e67df23dfd/nyx/redqueen.c#L872
выглядит инструментация для LEA.
github.com
для ADD
github.com
для CMP.
Окей, с бэк-эндом разробрались, теперь займемся фронт-эндом.
На стороне AFL++ в случае выставления флага
Точнее происходит это вот тут вот: https://github.com/AFLplusplus/AFLp...0bf15b67e8d972da601bb74b/src/afl-fuzz.c#L2550
как видим - в случае cmplog структура fsrv просто дублируется. после чего поднимается новый инстанс.
Обратите внимание, что первым делом задается общий указатель на карту покрытия.
То есть карта покрытия будет единой на оба инстанса.
Для нас это важный момент, так как у нас, в режиме NYX, будет один инстанс.
Тут следует упомянуть, что в AFL++ вся информация хранится в структуре afl_state.
github.com
В ней есть два объекта
Внутри струкруты форксервера уже хранятся свои данные нужные инстансу.
Нужно еще разобраться как же происходит коммуникация между софтом. Для этого используется общий сегмент памяти shm.
На стороне AFL++
github.com
либо
github.com
в зависимости от реализации shm.
И что еще нужно знать про cmplog на стороне AFL++ так это то, как он запускается.
github.com
Функция
Её логика проста как угол дома - запись ввода и запуск итерации.
На стороне QEMU инициализация общего сегмента памяти выполняется вот так: https://github.com/AFLplusplus/qemu...9b3b701bf35b1bf6b4b/accel/tcg/cpu-exec.c#L354
И так, мы понимаем как работает фронтенд и бекенд для cmplog.
Теперь разберемся как работает NYX режим. На стороне AFL++ происходит все тоже самое что и для обычного фаззинга в режиме QEMU, только в качестве executor-a используется функция из библиотеки libnyx.
Библиотека libnyx - это дополнительний слой абстракции, между фронтом и бэком, предоставляющий унифицированный интерфейс.
Создан он, по всей видимости, для удобства. С его помощью можно построить свой фаззер просто вызывая нужные функции.
Давайте рассмотрим как его использование реализовано в AFL++
Сперва заглянем в файл заголовков: https://github.com/AFLplusplus/AFLp...5b67e8d972da601bb74b/include/forkserver.h#L62
В данном заголовке существует структура nyx_plugin_handler_t, которая описывает существующий на данный момент набор апи, который предоставляет интерфейс.
Я выделил функцию, которую нету в оригинальной версии. Ее мы добавим во второй части.
Инициализируется данная структура в функции afl_load_libnyx_plugin()
github.com
Опять таки, забегая вперед, на скриншоте видно, как я добавил новое апи
Сама же библиотека написана на rust. И нужное нам апи в ней есть:
github.com
Однако, не экспортируется:
github.com
В файле экспортов функция
Часть вторая - портируем код.
При правильной планировке задач страдания разработчика сводятся к минимуму.
Я всегда вспоминаю ту буханку хлеба, из которой сделали троллейбус =)
И так, нам нужно перенести движёк cmplog из кему в кему, доработать libnyx, доработать AFL++ для взаимодействия с доработанной libnyx, доработать сам AFL++ для поддержки фаззигна в режиме CMPLOG + QEMU_NYX.
В первой части мы рассмотрели как инициализируется сегменг общей памяти в AFL++, помним как он подгружается на стороне QEMU.
Нам нужно найти место в QEMU_NYX где бы мы могли аккуратно разместить код инициализации shm сегмента, а так же двух хендлеров - строковых и цифровых. Еще нам потребуется функция для хеширования адреса.
Быстро прогрепав исходники по паттерну redqueen я нашел подходящего кандидата:
github.com
Тут можно разместить функцию, которая будет инициализировать shm. Но сперва ее нужно портировать. У меня это получилось вот так:
для файла nyx/redqueen.c
после чего я просто добавил
Далее я дословно переписал cmplog_cmp()
Хотя этот switch стоило бы заменить на изьящьную
так же дословно был переписан и cmplog_rtn().
Теперь нужно добавить их в те места, где это необходимо:
Ну и так же точно для остальных сравнений. Для перехвата аргументов вызова добавленную функцию cmplog_rtn() я засветил еще в первой части.
Далее у нас по плану было
Что касается libnyx - все что нам нужно - это экспортировать функцию которая устанавливает булево значение для redqueen.
Сделать это совершенно не трудно, мы сделаем по аналогии уже экспортируемой функции:
нужно будет пересобрать библиотеку
Теперь переходим к AFL++.
Сперва добавим код, который будет импортировать только что экспортированное апи.
Хотя, мы это уже сделали - я показал в первой части, как мы добавили указатель на функцию и добавиль код который инициализирует потом этот указатель.
Выходит, что нам осталось только доработать сам AFL++ что бы он мог работать с CMPLOG в режиме QEMU-NYX.
Ну сперва необходимо вспомнить как инициализируется cmplog форксервер. И адаптировать его под наш случай.
github.com
Просто вместо дублирования fsrv присваиваем указатель.
Ниже по коду обрамляем логику перезапуска форксервера, так как в старом режиме у нас было 2 отдельных процесса, а теперь будет один.
Вроде как все. Теперь нужно разобраться с запуском cmplog итерации.
Мы помним то место, куда стекают вызовы cmplog. Перепишем его следующим образом:
У вдумчивого читателя обязательно должен был возникнуть вопрос почему мы запускаем две итерации вместо одной.
Дело в том, что при выполнении в режиме redqueen покрытие не собирается посредством intel_pt. На сколько я могу предположить сделано это умышленно, дабы не было побочных результатов трассировки.
Хотя с другой стороны их и так не должно быть, т.к. апи intel_pt отличает контексты выполнения (регистр cr3).
RedQueen же инструментирует благодоря обычным программным точкам останова. Я не стал проверять.
Важно уточнить, что сперва мы собираем карту cmplog, а после собираем карту покрытия с тем же вводом, так как перед запуском карта покрытия обнуляется, и в обратной последовательности карты покрытия не будет.
А ну и на последок:
Это нужно что бы можно было изменять максимальную длинну ввода в режиме NYX.
Часть третья - тестирование.
У меня есть специально обученная программулина, которая позволяет тестировать фаззеры.
Вот ее код:
я скомпилировал ее gcc x/tsrv.c -o x/tsrv -ggdb3
и запускаю фаззинг.
Разумеется, у меня уже скомпилированно и загруженно кастомное ядро с KVM-PT.
Почти 4к запусков в секунду. Это даже больше, чем можно получить просто дергая системный вызов fork() =)
Результаты работы cmplog будут появляться в дириктории
Результаты просто отличные! Абсолютно автоматически мы достали из памяти все необходимое.
Часть четвертая - полученный набор патчей
Для кему:
для libnyx:
Для AFL++
Часть пятая - эпилог
Дорогие форумчане. С наступающим новым годом. Желаю что бы в новом году нашлось, наконец-то, место для безопасности в нашем нелёгком деле.
Что бы она восстала из мертвых, и расправила крылья, как когда-то давно.
Ведь еще со времен phrack все знают, что хакер это админ умноженный на минус один.
С уважением, Кот.
Статья написана для Конкурса статей #10
Привет, Дамага!
Мотивационная часть
В эпоху информационного общества многие привычные понятия трансформируются, приобретают новую материальную форму - это неизбежное влияние эволюции.
Бытует мнение, что одним из основопологающих принципов новой эпохи является трансформация из количества в качество.
Это весьма резонно, ведь информации становится все больше и больше. Технологический прогресс ускоряется в геометрической прогрессии - а значит времени для усвоения новой информации становится все меньше и меньше.
А из этого следует не хитрый вывод, что дальнейшая судьба человека, общества, нации будет весьма сильно зависить от способности взаимодействия с информационным полем.
Мы не есть исключение. В институтах ученые, академики, доценты и аспиранты пишут научные труды, делятся ими с общественностью и так далее.
То, во что рано или поздно, частично или полностью, трансформируется вышеописанное - это информациооные ресурсы профильных назначений. Например Дамага.
К сожалению, люди забывают о безопасности. Забывают о ней что на уровне человека, что на уровне государства, что на уровне нашего с вами конкурса статей... =)
А жаль.
Введение
Я люблю фаззинг.
Можно смотреть вечно на три вещи: как горит огонь, как течет вода, и как AFL++ собирает карту покрытия =)
В контексте AFL++ режим NYX - это режим фаззинга на уровне гипервизора, который использует аппаратные возможности процессоров intel (а именно - intel_pt) для сбора покрытия базовых блоков.
То есть, если раньше мы использовали либо статическую инструментацию (на этапе компиляции), либо динамическую (на этапе выполнения), то тут инструментация не выполняется вообще.
Точнее в ней нету необходимости, т.к. аппаратные возможности процессора позволяют получить информацию о выполнении процессором инструкций ветвления (так называемые углы (edges)).
Это дает огромное преимущество в производительности.
Кроме того, используется технология отката снапшота к первичному состоянию. Это дает сразу два весомых преимущества.
Во первых - благодоря dirty pages - перезаписываются только те страницы памяти, которые были модифицированы в процессе итерации. То есть перезагружаются только нужные "пиксели", а не все "изображение".
Во вторых - благодоря откату всей системы включая ядро - фаззинг получается 100% детерменированным.
Но есть и ложка дегтя в бочке меда. А именно - так называемые roadblocks (подводные камни).
Смысл в том, что в ходе фаззинга в исследуемом приложении существуют логические числовых и строковых значений, констант, либо динамически сгенерированных.
Константы, теоретически, можно извлечь из приложения и создать словарь для фаззинга. Во всяком случае строчные. С числовыми будет уже сложнее, но тоже можно.
Однако, для динамических паттернов эта техника не подойдет. Прийдется реверсить приложение и патчить те места, которые требуют подобных значений.
Существует технология -> RedQueen <-
Это технология, простая как двери, которая позволяет в процессе выполнения программы инструментировать отдельные мнемоники, отвечающие за интересующие нас операции, и получить в итоге динамически созданный словарь для фаззинга.
Часть первая - анализируем существующее.
И так, начнем мы, пожалуй, с того, что уже есть.
Те, кто читал мои статьи раньше, знет о существовании cmplog. Кто не знает - это имплементация RedQueen для AFL++.
Для начала откроем файл https://github.com/AFLplusplus/qemu...f35b1bf6b4b/target/i386/tcg/translate.c#L1449
Это то, как инструментируются мнемоники сравнения выраженные через SUB:
Как мы видим они вызывают функцию afl_gen_compcov(), передают туда адрес инструкции, аргументы и размер операнда.
qemuafl/qemuafl/cpu-translate.h at 4d837f06d5c1b6a93e9e89b3b701bf35b1bf6b4b · AFLplusplus/qemuafl
This fork of QEMU enables fuzzing userspace ELF binaries under AFL++. - AFLplusplus/qemuafl
Адрес инструкции хешируется, и в зависимости от размера операнда вызывается нужная вспомогательная функция, которая уже и выполняет взаимодействие с картой cmplog.
qemuafl/accel/tcg/tcg-runtime.c at 4d837f06d5c1b6a93e9e89b3b701bf35b1bf6b4b · AFLplusplus/qemuafl
This fork of QEMU enables fuzzing userspace ELF binaries under AFL++. - AFLplusplus/qemuafl
Обратите внимание на поле shape в структуре headers. Она определена в файле
AFLplusplus/include/cmplog.h at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
То есть это просто размер паттерна минус один.
Например для токена размером 1 байт (8 бит) shape будет равно 0.
Для токена в 2 байта - единице.
В 4 байта - трем.
Для 8 байт - семи.
Ну и наконец для строки длинной в 31 байт он будет равен 30.
Теперь посмотрим как инструментируются инструкции класса CALL.
qemuafl/target/i386/tcg/translate.c at 4d837f06d5c1b6a93e9e89b3b701bf35b1bf6b4b · AFLplusplus/qemuafl
This fork of QEMU enables fuzzing userspace ELF binaries under AFL++. - AFLplusplus/qemuafl
qemuafl/target/i386/tcg/translate.c at 4d837f06d5c1b6a93e9e89b3b701bf35b1bf6b4b · AFLplusplus/qemuafl
This fork of QEMU enables fuzzing userspace ELF binaries under AFL++. - AFLplusplus/qemuafl
Как видим, тут напрямую вызывается вспомогательная функция.
qemuafl/accel/tcg/tcg-runtime.c at 4d837f06d5c1b6a93e9e89b3b701bf35b1bf6b4b · AFLplusplus/qemuafl
This fork of QEMU enables fuzzing userspace ELF binaries under AFL++. - AFLplusplus/qemuafl
Внутри нее все тоже самое, хешируется указатель инструкции, в зависимости от архитектуры (и соглашения вызовов) добываются аргументы и их содержимое копируется в карту cmplog.
И так, это то, как работала старая реализация внутри qemu которая инструментировала программы динамечески.
Теперь же взлянем на то, как работает современная реализация в QEMU-NYX.
QEMU-Nyx/nyx/redqueen.c at ff1c89732115274e912a2809fcba58e67df23dfd · nyx-fuzz/QEMU-Nyx
Contribute to nyx-fuzz/QEMU-Nyx development by creating an account on GitHub.
Для операций сравнения у нас появилась еще и мнемоника ADD.
Не используемые в моем случае соглашения о вызовах я закоментировал.
Забегая вперед, вот так выглядит у меня функция test_strcmp().
В оригинале она выглядит чуть иначе:
QEMU-Nyx/nyx/redqueen.c at ff1c89732115274e912a2809fcba58e67df23dfd · nyx-fuzz/QEMU-Nyx
Contribute to nyx-fuzz/QEMU-Nyx development by creating an account on GitHub.
Разница не велика, но весома.
Вот так вот https://github.com/nyx-fuzz/QEMU-Ny...e912a2809fcba58e67df23dfd/nyx/redqueen.c#L872
выглядит инструментация для LEA.
QEMU-Nyx/nyx/redqueen.c at ff1c89732115274e912a2809fcba58e67df23dfd · nyx-fuzz/QEMU-Nyx
Contribute to nyx-fuzz/QEMU-Nyx development by creating an account on GitHub.
QEMU-Nyx/nyx/redqueen.c at ff1c89732115274e912a2809fcba58e67df23dfd · nyx-fuzz/QEMU-Nyx
Contribute to nyx-fuzz/QEMU-Nyx development by creating an account on GitHub.
Окей, с бэк-эндом разробрались, теперь займемся фронт-эндом.
На стороне AFL++ в случае выставления флага
-c при запуске - фаззер создает новый экземпляр форксервера (новый процесс, и новую структуру в памяти) и переключается между основным и cmplog в процессе работы.Точнее происходит это вот тут вот: https://github.com/AFLplusplus/AFLp...0bf15b67e8d972da601bb74b/src/afl-fuzz.c#L2550
как видим - в случае cmplog структура fsrv просто дублируется. после чего поднимается новый инстанс.
Обратите внимание, что первым делом задается общий указатель на карту покрытия.
afl->cmplog_fsrv.trace_bits = afl->fsrv.trace_bits;То есть карта покрытия будет единой на оба инстанса.
Для нас это важный момент, так как у нас, в режиме NYX, будет один инстанс.
Тут следует упомянуть, что в AFL++ вся информация хранится в структуре afl_state.
AFLplusplus/include/afl-fuzz.h at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
В ней есть два объекта
afl_forkserver_t. Основной и для cmplog.Внутри струкруты форксервера уже хранятся свои данные нужные инстансу.
Нужно еще разобраться как же происходит коммуникация между софтом. Для этого используется общий сегмент памяти shm.
На стороне AFL++
AFLplusplus/src/afl-sharedmem.c at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
AFLplusplus/src/afl-sharedmem.c at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
И что еще нужно знать про cmplog на стороне AFL++ так это то, как он запускается.
AFLplusplus/src/afl-fuzz-cmplog.c at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
Функция
common_fuzz_cmplog_stuff() вызывается всякий раз когда нам нужно заполнить карту cmplog значениями специфичными для интересующего нас инпута.Её логика проста как угол дома - запись ввода и запуск итерации.
На стороне QEMU инициализация общего сегмента памяти выполняется вот так: https://github.com/AFLplusplus/qemu...9b3b701bf35b1bf6b4b/accel/tcg/cpu-exec.c#L354
И так, мы понимаем как работает фронтенд и бекенд для cmplog.
Теперь разберемся как работает NYX режим. На стороне AFL++ происходит все тоже самое что и для обычного фаззинга в режиме QEMU, только в качестве executor-a используется функция из библиотеки libnyx.
Библиотека libnyx - это дополнительний слой абстракции, между фронтом и бэком, предоставляющий унифицированный интерфейс.
Создан он, по всей видимости, для удобства. С его помощью можно построить свой фаззер просто вызывая нужные функции.
Давайте рассмотрим как его использование реализовано в AFL++
Сперва заглянем в файл заголовков: https://github.com/AFLplusplus/AFLp...5b67e8d972da601bb74b/include/forkserver.h#L62
В данном заголовке существует структура nyx_plugin_handler_t, которая описывает существующий на данный момент набор апи, который предоставляет интерфейс.
Я выделил функцию, которую нету в оригинальной версии. Ее мы добавим во второй части.
Инициализируется данная структура в функции afl_load_libnyx_plugin()
AFLplusplus/src/afl-forkserver.c at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
Опять таки, забегая вперед, на скриншоте видно, как я добавил новое апи
nyx_option_set_redqueen_mode.Сама же библиотека написана на rust. И нужное нам апи в ней есть:
libnyx/libnyx/src/lib.rs at ea6ceb994ab975b81aea0daaf64b92a3066c1e8d · nyx-fuzz/libnyx
Contribute to nyx-fuzz/libnyx development by creating an account on GitHub.
Однако, не экспортируется:
libnyx/libnyx/src/ffi.rs at ea6ceb994ab975b81aea0daaf64b92a3066c1e8d · nyx-fuzz/libnyx
Contribute to nyx-fuzz/libnyx development by creating an account on GitHub.
option_set_redqueen_mode не описана.Часть вторая - портируем код.
При правильной планировке задач страдания разработчика сводятся к минимуму.
Я всегда вспоминаю ту буханку хлеба, из которой сделали троллейбус =)
И так, нам нужно перенести движёк cmplog из кему в кему, доработать libnyx, доработать AFL++ для взаимодействия с доработанной libnyx, доработать сам AFL++ для поддержки фаззигна в режиме CMPLOG + QEMU_NYX.
В первой части мы рассмотрели как инициализируется сегменг общей памяти в AFL++, помним как он подгружается на стороне QEMU.
Нам нужно найти место в QEMU_NYX где бы мы могли аккуратно разместить код инициализации shm сегмента, а так же двух хендлеров - строковых и цифровых. Еще нам потребуется функция для хеширования адреса.
Быстро прогрепав исходники по паттерну redqueen я нашел подходящего кандидата:
QEMU-Nyx/nyx/interface.c at ff1c89732115274e912a2809fcba58e67df23dfd · nyx-fuzz/QEMU-Nyx
Contribute to nyx-fuzz/QEMU-Nyx development by creating an account on GitHub.
для файла nyx/redqueen.c
после чего я просто добавил
setup_cmplog_map() перед вызовом init_redqueen_state(); Далее я дословно переписал cmplog_cmp()
Хотя этот switch стоило бы заменить на изьящьную
v = size/8 - 1;
так же дословно был переписан и cmplog_rtn().
Теперь нужно добавить их в те места, где это необходимо:
Ну и так же точно для остальных сравнений. Для перехвата аргументов вызова добавленную функцию cmplog_rtn() я засветил еще в первой части.
Далее у нас по плану было
доработать libnyx, доработать AFL++ для взаимодействия с доработанной libnyxЧто касается libnyx - все что нам нужно - это экспортировать функцию которая устанавливает булево значение для redqueen.
Сделать это совершенно не трудно, мы сделаем по аналогии уже экспортируемой функции:
нужно будет пересобрать библиотеку
cargo build и скопировать обновленную либу в директорию AFL++.(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus/nyx_mode/libnyx/libnyx} #_ cp target/debug/liblibnyx.so ../../../libnyx.so Теперь переходим к AFL++.
Сперва добавим код, который будет импортировать только что экспортированное апи.
Хотя, мы это уже сделали - я показал в первой части, как мы добавили указатель на функцию и добавиль код который инициализирует потом этот указатель.
Выходит, что нам осталось только доработать сам AFL++ что бы он мог работать с CMPLOG в режиме QEMU-NYX.
Ну сперва необходимо вспомнить как инициализируется cmplog форксервер. И адаптировать его под наш случай.
AFLplusplus/src/afl-fuzz.c at 85e14cf8d137d0390bf15b67e8d972da601bb74b · AFLplusplus/AFLplusplus
The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...
Просто вместо дублирования fsrv присваиваем указатель.
Ниже по коду обрамляем логику перезапуска форксервера, так как в старом режиме у нас было 2 отдельных процесса, а теперь будет один.
Вроде как все. Теперь нужно разобраться с запуском cmplog итерации.
Мы помним то место, куда стекают вызовы cmplog. Перепишем его следующим образом:
У вдумчивого читателя обязательно должен был возникнуть вопрос почему мы запускаем две итерации вместо одной.
Дело в том, что при выполнении в режиме redqueen покрытие не собирается посредством intel_pt. На сколько я могу предположить сделано это умышленно, дабы не было побочных результатов трассировки.
Хотя с другой стороны их и так не должно быть, т.к. апи intel_pt отличает контексты выполнения (регистр cr3).
RedQueen же инструментирует благодоря обычным программным точкам останова. Я не стал проверять.
Важно уточнить, что сперва мы собираем карту cmplog, а после собираем карту покрытия с тем же вводом, так как перед запуском карта покрытия обнуляется, и в обратной последовательности карты покрытия не будет.
А ну и на последок:
Это нужно что бы можно было изменять максимальную длинну ввода в режиме NYX.
Часть третья - тестирование.
У меня есть специально обученная программулина, которая позволяет тестировать фаззеры.
Вот ее код:
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
#include <arpa/inet.h>
void LOG(const char *msg);
char *log_name = NULL; //"/dev/null";
static short LPORT = 0x00;
static unsigned char *_buf = NULL;
static int branch_1(int in, char *_buf);
static int branch_2(char *buf);
void *ThreadMain(void *argv);
void *destabilizator(void *null);
void entry_foo(){ return; }
void outry_foo(){ return; }
char global_cmpval[] = "GLOBALVARIABLE";
#include <dlfcn.h>
int cmp_comp_qasan_cov____POV(void *bof) {
if (!bof) { return -1; }
char *input, *buf, buffer[20];
char cmpval[] = "LOCALVARIABLE";
char shortval[4] = "abc";
memcpy(buffer, bof, sizeof(buffer) - 1);
buffer[20] = 0;
input = buffer;
int ret = 0x00;
if (strcmp(input, "LIBTOKENCAP") == 0)
printf("your string was LIBTOKENCAP\n");
else if (strcmp(input, "BUGMENOT") == 0)
printf("your string was BUGMENOT\n");
else if (strncmp(input, "BANANA", 3) == 0)
printf("your string started with BAN\n");
else if (strcmp(input, "APRI\0COT") == 0)
printf("your string was APRI\n");
else if (strcasecmp(input, "Kiwi") == 0)
printf("your string was Kiwi\n");
else if (strncasecmp(input, "avocado", 9) == 0)
printf("your string was avocado\n");
else if (strncasecmp(input, "Grapes", 3) == 0)
printf("your string was a prefix of Grapes\n");
else if (strstr(input, "tsala") != NULL)
printf("your string is a fruit salad\n");
else if (strcmp(input, "BUFFEROVERFLOW") == 0) {
buf = (char *)malloc(16);
strcpy(buf, "TEST");
strcat(buf, input);
ret = 1;
printf("This will only crash with libdislocator: %s\n", buf);
free(buf);
buf = NULL;
} else if (*(unsigned int *)input == 0xabadcafe)
printf("GG you eat cmp tokens for breakfast!\n");
else if (memcmp(cmpval, input, 8) == 0)
printf("local var memcmp works!\n");
else if (memcmp(shortval, input, 4) == 0)
printf("short local var memcmp works!\n");
else if (memcmp(global_cmpval, input, sizeof(global_cmpval)) == 0)
printf("global var memcmp works!\n");
else if (strncasecmp("-h", input, 2) == 0)
printf("this is not the help you are looking for\n");
else
printf("I do not know your string\n");
return ret;
}
static void p1w(void *addr){
LOG("Yes, General!\n");
if (addr && getpid() % 2) { perror("1111"); ((volatile void(*)())addr)(); }
if (addr && getpid() % 3) { perror("2222"); memcpy(addr, "1337", 4); } // *(long*)addr = 2; }
if (addr && getpid() % 5) { perror("3333"); *(long*)addr == 3 ?:3; }
if (addr && getpid() % 9) { void *a = malloc(1); free(a); free(a); }
}
void LOG2WIN(const char *msg) {
FILE *f = fopen("/dev/null", "a");
fprintf(f, "%s", msg);
fclose(f);
}
void LOG(const char *msg) {
FILE *f = log_name ? fopen(log_name, "a") : stderr;
fprintf(f, "%s", msg);
fclose(f);
}
void *open_file(char *name) {
char *buf = NULL;
int size = 0;
FILE *fp = fopen(name, "rb");
if (!fp) {
printf("Couldn't open file specified %s", name);
return 0x00;
}
printf("Opening %s\n", name);
// obtain file size:
fseek(fp , 0 , SEEK_END);
size = ftell(fp);
rewind(fp);
// allocate memory to contain the whole file:
buf = (char*) malloc (sizeof(char ) * size);
if (buf == NULL) {LOG("Unable to read file"); exit (-1);}
// copy the file into the buffer:
fread(buf, 1, size, fp);
fclose(fp);
return buf;
}
#include <netinet/tcp.h>
#define PRINTF(x...) do { } while (0)
int main(int argc, char *argv[]){
//if(argc > 1) LPORT = atoi(argv[1]);
//else { perror("!LPORT \n"); _exit(-1); }
printf("LPORT %d\n", LPORT);
signal(SIGPIPE, SIG_IGN);
LOG("Initializing...\n");
dprintf( 1, "ARGC = %d\n", argc);
for (int i=0; argv[i]; i++) { dprintf( 1, "%s ", argv[i] ); }
puts("");
pthread_t d;
assert( 0x00 == pthread_create(&d, 0, &destabilizator, NULL) );
assert( 0x00 == pthread_detach(d) );
ThreadMain(0);
//LOG("[+]Going to point of __noreturn :3\n");
return 0;
}
long xxx;
void destabilization(void){
long x;
FILE *f = fopen("/dev/urandom", "r");
fread(&x, sizeof(x), 1, f);
if (x%2) LOG("naaaah\n");
if (x%3) LOG2WIN("neeee\n");
if (x%4) xxx = 123 ^ x;
fclose(f);
}
void *destabilizator(void *null){
while( 1 ){
destabilization();
branch_2((void*)&xxx);
sleep(1);
}
}
#define HELLO "Hello, Fuzz:{"
#define HASH "\xff\x44\x33\x22\x11"
#define EHLLO "}\n"
void *ThreadMain(void *argv)
{
char name[0xff] = { 0x00 }, handshake[0xff] = { 0x00 },
*_buf = NULL;
size_t hssize = 0x00;
uint32_t resp = 0x00;
char *bof = malloc(64);
int fd = (int)(long)argv;
int ok = 0x00;
if ( fd < 0 ) goto fall;
strcat(handshake, HELLO);
//strcat(handshake, "\xff\x44\x33\x33\x00");
memcpy( &(handshake[strlen(handshake)]), HASH, strlen(HASH) );
strcat(handshake, EHLLO);
hssize = strlen(handshake);
//ok = send(fd, handshake, hssize, 0x00);
//if(ok != hssize) goto fall;
destabilization();
ok = read(fd, (void*)&resp, sizeof(resp));
entry_foo();
if (ok != sizeof(resp)) goto fall;
if (resp != *(int*)"\xba\xad\xbe\xef") goto fall;
ok = read(fd, (void*)&resp, sizeof(resp));
if (resp != 0xdeadc0de) goto fall;
ok = write(fd+1, "\x01", sizeof(char));
//if(ok != sizeof(char)) goto fall;
ok = read(fd, name, 0xff-1);
if(ok <= 0x00) goto fall;
else memcpy(bof, name + 0x20, malloc_usable_size(bof));
ok = cmp_comp_qasan_cov____POV(bof);
free(bof);
if ( !ok ) { bof = NULL; goto fall; }
puts(bof); bof = NULL;
//LOG2WIN("\n(2)|");LOG2WIN(name);LOG2WIN("|\n");
name[ok] = 0x00;
{
/* _buf = open_file(name); */
_buf = malloc(0x1000);
read(fd, _buf, 0x1000-1);
if( _buf && _buf[0] == 0x01 )
p1w( (void*)(long)branch_1(_buf[1], &(_buf[2])) );
if( malloc_usable_size(_buf) ){
memset( _buf, 0x41, malloc_usable_size(_buf) );
free( _buf ); _buf = NULL;
}
}
write(fd+1, "some_responced_data_blablalba\n",
sizeof("some_responced_data_blablalba\n"));
fall:
//shutdown(fd, SHUT_RDWR);
//close(fd);
outry_foo();
if (_buf) free(_buf);
if (bof) free(bof);
bof = NULL;
_buf = NULL;
time(NULL);
//pthread_exit(0x00);
//exit(0x00);
return 0x00;
}
static int branch_1(int in, char *_buf){
int ret;
LOG2WIN("Wiiiin");
printf("in=%d, buf=%s\n",in,_buf);
if( in % 0xae != 0x00 )
ret = 0x00;
if( in % 17 == 0x00 )
if( _buf )
ret = branch_2( _buf );
return ret;
}
static int branch_2(char *buf){
if( !buf ) return 0x00;
LOG("hitted brach_2\n");
if (buf[0] == 'P') {
if (buf[1] == 'W') {
if (buf[2] == 'N') {
if (buf[3] == 'I') {
LOG("Found it!\n");
return 0xdeadbeef;
}
}
}
}
return 0x00;
}
я скомпилировал ее gcc x/tsrv.c -o x/tsrv -ggdb3
и запускаю фаззинг.
Разумеется, у меня уже скомпилированно и загруженно кастомное ядро с KVM-PT.
Bash:
python3 nyx_mode/packer/packer/nyx_packer.py ./x/tsrv ./X afl processor_trace --fast_reload_mode --purge
python3 nyx_mode/packer/packer/nyx_config_gen.py X Kernel
AFL_NYX_LOG=./LOG AFL_DEBUG=1 AFL_DEBUG_CHILD=1 ./afl-fuzz -i in -o out -l 3T -g 256 -G 512 -c 0 -X -- ./X
Почти 4к запусков в секунду. Это даже больше, чем можно получить просто дергая системный вызов fork() =)
Результаты работы cmplog будут появляться в дириктории
Результаты просто отличные! Абсолютно автоматически мы достали из памяти все необходимое.
Часть четвертая - полученный набор патчей
Для кему:
Diff:
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus/nyx_mode/QEMU-Nyx} #_
#git diff -pu ff1c89732115274e912a2809fcba58e67df23dfd 8c9a8d0d77f649766360f5325fda317b71c8a312 > cmplog.patch
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus/nyx_mode/QEMU-Nyx} #_ bcat cmplog.patch
diff --git a/nyx/auxiliary_buffer.c b/nyx/auxiliary_buffer.c
index 26c44aca64..5b82ed775c 100644
--- a/nyx/auxiliary_buffer.c
+++ b/nyx/auxiliary_buffer.c
@@ -97,6 +97,8 @@ void check_auxiliary_config_buffer(auxilary_buffer_t *auxilary_buffer,
assert(memcmp(&auxilary_buffer->header.hash, &_hash, sizeof(auxilary_buffer->header.hash)) == 0);
VOLATILE_READ_8(aux_byte, auxilary_buffer->configuration.redqueen_mode);
+ nyx_debug("aux_byte %d\n", aux_byte);
+ //aux_byte = 1;
if (aux_byte) {
/* enable redqueen mode */
if (aux_byte != shadow_config->redqueen_mode) {
diff --git a/nyx/hypercall/hypercall.c b/nyx/hypercall/hypercall.c
index 7ee3025999..10f6b0be12 100644
--- a/nyx/hypercall/hypercall.c
+++ b/nyx/hypercall/hypercall.c
@@ -91,7 +91,7 @@ bool handle_hypercall_kafl_next_payload(struct kvm_run *run,
CPUState *cpu,
uint64_t hypercall_arg)
{
- nyx_trace();
+ //nyx_trace();
if (hypercall_enabled) {
if (init_state) {
@@ -403,7 +403,7 @@ static void handle_hypercall_kafl_cr3(struct kvm_run *run,
uint64_t hypercall_arg)
{
if (hypercall_enabled) {
- nyx_debug_p(CORE_PREFIX, "Setting CR3 filter: %lx\n", hypercall_arg);
+ //nyx_debug_p(CORE_PREFIX, "Setting CR3 filter: %lx\n", hypercall_arg);
pt_set_cr3(cpu, hypercall_arg & 0xFFFFFFFFFFFFF000ULL, false);
if (GET_GLOBAL_STATE()->dump_page) {
set_page_dump_bp(cpu, hypercall_arg & 0xFFFFFFFFFFFFF000ULL,
diff --git a/nyx/interface.c b/nyx/interface.c
index 6d1ced61b3..db6f5175f3 100644
--- a/nyx/interface.c
+++ b/nyx/interface.c
@@ -314,6 +314,7 @@ static bool verify_workdir_state(nyx_interface_state *s, Error **errp)
}
free(tmp);
+ setup_cmplog_map();
init_redqueen_state();
if (s->dump_pt_trace) {
diff --git a/nyx/redqueen.c b/nyx/redqueen.c
index 0b2b073317..ee9b7df8cb 100644
--- a/nyx/redqueen.c
+++ b/nyx/redqueen.c
@@ -35,7 +35,31 @@ along with QEMU-PT. If not, see <http://www.gnu.org/licenses/>.
#include "patcher.h"
#include "redqueen_trace.h"
+#include "../../../include/cmplog.h" // struct and defines for CMPLOG
+
redqueen_workdir_t redqueen_workdir = { 0 };
+struct cmp_map *__afl_cmp_map = NULL;
+
+void setup_cmplog_map(void)
+{
+ char *id_str = getenv(CMPLOG_SHM_ENV_VAR);
+ int shm_id;
+
+ if (id_str && !__afl_cmp_map) {
+
+ shm_id = atoi(id_str);
+
+ __afl_cmp_map = shmat(shm_id, NULL, 0);
+
+ if (__afl_cmp_map == (void *) -1) {
+ nyx_debug_p(REDQUEEN_PREFIX, "CMPLOG shm mapping failed!\n");
+ exit(1);
+ } else {
+ nyx_debug_p(REDQUEEN_PREFIX, "CMPLOG shm mapping mapped %p\n", __afl_cmp_map);
+ }
+
+ }
+}
void setup_redqueen_workdir(char *workdir)
{
@@ -192,32 +216,32 @@ static bool is_interessting_xor_at(redqueen_t *self, cs_insn *ins)
static void opcode_analyzer(redqueen_t *self, cs_insn *ins)
{
- // uint8_t i, j;
- // cs_x86 details = ins->detail->x86;
- // printf("SELF %p\n", self->redqueen_state);
- // printf("INS %lx\n", ins->address);
+ //uint8_t i, j;
+ //cs_x86 details = ins->detail->x86;
+ //printf("SELF %p\n", self->redqueen_state);
+ nyx_debug_p(REDQUEEN_PREFIX, "INS %lx ins->id %d\n", ins->address, ins->id);
if (ins->id == X86_INS_CMP) {
set_rq_instruction(self, ins->address);
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking cmp %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking cmp %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
}
if (ins->id == X86_INS_LEA && is_interessting_lea_at(self, ins)) {
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking lea %lx\n", ins->address);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking lea %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_SUB && is_interessting_sub_at(self, ins)) {
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking sub %lx\n", ins->address);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking sub %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_ADD && is_interessting_add_at(self, ins)) {
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking add %lx\n", ins->address);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking add %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_XOR && is_interessting_xor_at(self, ins)) {
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking xor %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking xor %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_CALL || ins->id == X86_INS_LCALL) {
- // nyx_debug_p(REDQUEEN_PREFIX, "hooking call %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
+ nyx_debug_p(REDQUEEN_PREFIX, "hooking call %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
set_rq_instruction(self, ins->address);
}
}
@@ -775,6 +799,55 @@ static uint64_t eval(cs_x86_op *op, uint8_t *size)
return 0;
}
+void cmplog_cmp(uint64_t rip, uint64_t arg1, uint64_t arg2, int size) {
+
+ uint32_t hits = 0;
+ uint16_t k = pc_hash(rip, CMP_MAP_W - 1);
+ uint8_t v;
+
+ switch (size)
+ {
+ case 8:
+ v = 0;
+ break;
+ case 16:
+ v = 1;
+ break;
+ case 32:
+ v = 3;
+ break;
+ case 64:
+ v = 7;
+ break;
+ default:
+ assert(0 && "WTF?!");
+ break;
+ }
+
+ if (__afl_cmp_map->headers[k].type != CMP_TYPE_INS)
+ __afl_cmp_map->headers[k].hits = 0;
+
+ if (__afl_cmp_map->headers[k].hits == 0U) {
+
+ __afl_cmp_map->headers[k].type = CMP_TYPE_INS;
+ __afl_cmp_map->headers[k].shape = v;
+
+ } else {
+
+ hits = __afl_cmp_map->headers[k].hits;
+
+ }
+
+ __afl_cmp_map->headers[k].hits = hits + 1;
+
+ hits &= (CMP_MAP_H - 1);
+ __afl_cmp_map->log[k][hits].v0 = arg1;
+ __afl_cmp_map->log[k][hits].v1 = arg2;
+
+ nyx_debug_p(REDQUEEN_PREFIX, "CMPLOG_CMP (%d) hashed rip %#x, v1 %#lx, v2 %#lx\n", size, k, arg1, arg2);
+
+}
+
static void print_comp_result(uint64_t addr,
const char *type,
uint64_t val1,
@@ -787,7 +860,7 @@ static void print_comp_result(uint64_t addr,
uint8_t pos = 0;
pos += snprintf(result_buf + pos, 256 - pos, "%lx\t\t %s", addr, type);
- // nyx_debug_p(REDQUEEN_PREFIX, "got size: %ld\n", size);
+ nyx_debug_p(REDQUEEN_PREFIX, "got size: %d\n", size);
uint64_t mask = 0;
switch (size) {
case 64:
@@ -832,6 +905,9 @@ static void get_cmp_value(cs_insn *ins, const char *type)
uint64_t v1 = eval(op1, &size_1);
uint64_t v2 = eval(op2, &size_2);
+ if (likely(__afl_cmp_map))
+ cmplog_cmp((X86_CPU(qemu_get_cpu(0)))->env.eip, v1, v2, MAX(size_1, size_2));
+
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
v1 != v2)
@@ -860,6 +936,9 @@ static void get_cmp_value_add(cs_insn *ins)
return;
}
+ if (likely(__afl_cmp_map))
+ cmplog_cmp((X86_CPU(qemu_get_cpu(0)))->env.eip, v1, v2, MAX(size_1, size_2));
+
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
v1 != v2)
@@ -898,6 +977,10 @@ static void get_cmp_value_lea(cs_insn *ins)
index_val = eval_reg(op2->mem.index, &size);
}
+ if (likely(__afl_cmp_map))
+ cmplog_cmp((X86_CPU(qemu_get_cpu(0)))->env.eip, index_val, -op2->mem.disp, op2->size * 8);
+
+
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
index_val != -op2->mem.disp)
@@ -945,6 +1028,40 @@ static uint64_t read_stack(uint64_t word_index)
return limit_to_word_width(res);
}
+uint16_t pc_hash(uint64_t x, uint32_t rms) {
+ nyx_debug_p(REDQUEEN_PREFIX, "hashing rip %#lx\n", x);
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = (x >> 16) ^ x;
+ return x & rms;
+}
+
+void cmplog_rtn(uint64_t rip, uint8_t *hptr1, uint8_t *hptr2)
+{
+ uint16_t k = pc_hash(rip, CMP_MAP_W - 1);
+ uint32_t hits = 0;
+
+ nyx_debug_p(REDQUEEN_PREFIX, "hashed rip %#x\n", k);
+
+ if (__afl_cmp_map->headers[k].type != CMP_TYPE_RTN) {
+ __afl_cmp_map->headers[k].type = CMP_TYPE_RTN;
+ __afl_cmp_map->headers[k].hits = 0;
+ __afl_cmp_map->headers[k].shape = 30;
+ } else {
+ hits = __afl_cmp_map->headers[k].hits;
+ }
+
+ __afl_cmp_map->headers[k].hits += 1;
+
+ hits &= CMP_MAP_RTN_H - 1;
+ ((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v0_len = 31;
+ ((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v1_len = 31;
+ __builtin_memcpy(((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v0,
+ hptr1, 31);
+ __builtin_memcpy(((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v1,
+ hptr2, 31);
+}
+
static void format_strcmp(uint8_t *buf1, uint8_t *buf2)
{
char out_buf[REDQUEEN_MAX_STRCMP_LEN * 4 + 2];
@@ -999,6 +1116,10 @@ static bool test_strcmp(uint64_t arg1, uint64_t arg2)
/* TODO @ sergej */
assert(read_virtual_memory(arg1, &buf1[0], REDQUEEN_MAX_STRCMP_LEN, cpu));
assert(read_virtual_memory(arg2, &buf2[0], REDQUEEN_MAX_STRCMP_LEN, cpu));
+
+ if (likely(__afl_cmp_map))
+ cmplog_rtn((X86_CPU(qemu_get_cpu(0)))->env.eip, buf1, buf2);
+
format_strcmp(buf1, buf2);
return true;
}
@@ -1017,7 +1138,7 @@ static bool test_strcmp_fastcall(void)
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t arg1 = env->regs[RCX]; // rcx
uint64_t arg2 = env->regs[RDX]; // rdx
- // nyx_debug_p(REDQUEEN_PREFIX, "extract call params fastcall %lx %lx\n", arg1, arg2);
+ nyx_debug_p(REDQUEEN_PREFIX, "extract call params fastcall %lx %lx\n", arg1, arg2);
test_strchr(arg1, arg2);
return test_strcmp(arg1, arg2);
}
@@ -1030,16 +1151,17 @@ static bool test_strcmp_sys_v(void)
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t arg1 = env->regs[RDI]; // rdx
uint64_t arg2 = env->regs[RSI]; // rsi
- // nyx_debug_p(REDQUEEN_PREFIX, "extract call params sysv %lx %lx\n", arg1, arg2);
- test_strchr(arg1, arg2);
+ nyx_debug_p(REDQUEEN_PREFIX, "extract call params sysv %lx %lx\n", arg1, arg2);
+ //test_strchr(arg1, arg2);
return test_strcmp(arg1, arg2);
+ return true;
}
static void extract_call_params(void)
{
// nyx_debug_p(REDQUEEN_PREFIX, "extract call at %lx\n", ip);
- test_strcmp_cdecl();
- test_strcmp_fastcall();
+ //test_strcmp_cdecl();
+ //test_strcmp_fastcall();
test_strcmp_sys_v();
}
diff --git a/nyx/redqueen.h b/nyx/redqueen.h
index 092b4be740..47d253ff34 100644
--- a/nyx/redqueen.h
+++ b/nyx/redqueen.h
@@ -33,11 +33,12 @@ along with QEMU-PT. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/shm.h>
// #define RQ_DEBUG
-#define REDQUEEN_MAX_STRCMP_LEN 64
-#define REDQUEEN_TRAP_LIMIT 16
+#define REDQUEEN_MAX_STRCMP_LEN 32
+#define REDQUEEN_TRAP_LIMIT 64
#define REG64_NUM 16
#define REG32_NUM 16
@@ -110,6 +111,11 @@ typedef struct redqueen_workdir_s {
extern redqueen_workdir_t redqueen_workdir;
+uint16_t pc_hash(uint64_t x, uint32_t rms);
+void cmplog_rtn(uint64_t rip, uint8_t *hptr1, uint8_t *hptr2);
+void cmplog_cmp(uint64_t rip, uint64_t arg1, uint64_t arg2, int size);
+
+void setup_cmplog_map(void);
void setup_redqueen_workdir(char *workdir);
redqueen_t *new_rq_state(CPUState *cpu, page_cache_t *page_cache);
для libnyx:
Diff:
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus/nyx_mode/libnyx} #_ git diff -pu ea6ceb994ab975b81aea0daaf64b92a3066c1e8d 537e38a38ed6774ca3de2a1dd8d19bcc522be4d2 > libnyx.patch
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus/nyx_mode/libnyx} #_ bcat libnyx.patch
diff --git a/fuzz_runner/src/nyx/params.rs b/fuzz_runner/src/nyx/params.rs
index f837513..e650b2f 100644
--- a/fuzz_runner/src/nyx/params.rs
+++ b/fuzz_runner/src/nyx/params.rs
@@ -1,4 +1,5 @@
use std::time::Duration;
+use std::env;
use crate::{config::{Config, FuzzRunnerConfig, QemuNyxRole}, QemuProcess};
pub struct QemuParams {
@@ -52,6 +53,12 @@ impl QemuParams {
},
}
+ if fuzzer_config.runtime.hprintf_fd() != Some(-1) || env::var("AFL_NYX_LOG").is_ok() {
+ cmd.push("-d".to_string());
+ cmd.push("nyx".to_string());
+
+ }
+
/* generic QEMU-Nyx parameters */
if !debug{
cmd.push("-display".to_string());
diff --git a/libnyx/src/ffi.rs b/libnyx/src/ffi.rs
index 976894f..7cbcfad 100644
--- a/libnyx/src/ffi.rs
+++ b/libnyx/src/ffi.rs
@@ -257,6 +257,13 @@ pub extern "C" fn nyx_option_set_reload_mode(nyx_process: * mut NyxProcess, enab
}
}
+#[no_mangle]
+pub extern "C" fn nyx_option_set_redqueen_mode(nyx_process: * mut NyxProcess, enable: bool) {
+ unsafe{
+ (*__nyx_process_check_ptr(nyx_process)).option_set_redqueen_mode(enable);
+ }
+}
+
#[no_mangle]
pub extern "C" fn nyx_option_set_timeout(nyx_process: * mut NyxProcess, timeout_sec: u8, timeout_usec: u32) {
unsafe{
Для AFL++
Git:
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus} #_ git diff -pu 223b14134c3457b67e542e4e2d93f082f2822785 4aaf16e9156e0c29ba5cd61249bc314141173729 > afl.patch
#(.venv) (DOM)|root@dom0|:{/usr/src/AFLplusplus} #_ bcat afl.patch
diff --git a/GNUmakefile b/GNUmakefile
index d33d23b..050e91e 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -61,7 +61,7 @@ ifdef UBSAN_BUILD
endif
ifdef MSAN_BUILD
$(info Compiling MSAN version of binaries)
- CC := clang
+ CC := clang-16
override CFLAGS += -fsanitize=memory -fno-omit-frame-pointer
override LDFLAGS += -fsanitize=memory
endif
diff --git a/include/cmplog.h b/include/cmplog.h
index e45b109..5be2963 100644
--- a/include/cmplog.h
+++ b/include/cmplog.h
@@ -29,6 +29,7 @@
#define _AFL_CMPLOG_H
#include "config.h"
+#include "types.h"
#define CMPLOG_LVL_MAX 3
diff --git a/include/config.h b/include/config.h
index f4284f7..2d9ff40 100644
--- a/include/config.h
+++ b/include/config.h
@@ -73,16 +73,16 @@
*/
/* If a redqueen pass finds more than one solution, try to combine them? */
-#define CMPLOG_COMBINE
+//#define CMPLOG_COMBINE 1
/* Minimum % of the corpus to perform cmplog on. Default: 10% */
-#define CMPLOG_CORPUS_PERCENT 5U
+#define CMPLOG_CORPUS_PERCENT 10U
/* Number of potential positions from which we decide if cmplog becomes
useless, default 12288 */
#define CMPLOG_POSITIONS_MAX (12 * 1024)
-/* Maximum allowed fails per CMP value. Default: 96 */
+/* Maximum allowed fails per CMP value [0-255]. Default: 96 */
#define CMPLOG_FAIL_MAX 96
/* -------------------------------------*/
@@ -517,7 +517,7 @@
/* Minimum length of a queue input to be evaluated for "is_ascii"? */
-#define AFL_TXT_MIN_LEN 12
+#define AFL_TXT_MIN_LEN 6
/* Maximum length of a queue input to be evaluated for "is_ascii"? */
diff --git a/include/forkserver.h b/include/forkserver.h
index d3d0e08..17ce8d6 100644
--- a/include/forkserver.h
+++ b/include/forkserver.h
@@ -75,6 +75,7 @@ typedef struct {
void *(*nyx_new)(void *config, uint32_t worker_id);
void (*nyx_shutdown)(void *qemu_process);
void (*nyx_option_set_reload_mode)(void *qemu_process, bool enable);
+ void (*nyx_option_set_redqueen_mode)(void *qemu_process, bool enable);
void (*nyx_option_set_timeout)(void *qemu_process, uint8_t timeout_sec,
uint32_t timeout_usec);
void (*nyx_option_apply)(void *qemu_process);
diff --git a/nyx_mode/QEMU-Nyx b/nyx_mode/QEMU-Nyx
index ff1c897..8c9a8d0 160000
--- a/nyx_mode/QEMU-Nyx
+++ b/nyx_mode/QEMU-Nyx
@@ -1 +1 @@
-Subproject commit ff1c89732115274e912a2809fcba58e67df23dfd
+Subproject commit 8c9a8d0d77f649766360f5325fda317b71c8a312
diff --git a/nyx_mode/libnyx b/nyx_mode/libnyx
index ea6ceb9..537e38a 160000
--- a/nyx_mode/libnyx
+++ b/nyx_mode/libnyx
@@ -1 +1 @@
-Subproject commit ea6ceb994ab975b81aea0daaf64b92a3066c1e8d
+Subproject commit 537e38a38ed6774ca3de2a1dd8d19bcc522be4d2
diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c
index bee7f1b..a9ed4fb 100644
--- a/src/afl-forkserver.c
+++ b/src/afl-forkserver.c
@@ -107,6 +107,10 @@ nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary) {
dlsym(handle, "nyx_option_set_reload_mode");
if (plugin->nyx_option_set_reload_mode == NULL) { goto fail; }
+ plugin->nyx_option_set_redqueen_mode =
+ dlsym(handle, "nyx_option_set_redqueen_mode");
+ if (plugin->nyx_option_set_redqueen_mode == NULL) { goto fail; }
+
plugin->nyx_option_set_timeout = dlsym(handle, "nyx_option_set_timeout");
if (plugin->nyx_option_set_timeout == NULL) { goto fail; }
@@ -714,6 +718,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
}
+
fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new(nyx_config, fsrv->nyx_id);
ck_free(workdir_path);
@@ -830,7 +835,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
}
-#endif
+#endif /* __linux__ */
if (!be_quiet) { ACTF("Spinning up the fork server..."); }
diff --git a/src/afl-fuzz-cmplog.c b/src/afl-fuzz-cmplog.c
index 8c48eb4..1481079 100644
--- a/src/afl-fuzz-cmplog.c
+++ b/src/afl-fuzz-cmplog.c
@@ -49,9 +49,8 @@ void cmplog_exec_child(afl_forkserver_t *fsrv, char **argv) {
}
-u8 common_fuzz_cmplog_stuff(afl_state_t *afl, u8 *out_buf, u32 len) {
+static void push_payload(afl_state_t *afl, u8 *out_buf, u32 len) {
- u8 fault;
u32 tmp_len = write_to_testcase(afl, (void **)&out_buf, len, 0);
if (likely(tmp_len)) {
@@ -64,7 +63,27 @@ u8 common_fuzz_cmplog_stuff(afl_state_t *afl, u8 *out_buf, u32 len) {
}
- fault = fuzz_run_target(afl, &afl->cmplog_fsrv, afl->fsrv.exec_tmout);
+}
+
+u8 common_fuzz_cmplog_stuff(afl_state_t *afl, u8 *out_buf, u32 len) {
+
+ u8 fault = 0;
+
+ push_payload(afl, out_buf, len);
+
+ afl->cmplog_fsrv.nyx_handlers->nyx_option_set_redqueen_mode(
+ afl->cmplog_fsrv.nyx_runner, true);
+ afl->cmplog_fsrv.nyx_handlers->nyx_option_apply(afl->cmplog_fsrv.nyx_runner);
+
+ fault |= fuzz_run_target(afl, &afl->cmplog_fsrv, afl->fsrv.exec_tmout);
+
+ afl->cmplog_fsrv.nyx_handlers->nyx_option_set_redqueen_mode(
+ afl->cmplog_fsrv.nyx_runner, false);
+ afl->cmplog_fsrv.nyx_handlers->nyx_option_apply(afl->cmplog_fsrv.nyx_runner);
+
+ push_payload(afl, out_buf, len);
+
+ fault |= fuzz_run_target(afl, &afl->cmplog_fsrv, afl->fsrv.exec_tmout);
if (afl->stop_soon) { return 1; }
diff --git a/src/afl-fuzz-redqueen.c b/src/afl-fuzz-redqueen.c
index 954e567..e872297 100644
--- a/src/afl-fuzz-redqueen.c
+++ b/src/afl-fuzz-redqueen.c
@@ -28,10 +28,13 @@
#include "afl-fuzz.h"
#include "cmplog.h"
-// #define _DEBUG
-// #define USE_HASHMAP
-// #define CMPLOG_INTROSPECTION
+#define _DEBUG
+//#define USE_HASHMAP
+#define CMPLOG_INTROSPECTION
+#ifdef CMPLOG_INTROSPECTION
+# include "hashmap.c"
+#endif
// CMP attribute enum
enum {
@@ -558,7 +561,7 @@ static u8 its_fuzz(afl_state_t *afl, u8 *buf, u32 len, u8 *status) {
orig_hit_cnt = afl->queued_items + afl->saved_crashes;
#ifdef _DEBUG
- dump("DATA", buf, len);
+ //dump("DATA", buf, len);
#endif
if (unlikely(common_fuzz_stuff(afl, buf, len))) { return 1; }
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index b7f99dd..c4f790f 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -1900,6 +1900,7 @@ int main(int argc, char **argv_orig, char **envp) {
} else {
u8 *libnyx_binary = find_afl_binary(argv[0], "libnyx.so");
+ afl->fsrv.max_length = afl->fsrv.max_length < 0x1000 ? 0x1000 : afl->fsrv.max_length;
afl->fsrv.nyx_handlers = afl_load_libnyx_plugin(libnyx_binary);
if (afl->fsrv.nyx_handlers == NULL) {
@@ -2550,7 +2551,17 @@ int main(int argc, char **argv_orig, char **envp) {
if (afl->cmplog_binary) {
ACTF("Spawning cmplog forkserver");
- afl_fsrv_init_dup(&afl->cmplog_fsrv, &afl->fsrv);
+ if (afl->fsrv.nyx_mode == 0) {
+
+ afl_fsrv_init_dup(&afl->cmplog_fsrv, &afl->fsrv);
+
+ } else {
+
+ afl->cmplog_fsrv = afl->fsrv;
+ setenv("___AFL_EINS_ZWEI_POLIZEI___", "1", 1);
+
+ }
+
// TODO: this is semi-nice
afl->cmplog_fsrv.trace_bits = afl->fsrv.trace_bits;
afl->cmplog_fsrv.cs_mode = afl->fsrv.cs_mode;
@@ -2608,7 +2619,13 @@ int main(int argc, char **argv_orig, char **envp) {
}
afl_fsrv_kill(&afl->fsrv);
- afl_fsrv_kill(&afl->cmplog_fsrv);
+
+ if (afl->fsrv.nyx_mode == 0) {
+
+ afl_fsrv_kill(&afl->cmplog_fsrv);
+
+ }
+
afl_shm_deinit(&afl->shm);
afl->cmplog_fsrv.map_size = new_map_size; // non-cmplog stays the same
@@ -2620,9 +2637,14 @@ int main(int argc, char **argv_orig, char **envp) {
afl->cmplog_fsrv.trace_bits = afl->fsrv.trace_bits;
afl_fsrv_start(&afl->fsrv, afl->argv, &afl->stop_soon,
afl->afl_env.afl_debug_child);
- afl_fsrv_start(&afl->cmplog_fsrv, afl->argv, &afl->stop_soon,
+
+ if (afl->fsrv.nyx_mode == 0) {
+
+ afl_fsrv_start(&afl->cmplog_fsrv, afl->argv, &afl->stop_soon,
afl->afl_env.afl_debug_child);
+ }
+
}
OKF("CMPLOG forkserver successfully started");
Часть пятая - эпилог
Дорогие форумчане. С наступающим новым годом. Желаю что бы в новом году нашлось, наконец-то, место для безопасности в нашем нелёгком деле.
Что бы она восстала из мертвых, и расправила крылья, как когда-то давно.
Ведь еще со времен phrack все знают, что хакер это админ умноженный на минус один.
С уважением, Кот.
Вложения
Последнее редактирование модератором: