
Fuzzer pour pilotes du noyau Linux
Ce dépôt contient toutes les sources (scripts d'installation inclus) dont vous avez besoin pour faire fonctionner difuze.
Ubuntu >= 14.04.5 LTS
Consultez le readme
Comme expliqué dans notre article, il y a deux composants principaux de difuze : Récupération d'interface et Moteur de fuzzing
Le mécanisme de récupération d'interface est basé sur les passes d'analyse LLVM. Chaque étape de la récupération d'interface est écrite sous forme de passes individuelles. Suivez les instructions ci-dessous pour faire fonctionner la Récupération d'interface.
Cette étape s'occupe de l'installation de LLVM et de c2xml :
Tout d'abord, assurez-vous d'avoir libxml (nécessaire pour c2xml) :
sudo apt-get install libxml2-dev
sudo pip install lxml
Ensuite, nous avons créé un script unique qui télécharge et construit tous les outils nécessaires.
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.
Exemple :
python setup_difuze.py -o difuze_deps
Pour terminer la configuration, vous devez également modifier votre variable d'environnement locale PATH. Le script de configuration vous indiquera exactement les modifications à effectuer.
Cela dépend de la réussite de la Configuration. Nous avons un seul script qui compile tout, de rien.
cd InterfaceHandlers
./build.sh
Cela dépend de la réussite de la Compilation. Pour exécuter les composants de récupération d'interface sur les pilotes du noyau, nous devons d'abord convertir les pilotes en bitcode LLVM.
Tout d'abord, nous avons besoin d'un noyau compilable. Cela signifie que vous devez pouvoir compiler le noyau en utilisant la configuration de compilation habituelle, c'est-à-dire make.
Nous capturons d'abord la sortie de la commande make, à partir de laquelle nous extrayons la commande de compilation exacte.
makebear make <all the options to make>
bear make -j8Cela générera un fichier compile_commands.json dans le répertoire courant.
Il suffit de passer V=1 et de rediriger la sortie vers le fichier.
Exemple :
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
NOTE : N'UTILISEZ PAS DE PROCESSUS MULTIPLES, c'est-à-dire -j. L'exécution en mode multi-processus perturbera le fichier de sortie car plusieurs processus tentent d'écrire dans le fichier de sortie.
Voilà. Ensuite, dans l'étape suivante, notre script prend le makeout.txt généré et exécute la récupération d'interface sur tous les pilotes reconnus.
Toutes les différentes étapes de la récupération d'interface sont regroupées dans un seul script helper_scripts/run_all.py
Comment exécuter :
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.
Le script compile, lie et exécute la récupération d'interface sur tous les pilotes reconnus, il peut donc prendre un temps considérable (45 min à 90 min).
Le script ci-dessus effectue les tâches suivantes en mode multiprocesseur pour utiliser tous les cœurs du CPU :
Tous les fichiers bitcode générés seront placés dans le dossier fourni à l'argument -l.
Cette étape prend un temps considérable, en fonction du nombre de cœurs dont vous disposez.
Donc, si vous avez déjà effectué cette étape, vous pouvez la sauter en passant -skb.
Cela effectue la liaison, il parcourt tous les fichiers bitcode, identifie les fichiers bitcode associés qui doivent être liés et les lie (en utilisant llvm-link) en un fichier bitcode consolidé (qui sera stocké à côté du fichier bitcode correspondant).
Comme pour l'étape précédente, vous pouvez sauter cette étape en passant -skl.
Cette étape recherche les déclarations de points d'entrée dans les fichiers d'en-tête et stocke leur configuration dans le fichier : hdr_file_config.txt sous le répertoire de compilation LLVM.
Pour sauter : -skp
Cette étape identifie tous les points d'entrée dans tous les fichiers bitcode consolidés des pilotes.
La sortie sera stockée dans le fichier : entry_point_out.txt sous le répertoire de compilation LLVM.
Exemple de contenu du fichier 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
Pour sauter : -ske
Cette étape exécutera le composant principal de récupération d'interface (IoctlCmdParser) sur tous les points d'entrée du fichier entry_point_out.txt. La sortie pour chaque point d'entrée sera stockée dans le dossier fourni pour l'option -f.
Pour sauter : -ski
Maintenant, nous allons montrer un exemple depuis le point où vous avez les sources du noyau jusqu'à l'obtention des résultats de la récupération d'interface.
Nous avons téléchargé un noyau mediatek 33.2.A.3.123.tar.bz2. Téléchargez et extrayez d'abord le fichier ci-dessus.
Supposons que vous ayez extrait le fichier ci-dessus dans un dossier appelé : ~/mediatek_kernel
Installez Bear et suivez les étapes ci-dessous :
cd ~/mediatek_kernel
source ./env.sh
cd kernel-3.18
# the following step may not be needed depending on the kernel
mkdir out
make O=out ARCH=arm64 tubads_defconfig
# generating compile_commands.json
bear make -j8 O=out ARCH=arm64
cd <repo_path>/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
La commande ci-dessus prend un certain temps (30 min à 1 heure).
Tout d'abord, tous les résultats d'analyse seront dans le dossier : ~/mediatek_kernel/ioctl_finder_out (argument donné à l'option -f), pour chaque point d'entrée un fichier .txt sera créé, qui contient toutes les informations sur l'interface récupérée.
Si vous êtes intéressé uniquement par les informations sur l'interface et que vous ne vous souciez de rien d'autre, nous vous recommandons d'utiliser le script parse_interface_output.py. Ce script convertit la sortie désordonnée de la passe de récupération d'interface en de jolis fichiers json avec un format propre et cohérent.
cd <repo_path>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <output_directory_for_json_files>
Ici, <ioctl_finder_out_dir> doit être le même que le dossier que vous avez fourni à l'option -f et <output_directory_for_json_files> est le dossier où les fichiers json doivent être créés.
Vous pouvez utiliser les fichiers json correspondants pour la récupération d'interface de l'ioctl correspondant.
-g (uniquement si vous utilisez makeout.txt)Pour fournir la valeur de l'option -g, vous devez connaître le nom du binaire *-gcc utilisé pour compiler le noyau.
Un moyen facile de le savoir serait de grep gcc dans makeout.txt et vous verrez des commandes de compilation à partir desquelles vous pourrez connaître le nom du binaire *-gcc.
Pour notre exemple ci-dessus, si vous faites grep gcc makeout.txt pour la compilation exemple, vous verrez beaucoup de lignes comme ci-dessous :
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
Donc, la valeur pour -g devrait être aarch64-linux-android-gcc.
Si le noyau à compiler est en 32 bits, alors le binaire sera très probablement arm-eabi-gcc
Pour les chipsets Qualcomm (ou msm), vous pouvez voir *gcc-wrapper.py au lieu de *.gcc, auquel cas vous devez fournir *gcc-wrapper.py.
-aSelon le type de chipset, vous devez fournir le numéro correspondant.
-oC'est le chemin du dossier fourni à l'option O= pour la commande make lors de la compilation du noyau.
Tous les noyaux n'ont pas besoin d'un chemin de sortie séparé. Vous pouvez compiler le noyau sans fournir d'option O, auquel cas vous NE DEVEZ PAS fournir de valeur pour cette option lors de l'exécution de run_all.py.
Pour les noyaux compilés avec clang, en plus des options ci-dessus, veuillez spécifier les options suivantes (en supposant que vous avez utilisé compile_commands.json) :
-isclang -clangp <PATH_TO_THE_CLANG_USED_TO_BUILD_THE_KERNEL> -llvmlinkp <PATH_TO_THE_LLVM_LINK (will be in the same folder as clang)>
Avant de pouvoir commencer le fuzzing, nous devons traiter un peu la sortie avec nos analyseurs de qualité de recherche (désolé).
Ils se trouvent ici. Le script principal à exécuter sera 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)
Vous voudrez passer -f le répertoire de sortie de l'analyse ioctl, par exemple ~/mediatek_kernel/ioctl_finder_out.
-o est l'endroit où stocker les résultats post-traités. Ce seront des fichiers XML faciles à digérer (jpits).
-n Spécifie dans quelle mesure vous voulez vous fier à notre récupération de nom de périphérique.
Si vous ne voulez pas faire de travail/recherche de nom, vous pouvez spécifier auto.
Cela a bien sûr pour inconvénient de sauter tout périphérique pour lequel nous ne récupérons pas de nom. Si vous voulez être paranoïaque et ne pas faire confiance à nos efforts de récupération (tout à fait raisonnable), vous pouvez utiliser l'option manual pour nommer chaque périphérique vous-même.
hybrid est alors une combinaison des deux -- nous nommerons le périphérique pour vous quand nous le pouvons, et nous vous laisserons faire en cas d'échec.
-m Parfois, les ioctls peuvent correspondre à plus d'un périphérique (c'est courant avec les ioctls v4l2/subdev par exemple). Le support pour cela est activé par défaut, mais il nécessite une interaction de l'utilisateur pour spécifier le nombre de périphériques pour chaque périphérique. Si cela vous embête trop, vous pouvez désactiver la demande en passant -m 0 (nous supposerons un seul périphérique pour chaque ioctl).
Après l'exécution, vous devriez avoir, dans votre dossier de sortie, un dossier pour chaque ioctl.
MangoFuzz est notre simple fuzzer prototype et est basé sur Peach (plus précisément MozPeach).
Ce n'est pas un fuzzer particulièrement sophistiqué mais il trouve des bugs. Il a également été construit pour être facilement extensible. Il y a 2 composants dans ce fuzzer : le moteur de fuzzing et l'exécuteur. L'exécuteur se trouve ici et le moteur de fuzzing se trouve ici.
L'exécuteur s'exécute sur le téléphone, en écoute des données que le moteur de fuzzing lui enverra.
Compilez-le simplement pour l'architecture de votre téléphone, faites adb push sur le téléphone, et exécutez-le avec le port sur lequel vous voulez qu'il écoute !
L'interface avec MangoFuzz est assez simple. Vous voudrez un objet Engine et un objet Parser, dans lesquels vous alimenterez votre moteur.
À partir de là, vous analysez les jpits avec votre Parser, puis exécutez le Engine. Facile !
Nous avons fourni quelques scripts d'exécution simples pour vous aider à démarrer.
Pour exécuter contre des pilotes spécifiques, vous pouvez utiliser runner.py sur l'un des dossiers ioctl dans le répertoire de sortie (créé par nos scripts de post-traitement).
Par exemple : ./runner.py -f honor8/out/chb -num 1000. Cela indique à MangoFuzz d'exécuter 1000 itérations contre toutes les paires de valeurs de commande ioctl relatives à l'ioctl/pilote chb.
Si au contraire nous voulons exécuter contre un appareil entier (téléphone), vous pouvez utiliser dev_runner.py. Par exemple : ./dev_runner.py -f honor8/out -num 100.
Cela continuera à boucler sur les fichiers de pilotes, en basculant aléatoirement entre eux pour 100 itérations chacun.
Notez qu'avant que le moteur de fuzzing puisse communiquer avec le téléphone, vous devrez utiliser ADB pour configurer le transfert de port, par exemple adb forward tcp:2022 tcp:2022