
Фаззер для драйверов ядра Linux
Этот репозиторий содержит все исходные файлы (включая скрипты настройки), необходимые для запуска difuze.
Ubuntu >= 14.04.5 LTS
См. README
Как описано в нашей статье, difuze состоит из двух основных компонентов: Восстановление интерфейса и Движок фаззинга
Механизм восстановления интерфейса основан на анализах проходов LLVM. Каждый шаг восстановления интерфейса реализован в виде отдельного прохода. Следуйте инструкциям ниже, чтобы запустить Восстановление интерфейса.
Этот шаг устанавливает LLVM и c2xml:
Сначала убедитесь, что у вас установлен libxml (требуется для c2xml):
sudo apt-get install libxml2-dev
sudo pip install lxml
Далее мы подготовили единый скрипт, который загружает и собирает все необходимые инструменты.
cd helper_scripts
python setup_difuze.py --help
usage: setup_difuze.py [-h] [-b TARGET_BRANCH] [-o OUTPUT_FOLDER]
optional arguments:
-h, --help show this help message and exit
-b TARGET_BRANCH Branch (i.e. version) of the LLVM to setup. Default:
release_38 e.g., release_38
-o OUTPUT_FOLDER Folder where everything needs to be setup.
Пример:
python setup_difuze.py -o difuze_deps
Для завершения настройки также потребуется изменить локальную переменную окружения PATH. Скрипт настройки сообщит вам точные изменения, которые необходимо внести.
Этот шаг зависит от успешного выполнения Настройки. У нас есть единый скрипт, который собирает всё, пожалуйста.
cd InterfaceHandlers
./build.sh
Этот шаг зависит от успешного выполнения Сборки. Для запуска компонентов восстановления интерфейса на драйверах ядра необходимо сначала преобразовать драйверы в LLVM-биткод.
Во-первых, нам нужно собрать ядро. То есть вы должны уметь компилировать ядро с помощью обычной настройки сборки, т.е. make.
Сначала мы захватываем вывод команды make, а из этого вывода извлекаем точную команду компиляции.
makebear make <все опции make>
bear make -j8Это создаст файл compile_commands.json в текущем каталоге.
Просто передайте V=1 и перенаправьте вывод в файл.
Пример:
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
ПРИМЕЧАНИЕ: НЕ ИСПОЛЬЗУЙТЕ НЕСКОЛЬКО ПРОЦЕССОВ, т.е. -j. Работа в многопроцессорном режиме испортит выходной файл, так как несколько процессов попытаются записать в него одновременно.
Вот и всё. Затем на следующем шаге наш скрипт берёт сгенерированный файл makeout.txt и запускает восстановление интерфейса на всех распознанных драйверах.
Все различные шаги восстановления интерфейса объединены в единый скрипт helper_scripts/run_all.py
Как запустить:
cd helper_scripts
python run_all.py --help
usage: run_all.py [-h] [-l LLVM_BC_OUT] [-a CHIPSET_NUM] [-m MAKEOUT]
[-c COMPJSON] [-g COMPILER_NAME] [-n ARCH_NUM] [-o OUT]
[-k KERNEL_SRC_DIR] [-isclang] [-clangp CLANG_PATH]
[-llvmlinkp LLVMLINK_PATH] [-skb] [-skl] [-skp] [-skP]
[-ske] [-skI] [-ski] [-skv] [-skd] [-f IOCTL_FINDER_OUT]
optional arguments:
-h, --help show this help message and exit
-l LLVM_BC_OUT Destination directory where all the generated bitcode
files should be stored.
-a CHIPSET_NUM Chipset number. Valid chipset numbers are:
1(mediatek)|2(qualcomm)|3(huawei)|4(samsung)
-m MAKEOUT Path to the makeout.txt file.
-c COMPJSON Path to the compile_commands_json generated by Bear.
-g COMPILER_NAME Name of the compiler used in the makeout.txt, This is
needed to filter out compilation commands. Ex: aarch64
-linux-android-gcc
-n ARCH_NUM Destination architecture, 32 bit (1) or 64 bit (2).
-o OUT Path to the out folder. This is the folder, which
could be used as output directory during compiling
some kernels.
-k KERNEL_SRC_DIR Base directory of the kernel sources.
-isclang flag to indicate that clang was used to built the
kernel
-clangp CLANG_PATH Absolute path to the clang binary (if not provided,
the one available in the path will be used)
-llvmlinkp LLVMLINK_PATH
Absolute path to the llvm-link binary (if not
provided, the one available in the path will be used)
-skb Skip LLVM Build (default: not skipped).
-skl Skip Dr Linker (default: not skipped).
-skp Skip Parsing Headers (default: not skipped).
-skP Skip Generating Preprocessed files (default: not
skipped).
-ske Skip Entry point identification (default: not
skipped).
-skI Skip Generate Includes (default: not skipped).
-ski Skip IoctlCmdParser run (default: not skipped).
-skv Skip V4L2 ioctl processing (default: not skipped).
-skd Skip Device name finder (default: not skipped).
-f IOCTL_FINDER_OUT Path to the output folder where the ioctl command
finder output should be stored.
Скрипт собирает, связывает и запускает восстановление интерфейса на всех распознанных драйверах, поэтому это может занять значительное время (45–90 минут).
Приведённый выше скрипт выполняет следующие задачи в многопроцессорном режиме, чтобы задействовать все ядра CPU:
Все сгенерированные файлы биткода будут помещены в папку, указанную в аргументе -l.
Этот шаг занимает много времени, в зависимости от количества ядер.
Если вы уже выполнили этот шаг, его можно пропустить, передав -skb.
Этот этап выполняет связывание: он просматривает все файлы биткода, определяет связанные файлы биткода, которые нужно связать, и связывает их (с помощью llvm-link) в единый файл биткода (который будет сохранён рядом с соответствующим файлом биткода).
Как и на предыдущем шаге, его можно пропустить, передав -skl.
Этот шаг ищет объявления точек входа в файлах заголовков и сохраняет их конфигурацию в файле: hdr_file_config.txt в каталоге сборки LLVM.
Чтобы пропустить: -skp
Этот шаг определяет все точки входа во всех объединённых файлах биткода драйверов.
Результат будет сохранён в файле: entry_point_out.txt в каталоге сборки LLVM.
Пример содержимого файла entry_point_out.txt:
IOCTL:msm_lsm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-lsm-client.c:msm_lsm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
IOCTL:msm_pcm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c:msm_pcm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
Чтобы пропустить: -ske
Этот шаг запускает основной компонент восстановления интерфейса (IoctlCmdParser) на всех точках входа из файла entry_point_out.txt. Результат для каждой точки входа будет сохранён в папке, указанной в опции -f.
Чтобы пропустить: -ski
Теперь мы покажем пример от момента, когда у вас есть исходники ядра, до получения результатов восстановления интерфейса.
Мы загрузили ядро mediatek 33.2.A.3.123.tar.bz2. Сначала загрузите и распакуйте указанный выше файл.
Предположим, вы распаковали файл в папку: ~/mediatek_kernel
Установите Bear и выполните следующие шаги:
cd ~/mediatek_kernel
source ./env.sh
cd kernel-3.18
# следующий шаг может не потребоваться в зависимости от ядра
mkdir out
make O=out ARCH=arm64 tubads_defconfig
# генерация compile_commands.json
bear make -j8 O=out ARCH=arm64
cd <путь_к_репозиторию>/helper_scripts
python run_all.py -l ~/mediatek_kernel/llvm_bitcode_out -a 1 -c ~/mediatek_kernel/kernel-3.18/compile_commands.json -n 2 -o ~/mediatek_kernel/kernel-3.18/out -k ~/mediatek_kernel/kernel-3.18 -f ~/mediatek_kernel/ioctl_finder_out
Приведённая выше команда занимает довольно много времени (30 мин – 1 час).
Во-первых, все результаты анализа будут находиться в папке: ~/mediatek_kernel/ioctl_finder_out (аргумент, переданный опции -f), для каждой точки входа будет создан файл .txt, содержащий всю информацию о восстановленном интерфейсе.
Если вас интересует только информация об интерфейсе, и вам не нужно ничего остальное, мы рекомендуем использовать скрипт parse_interface_output.py. Этот скрипт преобразует сложный вывод прохода восстановления интерфейса в удобные JSON-файлы с чистым и единообразным форматом.
cd <путь_к_репозиторию>/helper_scripts
python parse_interface_output.py <каталог_ioctl_finder_out> <каталог_для_JSON_файлов>
Здесь <каталог_ioctl_finder_out> должен совпадать с папкой, которую вы передали опции -f, а <каталог_для_JSON_файлов> — это папка, где будут созданы JSON-файлы.
Вы можете использовать соответствующие JSON-файлы для восстановления интерфейса соответствующего ioctl.
-g (только если вы используете makeout.txt)Чтобы указать значение для опции -g, вам нужно знать имя бинарного файла *-gcc, используемого для компиляции ядра.
Простой способ узнать это — выполнить grep gcc makeout.txt, и вы увидите команды компиляции, из которых можно узнать имя бинарного файла *-gcc.
Для нашего примера выше, если вы выполните grep gcc makeout.txt для примера сборки, вы увидите много строк, подобных этой:
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
Итак, значением для -g должно быть aarch64-linux-android-gcc.
Если ядро собирается как 32-битное, то бинарный файл, скорее всего, будет arm-eabi-gcc
Для чипсетов Qualcomm (или msm) вы можете увидеть *gcc-wrapper.py вместо *.gcc, в этом случае следует указать *gcc-wrapper.py.
-aВ зависимости от типа чипсета необходимо указать соответствующий номер.
-oЭто путь к папке, переданной опции O= команды make во время сборки ядра.
Не все ядра требуют отдельного пути вывода. Вы можете собрать ядро, не передавая опцию O, и в этом случае НЕ СЛЕДУЕТ указывать значение для этой опции при запуске run_all.py.
Для ядер, собранных с помощью clang, в дополнение к указанным выше опциям укажите следующие (предполагая, что вы использовали compile_commands.json):
-isclang -clangp <ПУТЬ_К_CLANG_ИСПОЛЬЗОВАННОМУ_ДЛЯ_СБОРКИ_ЯДРА> -llvmlinkp <ПУТЬ_К_LLVM_LINK (будет в той же папке, что и clang)>
Прежде чем мы сможем начать фаззинг, необходимо немного обработать вывод с помощью наших (извините) парсеров исследовательского качества.
Они находятся здесь. Основной скрипт для запуска — run_all.py:
$ python run_all.py --help
usage: run_all.py [-h] -f F -o O [-n {manual,auto,hybrid}] [-m M]
run_all options
optional arguments:
-h, --help show this help message and exit
-f F Filename of the ioctl analysis output OR the entire
output directory created by the system
-o O Output directory to store the results. If this
directory does not exist it will be created
-n {manual,auto,hybrid}
Specify devname options. You can choose manual
(specify every name manually), auto (skip anything that
we don't identify a name for), or hybrid (if we
detected a name, we use it, else we ask the user)
-m M Enable multi-device output most ioctls only have one
applicable device node, but some may have multiple. (0
to disable)
Вам нужно передать -f каталог вывода анализа ioctl, например ~/mediatek_kernel/ioctl_finder_out.
-o — куда вы хотите сохранить результаты постобработки. Это будут легко читаемые XML-файлы (jpits).
-n — задаёт степень, в которой вы хотите полагаться на наше восстановление имени устройства.
Если вы не хотите выполнять никакую работу/поиск имён, вы можете указать auto.
Конечно, это приведёт к пропуску любого устройства, для которого мы не восстановили имя. Если вы хотите быть параноиком и не доверять ни одному из наших усилий по восстановлению (вполне разумно), вы можете использовать опцию manual, чтобы назвать каждое устройство самостоятельно.
hybrid — это комбинация обоих: мы будем называть устройство за вас, когда сможем, и вернёмся к вам, когда потерпим неудачу.
-m — иногда ioctl могут соответствовать более чем одному устройству (это распространено для ioctl v4l2/subdev). Поддержка этого включена по умолчанию, но требует взаимодействия с пользователем для указания количества устройств для каждого устройства. Если это слишком раздражает, вы можете отключить запрос, передав -m 0 (мы будем предполагать одно устройство для каждого ioctl).
После запуска в вашей выходной папке должна появиться папка для каждого ioctl.
MangoFuzz — это наш простой прототип фаззера, основанный на Peach (в частности, MozPeach).
Это не особенно сложный фаззер, но он находит ошибки. Он также был создан с возможностью лёгкого расширения. У этого фаззера 2 компонента: движок фаззинга и исполнитель. Исполнитель находится здесь, а движок фаззинга — здесь.
Исполнитель запускается на телефоне и прослушивает данные, которые будет отправлять ему движок фаззинга.
Просто скомпилируйте его для архитектуры вашего телефона, выполните adb push на телефон и запустите с указанием порта, который вы хотите слушать!
Взаимодействие с MangoFuzz довольно простое. Вам понадобится объект Engine и объект Parser, в который вы передадите свой движок.
Затем вы анализируете jpits с помощью Parser и запускаете Engine. Легко!
Мы предоставили несколько простых скриптов для запуска, чтобы вы могли начать.
Чтобы запустить фаззинг для конкретных драйверов, вы можете использовать runner.py на одной из папок ioctl в выходном каталоге (созданном нашими скриптами постобработки).
Например: ./runner.py -f honor8/out/chb -num 1000. Это указывает MangoFuzz выполнить 1000 итераций по всем парам значений команд ioctl, относящихся к драйверу/ioctl chb.
Если вместо этого мы хотим запустить фаззинг для всего устройства (телефона), вы можете использовать dev_runner.py. Например: ./dev_runner.py -f honor8/out -num 100.
Это будет циклически перебирать файлы драйверов, случайным образом переключаясь между ними, по 100 итераций каждый.
Обратите внимание, что до того, как движок фаззинга сможет взаимодействовать с телефоном, вам нужно будет настроить перенаправление портов через ADB, например adb forward tcp:2022 tcp:2022.