
Набор инструментов для внутрипроцессного фаззинга на основе Frida с прокси AFL++, отдельными активным/пассивным режимами и обменом через разделяемую память для высокопроизводительного поиска уязвимостей с управлением покрытием на различных платформах.
fpicker — это набор инструментов для фаззинга на основе Frida, который предлагает различные режимы фаззинга для внутрипроцессного фаззинга, такие как режим AFL++ или пассивный режим трассировки. Он должен работать на всех платформах, поддерживаемых Frida.
Некоторую справочную информацию, а также мысли и идеи, стоящие за fpicker, можно найти в статье в блоге, которую я написал.
Fpicker основан на предыдущих разработках ToothPicker, созданных в ходе моей магистерской диссертации. Большая часть fpicker была разработана в рабочее время у моего работодателя (ERNW).
Необходимо для запуска fpicker:
frida-core-devkit для соответствующей платформы, доступный в релизах Frida на GitHub.
libfrida-core-ios.a, libfrida-core-macos.a или libfrida-core-linux.a.frida-core.h). Сохраните их как frida-core-linux.h или frida-core-ios.h в зависимости от платформы.update_frida_version.sh перед сборкой.Требуется только при работе в режиме AFL++:
CFLAGS="-DUSEMMAP=1".CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS".Fpicker можно собрать для macOS, iOS или Linux. Makefile в настоящее время поддерживает сборку только для iOS на macOS, но вполне возможно собрать fpicker с использованием инструментария iOS на Linux.
В зависимости от желаемой цели выполните:
make fpicker-macos
make fpicker-ios
make fpicker-linux
для сборки fpicker.
После сборки fpicker необходимо также собрать обвязку для фаззинга:
Примеры различных вариантов фаззинга см. в папке examples. Общий подход следующий:
examples/test/test.js) (см.
здесь для получения дополнительной информации об обвязках)frida-compile test.js -o harness.jsТеперь fpicker может начать фаззинг. Точная команда сильно зависит от конфигурации и настройки. Ниже приведено несколько примеров. Они в основном соответствуют примерам из папки examples.
afl-fuzz -i examples/test-network/in -o ./examples/test-network/out -- \\
./fpicker --fuzzer-mode afl -e attach -p test-network -f ./examples/test-network/harness.js
./fpicker --fuzzer-mode standalone -e attach -p server-process -f harness.js --input-mode cmd \\
--command "./client-send @@" -i indir -o outdir
./fpicker --fuzzer-mode active --communication-mode shm -e attach -p server-process -f harness.js \\
-i indir -o outdir --standalone-mutator cmd --mutator-command "radamsa"
./fpicker --fuzzer-mode passive --communication-mode send -e attach -p server-process -o outdir -f harness.js
./fpicker --fuzzer-mode active -e attach -p test -D remote -o examples/test/out/ -i examples/test/in/ \\
-f fuzzer-agent.js --standalone-mutator cmd --mutator-command "radamsa"
Каждая цель требует собственной обвязки для фаззинга. Самая важная часть этой обвязки — определение точки входа для Stalker от Frida, которая фактически определяет, в каком месте вставляется инструментация. В режиме in-process это просто. Функция обычно является той, которая вызывается на каждой итерации фаззинга. Однако это может быть и другая функция.
Минималистичная реализация обвязки (в режиме command) может выглядеть так:
// Import the fuzzer base class
const Fuzzer = require("harness/fuzzer.js");
// The custom fuzzer needs to subclass the Fuzzer class to work properly
class TestFuzzer extends Fuzzer.Fuzzer {
constructor() {
// The constructor needs to specify the address of the targeted function and a NativeFunction
// object that can later be called by the fuzzer.
const FUZZ_FUNCTION_ADDR = Module.getExportByName(null, "FUZZ_FUNCTION");
const FUZZ_FUNCTION = new NativeFunction(
FUZZ_FUNCTION_ADDR,
"void", ["pointer", "int64"], {
});
super("test", FUZZ_FUNCTION_ADDR, FUZZ_FUNCTION);
}
}
const f = new TestFuzzer();
exports.fuzzer = f;
Эта обвязка настраивает инструментацию на отслеживание функции FUZZ_FUNCTION. Инструментация начинается при входе в эту функцию и завершается при её возврате. Эту функцию следует выбирать тщательно, так как она ресурсоёмка, и чем больше (потенциально неважных) частей процесса инструментировано, тем медленнее становится фаззер. Конечно, это компромисс между скоростью и желаемым покрытием. Кроме того, в настоящее время фаззер поддерживает только функции, которые вызываются только один раз за одну итерацию фаззинга, т.е. функция не должна вызываться более одного раза в течение одного тестового случая, иначе информация о покрытии может стать ненадёжной.
При использовании режима in-process в скрипте фаззера требуется ещё одна функция: метод fuzz. Он будет вызываться на каждой итерации. Он будет вызван с двумя параметрами: указателем на буфер и длиной буфера. Наша примерная целевая функция принимает два параметра: указатель на буфер и его длину. Таким образом, мы можем просто передать параметры, которые получаем в методе fuzz.
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}
В пассивном режиме необходимо указать обратный вызов, который обрабатывает требуемые данные. Фаззер ожидает получить буфер полезной нагрузки и его длину. В зависимости от целевой функции, которую фаззят, эти данные необходимо извлечь. В следующем примере у нас снова есть функция с двумя параметрами: указатель на буфер и его длина. Параметр args содержит все потенциальные параметры, которые получает целевая функция, поэтому параметр длины (в нашем случае второй) можно получить через args[1]. Затем мы считываем буфер как Uint8Array и отправляем его обратно фаззеру с помощью метода sendPassiveCorpus.
passiveCallback(args) {
const len = args[1];
const data = new Uint8Array(Memory.readByteArray(args[0], parseInt(len)));
// this encodes the data and sends it back to the fuzzer
this.sendPassiveCorpus(data, len);
}
Если цель требует некоторой подготовки перед началом фаззинга, fpicker предоставляет метод prepare, который вызывается во время инициализации фаззера. Подготовка может включать установку состояния, например, создание экземпляра объекта. Такая функция подготовки может выглядеть следующим образом:
prepare() {
// the object can be attached to the fuzzer instance so that it can be used within the
// fuzz() method later on.
this.required_object = call_native_function_that_creates_object();
}
fpicker предлагает большой набор режимов и конфигураций, которые описаны ниже. Большинство этих режимов можно комбинировать различными способами. В конце этого раздела представлена таблица, показывающая, какие опции можно комбинировать и каков их статус реализации.
Fpicker имеет три различных режима фаззинга: режим AFL++, автономный активный режим и автономный пассивный режим:
Режим AFL++: В режиме AFL++ fpicker действует как прокси между AFL++ и целевым процессом. Используя возможности инструментации Frida, битовая карта покрытия AFL заполняется во время фаззинга цели входными данными, сгенерированными AFL++.
Автономный активный режим: В автономном активном режиме фаззер использует сводки вызовов Stalker от Frida для сбора покрытия в виде базовых блоков, которые выполняются в течение итерации. Это не ново и ранее реализовывалось в различных формах. Однако в сочетании с некоторыми другими настройками фаззера это может дать различные преимущества. Это также хорошая альтернатива, если AFL++ неприменим или нежелателен в данной среде или случае.
Автономный пассивный режим: Пассивный режим — это скорее трассировщик, чем фаззер. По сути, он делает то же самое, что и автономный активный режим. Однако он не отправляет собственные входные данные. Он просто подключается к определённой функции и собирает покрытие. Как только обнаруживается новое покрытие, сохраняются как покрытие, так и входные данные.
Хотя fpicker в основном разработан как внутрипроцессный фаззер, он также поддерживает фаззинг через внешнюю команду. Для этого fpicker предлагает два режима ввода.
Режим ввода «Внутри процесса»: В режиме ввода «внутри процесса» обвязка напрямую вызывает указанную функцию в целевом процессе. Фаззер отправляет полезную нагрузку обвязке, а обвязка подготавливает полезную нагрузку таким образом, чтобы можно было вызвать целевую функцию.
Режим ввода CMD: В режиме ввода команды полезная нагрузка перенаправляется внешней команде. Это полезно, если подготовка параметров или другого состояния при прямом вызове целевой функции слишком сложна. Сбор покрытия по-прежнему должен быть привязан к определённой функции. Возможно, есть клиент, которому можно передать полезную нагрузку, которая затем запускает целевую функцию.
Режим связи определяет, как внедрённая обвязка взаимодействует с фаззером. Это во многом зависит от целевого приложения. Frida предоставляет API для отправки и получения сообщений от внедрённого агентского скрипта. Этот тип связи довольно затратен. Одним из факторов является то, что передаваемое сообщение необходимо кодировать в JSON. Поэтому отправка двоичных данных проста. В связи с этим fpicker предлагает второй режим связи — через разделяемую память. Однако это работает только в том случае, если можно установить разделяемую память между фаззером и целевым приложением, что означает, что этот режим нельзя использовать, когда цель подключена к хосту фаззера через USB. В режиме ввода CMD режим связи относится только к тому, как информация о покрытии передаётся обратно фаззеру, а не к тому, как отправляется полезная нагрузка, поскольку это делегируется внешней команде.
Режим связи Send: В режиме связи отправки полезная нагрузка отправляется с использованием механизма вызова RPC от Frida. Это позволяет фаззеру выполнять функцию JavaScript внутри внедрённого скрипта обвязки. Эта функция внутри обвязки может затем выполнить все необходимые приготовления для вызова целевой функции. После возврата из целевой функции сбор покрытия прекращается, и обвязка может сообщить фаззеру, что итерация завершена. Это делается путём отправки информации о покрытии обратно фаззеру с помощью API отправки Frida.
Режим связи SHM: В режиме связи через разделяемую память фаззер и скрипт обвязки взаимодействуют через разделяемую память и семафоры. Буфер в разделяемой памяти используется для отправки полезной нагрузки и получения информации о покрытии. Вместо отправки и получения два компонента используют ожидание и отправку сигнала семафору. В зависимости от системы и цели это даёт значительный прирост производительности. Особенно потому, что двоичная полезная нагрузка записывается в память один раз и не требует кодирования, декодирования или копирования в другие области памяти. К сожалению, этот режим иногда приводит к низкой стабильности при работе с AFL++. Пока неясно, почему.
Режим выполнения может быть spawn или attach. Это довольно очевидно. fpicker может либо подключиться к запущенному процессу, либо породить процесс. Одно из основных различий между двумя режимами заключается в том, что в случае сбоя подключённой цели fpicker не будет пытаться перезапустить её.
В автономном режиме fpicker предлагает три различные стратегии мутации входных данных. Если говорить мягко, мутация входных данных, безусловно, имеет много возможностей для улучшения.
Автономный мутатор NULL: Этот мутатор не мутирует полезную нагрузку и просто возвращает копию той же полезной нагрузки. В основном для тестирования. В остальном не очень полезен.
Автономный мутатор Rand: Очень плохой случайный мутатор. Он просто случайным образом заменяет значения в случайных местах исходной полезной нагрузки. Он не изменяет длину полезной нагрузки.
Автономный мутатор Custom: Этот мутатор может вызывать внешнюю команду для мутации полезных нагрузок. Он записывает полезную нагрузку в stdin и получает мутированную полезную нагрузку из stdout. Из-за неглубокой реализации он оказывает значительное влияние на производительность.
При использовании опции -D usb Frida выберет первое локальное USB-устройство, например iPhone или телефон Android.
С помощью опции -D remote можно фаззить процесс, запущенный на сетевом устройстве. Для этого удалённое устройство должно запускать frida-server. В качестве примера конфигурации используйте SSH с переадресацией портов, чтобы привязать порт прослушивания frida-server по умолчанию (27042) на удалённом устройстве к сокету на локальном клиенте.
ssh -N [email protected] -L 127.0.0.1:27042:127.0.0.1:27042
На iPhone можно также использовать iproxy для перенаправления порта через USB-соединение.
Это может быть особенно полезно при запуске Frida на нестандартном порту на неджейлбрейкнутом устройстве с помощью гаджета Frida. При работе с гаджетом Frida единственным доступным процессом будет процесс с именем Gadget, независимо от имени целевого приложения.
iproxy 27042 27042
Затем используйте frida-ps для проверки конфигурации, выведя список процессов на удалённом устройстве:
frida-ps -R