Привет всем!
Мотивационная часть
В эпоху информационного общества многие привычные понятия трансформируются, приобретают новую материальную форму - это неизбежное влияние эволюции.
Бытует мнение, что одним из основопологающих принципов новой эпохи является трансформация из количества в качество.
Это весьма резонно, ведь информации становится все больше и больше. Технологический прогресс ускоряется в геометрической прогрессии - а значит времени для усвоения новой информации становится все меньше и меньше.
А из этого следует не хитрый вывод, что дальнейшая судьба человека, общества, нации будет весьма сильно зависить от способности взаимодействия с информационным полем.
Мы не есть исключение. В институтах ученые, академики, доценты и аспиранты пишут научные труды, делятся ими с общественностью и так далее.
То, во что рано или поздно, частично или полностью, трансформируется вышеописанное - это информациооные ресурсы профильных назначений. Например Дамага.
К сожалению, люди забывают о безопасности. Забывают о ней что на уровне человека, что на уровне государства, что на уровне нашего с вами конкурса статей... =)
А жаль.
Введение
Я люблю фаззинг.
Можно смотреть вечно на три вещи: как горит огонь, как течет вода, и как 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. https://github.com/AFLplusplus/qemu...f35b1bf6b4b/target/i386/tcg/translate.c#L5186
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++ в случае выставления флага -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.
github.com
Внутри струкруты форксервера уже хранятся свои данные нужные инстансу.
Нужно еще разобраться как же происходит коммуникация между софтом. Для этого используется общий сегмент памяти shm.
На стороне AFL++
github.com
либо
github.com
в зависимости от реализации shm.
И что еще нужно знать про cmplog на стороне AFL++ так это то, как он запускается.
github.com
Функция 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()
github.com
Опять таки, забегая вперед, на скриншоте видно, как я добавил новое апи nyx_option_set_redqueen_mode.
Сама же библиотека написана на rust. И нужное нам апи в ней есть:
github.com
Однако, не экспортируется:
github.com
В файле экспортов функция option_set_redqueen_mode не описана.
Часть вторая - портируем код.
При правильной планировке задач страдания разработчика сводятся к минимуму.
Я всегда вспоминаю ту буханку хлеба, из которой сделали троллейбус =)
И так, нам нужно перенести движёк cmplog из кему в кему, доработать libnyx, доработать AFL++ для взаимодействия с доработанной libnyx, доработать сам AFL++ для поддержки фаззигна в режиме CMPLOG + QEMU_NYX.
В первой части мы рассмотрели как инициализируется сегменг общей памяти в AFL++, помним как он подгружается на стороне QEMU.
Нам нужно найти место в QEMU_NYX где бы мы могли аккуратно разместить код инициализации shm сегмента, а так же двух хендлеров - строковых и цифровых. Еще нам потребуется функция для хеширования адреса.
Быстро прогрепав исходники по паттерну redqueen я нашел подходящего кандидата:
github.com
Тут можно разместить функцию, которая будет инициализировать shm. Но сперва ее нужно портировать. У меня это получилось вот так:
для файла 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 форксервер. И адаптировать его под наш случай. https://github.com/AFLplusplus/AFLp...d972da601bb74b/src/afl-fuzz.c#L2553C5-L2553C6
Просто вместо дублирования fsrv присваиваем указатель.
Ниже по коду обрамляем логику перезапуска форксервера, так как в старом режиме у нас было 2 отдельных процесса, а теперь будет один.
Вроде как все. Теперь нужно разобраться с запуском cmplog итерации.
Мы помним то место, куда стекают вызовы cmplog. Перепишем его следующим образом:
У вдумчивого читателя обязательно должен был возникнуть вопрос почему мы запускаем две итерации вместо одной.
Дело в том, что при выполнении в режиме redqueen покрытие не собирается посредством intel_pt. На сколько я могу предположить сделано это умышленно, дабы не было побочных результатов трассировки.
Хотя с другой стороны их и так не должно быть, т.к. апи intel_pt отличает контексты выполнения (регистр cr3).
RedQueen же инструментирует благодоря обычным программным точкам останова. Я не стал проверять.
Важно уточнить, что сперва мы собираем карту cmplog, а после собираем карту покрытия с тем же вводом, так как перед запуском карта покрытия обнуляется, и в обратной последовательности карты покрытия не будет.
А ну и на последок:
Это нужно что бы можно было изменять максимальную длинну ввода в режиме NYX.
Часть третья - тестирование.
У меня есть специально обученная программулина, которая позволяет тестировать фаззеры.
Вот ее код:
я скомпилировал ее gcc x/tsrv.c -o x/tsrv -ggdb3
и запускаю фаззинг.
Разумеется, у меня уже скомпилированно и загруженно кастомное ядро с KVM-PT.
Bash:Скопировать в буфер обмена
Почти 4к запусков в секунду. Это даже больше, чем можно получить просто дергая системный вызов fork() =)
Результаты работы cmplog будут появляться в дириктории
Результаты просто отличные! Абсолютно автоматически мы достали из памяти все необходимое.
Часть четвертая - полученный набор патчей
Для кему:
для libnyx:
Для AFL++
Часть пятая - эпилог
Дорогие форумчане c Новым Годом Вас! Желаю что бы в новом году нашлось, наконец-то, место для безопасности в нашем нелёгком деле.
Что бы она восстала из мертвых, и расправила крылья, как когда-то давно.
Ведь еще со времен phrack все знают, что хакер это админ умноженный на минус один.
С уважением, Кот.
Мотивационная часть
В эпоху информационного общества многие привычные понятия трансформируются, приобретают новую материальную форму - это неизбежное влияние эволюции.
Бытует мнение, что одним из основопологающих принципов новой эпохи является трансформация из количества в качество.
Это весьма резонно, ведь информации становится все больше и больше. Технологический прогресс ускоряется в геометрической прогрессии - а значит времени для усвоения новой информации становится все меньше и меньше.
А из этого следует не хитрый вывод, что дальнейшая судьба человека, общества, нации будет весьма сильно зависить от способности взаимодействия с информационным полем.
Мы не есть исключение. В институтах ученые, академики, доценты и аспиранты пишут научные труды, делятся ими с общественностью и так далее.
То, во что рано или поздно, частично или полностью, трансформируется вышеописанное - это информациооные ресурсы профильных назначений. Например Дамага.
К сожалению, люди забывают о безопасности. Забывают о ней что на уровне человека, что на уровне государства, что на уровне нашего с вами конкурса статей... =)
А жаль.
Введение
Я люблю фаззинг.
Можно смотреть вечно на три вещи: как горит огонь, как течет вода, и как 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. https://github.com/AFLplusplus/qemu...f35b1bf6b4b/target/i386/tcg/translate.c#L5186
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!...
в зависимости от реализации shm.
И что еще нужно знать про 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.
Часть вторая - портируем код.
При правильной планировке задач страдания разработчика сводятся к минимуму.
Я всегда вспоминаю ту буханку хлеба, из которой сделали троллейбус =)
И так, нам нужно перенести движёк 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 форксервер. И адаптировать его под наш случай. https://github.com/AFLplusplus/AFLp...d972da601bb74b/src/afl-fuzz.c#L2553C5-L2553C6
Просто вместо дублирования 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++) { dprintf( 1, "%s ", argv ); }
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:Скопировать в буфер обмена
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");
Часть пятая - эпилог
Дорогие форумчане c Новым Годом Вас! Желаю что бы в новом году нашлось, наконец-то, место для безопасности в нашем нелёгком деле.
Что бы она восстала из мертвых, и расправила крылья, как когда-то давно.
Ведь еще со времен phrack все знают, что хакер это админ умноженный на минус один.
С уважением, Кот.