
Fuzzer para controladores del kernel de Linux
Este repositorio contiene todas las fuentes (incluyendo scripts de configuración) que necesitas para poner difuze en funcionamiento.
Ubuntu >= 14.04.5 LTS
Consulta el readme
Como se explica en nuestro artículo, hay dos componentes principales de difuze: Recuperación de Interfaz y Motor de Fuzzing
El mecanismo de recuperación de interfaz se basa en pases de análisis de LLVM. Cada paso de la recuperación de interfaz está escrito como pases individuales. Sigue las instrucciones a continuación para poner en funcionamiento la Recuperación de Interfaz.
Este paso se encarga de instalar LLVM y c2xml:
Primero, asegúrate de tener libxml (requerido para c2xml):
sudo apt-get install libxml2-dev
sudo pip install lxml
A continuación, hemos creado un único script que descarga y construye todas las herramientas necesarias.
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.
Ejemplo:
python setup_difuze.py -o difuze_deps
Para completar la configuración también necesitas modificaciones en tu variable de entorno PATH local. El script de configuración te dará los cambios exactos que necesitas hacer.
Esto depende de la finalización exitosa de la Configuración. Tenemos un único script que compila todo, de nada.
cd InterfaceHandlers
./build.sh
Esto depende de la finalización exitosa de la Compilación. Para ejecutar los componentes de Recuperación de Interfaz en controladores del kernel, primero necesitamos convertir los controladores a bitcode de LLVM.
Primero, necesitamos tener un kernel compilable. Esto significa que deberías ser capaz de compilar el kernel usando la configuración de compilación habitual, es decir, make.
Primero capturamos la salida del comando make, de esta salida extraemos el comando de compilación exacto.
makebear make <all the options to make>
bear make -j8Esto generará un archivo compile_commands.json en el directorio actual.
Simplemente pasa V=1 y redirige la salida al archivo.
Ejemplo:
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
NOTA: NO USES MÚLTIPLES PROCESOS, es decir, -j. Ejecutar en modo multiproceso desordenará el archivo de salida ya que múltiples procesos intentan escribir en el archivo de salida.
Eso es todo. A continuación, en el siguiente paso nuestro script toma el makeout.txt generado y ejecuta la Recuperación de Interfaz en todos los controladores reconocidos.
Todos los diversos pasos de la Recuperación de Interfaz están envueltos en un único script helper_scripts/run_all.py
Cómo ejecutar:
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.
El script compila, enlaza y ejecuta la Recuperación de Interfaz en todos los controladores reconocidos, por lo que puede tomar un tiempo considerable (45 min-90 min).
El script anterior realiza las siguientes tareas en modo multiprocesador para hacer uso de todos los núcleos de la CPU:
Todos los archivos de bitcode generados se colocarán en la carpeta proporcionada al argumento -l.
Este paso toma un tiempo considerable, dependiendo del número de núcleos que tengas.
Por lo tanto, si ya has realizado este paso, puedes saltarlo pasando -skb.
Esto realiza el enlace, recorre todos los archivos de bitcode e identifica los archivos de bitcode relacionados que necesitan ser enlazados y los enlaza (usando llvm-link) en un archivo de bitcode consolidado (que se almacenará junto al archivo de bitcode correspondiente).
Similar al paso anterior, puedes saltar este paso pasando -skl.
Este paso busca las declaraciones de puntos de entrada en los archivos de encabezado y almacena su configuración en el archivo: hdr_file_config.txt bajo el directorio de compilación de LLVM.
Para saltar: -skp
Este paso identifica todos los puntos de entrada en todos los archivos de bitcode consolidados del controlador.
La salida se almacenará en el archivo: entry_point_out.txt bajo el directorio de compilación de LLVM.
Ejemplo de contenido en el archivo 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
Para saltar: -ske
Este paso ejecutará el componente principal de Recuperación de Interfaz (IoctlCmdParser) en todos los puntos de entrada del archivo entry_point_out.txt. La salida para cada punto de entrada se almacenará en la carpeta proporcionada para la opción -f.
Para saltar: -ski
Ahora, mostraremos un ejemplo desde el punto donde tienes las fuentes del kernel hasta el punto de obtener resultados de Recuperación de Interfaz.
Hemos subido un kernel mediatek 33.2.A.3.123.tar.bz2. Primero descarga y extrae el archivo anterior.
Supongamos que extrajiste el archivo anterior en una carpeta llamada: ~/mediatek_kernel
Instala Bear y sigue los pasos a continuación:
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
El comando anterior toma bastante tiempo (30 min - 1h).
Primero, todos los resultados del análisis estarán en la carpeta: ~/mediatek_kernel/ioctl_finder_out (argumento dado a la opción -f), para cada punto de entrada se creará un archivo .txt, que contiene toda la información sobre la interfaz recuperada.
Si estás interesado únicamente en la información sobre la interfaz y no te importa el resto, te recomendamos usar el script parse_interface_output.py. Este script convierte la salida loca del pase de Recuperación de Interfaz en bonitos archivos json con un formato limpio y consistente.
cd <repo_path>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <output_directory_for_json_files>
Aquí <ioctl_finder_out_dir> debe ser el mismo que la carpeta que proporcionaste a la opción -f y <output_directory_for_json_files> es la carpeta donde se deben crear los archivos json.
Puedes usar los archivos json correspondientes para la recuperación de interfaz del ioctl correspondiente.
-g (solo si usas makeout.txt)Para proporcionar el valor para la opción -g necesitas saber el nombre del binario *-gcc utilizado para compilar el kernel.
Una forma fácil de saber esto sería buscar (grep) gcc en makeout.txt y verás comandos del compilador de los cuales puedes conocer el nombre del binario *-gcc.
Para nuestro ejemplo anterior, si haces grep gcc makeout.txt para la compilación de ejemplo, verás muchas líneas como la siguiente:
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
Por lo tanto, el valor para -g debe ser aarch64-linux-android-gcc.
Si el kernel a compilar es de 32 bits, entonces el binario muy probablemente será arm-eabi-gcc
Para chipsets Qualcomm (o msm), puedes ver *gcc-wrapper.py en lugar de *.gcc, en cuyo caso debes proporcionar *gcc-wrapper.py.
-aDependiendo del tipo de chipset, debes proporcionar el número correspondiente.
-oEsta es la ruta de la carpeta proporcionada a la opción O= para el comando make durante la compilación del kernel.
No todos los kernels necesitan una ruta de salida separada. Puedes compilar el kernel sin proporcionar la opción O, en cuyo caso NO DEBES proporcionar valor para esa opción al ejecutar run_all.py.
Para kernels compilados usando clang, además de las opciones anteriores, especifica las siguientes opciones (suponiendo que usaste 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)>
Antes de poder comenzar el fuzzing necesitamos procesar la salida un poco con nuestros analizadores de calidad investigativa (lo siento).
Estos se encuentran aquí. El script principal a ejecutar será 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)
Querrás pasar -f el directorio de salida del análisis ioctl, por ejemplo ~/mediatek_kernel/ioctl_finder_out.
-o es donde almacenar los resultados postprocesados. Estos serán archivos XML fáciles de digerir (jpits).
-n Especifica el sistema en qué grado deseas confiar en nuestra recuperación de nombres de dispositivos.
Si no quieres hacer ningún trabajo/búsqueda de nombres, puedes especificar auto.
Esto por supuesto tiene el costo de omitir cualquier dispositivo para el cual no recuperemos un nombre. Si quieres ser paranoico y no confiar en ninguno de nuestros esfuerzos de recuperación (totalmente razonable), puedes usar la opción manual para nombrar cada dispositivo tú mismo.
hybrid entonces es una combinación de ambos: nombraremos el dispositivo por ti cuando podamos, y recurriremos a ti cuando hayamos fallado.
-m A veces los ioctls pueden corresponder a más de un dispositivo (esto es común con ioctls v4l2/subdev, por ejemplo). El soporte para esto está habilitado por defecto, pero requiere interacción del usuario para especificar el número de dispositivos para cada dispositivo. Si esto te parece demasiado molesto, puedes deshabilitar la indicación pasando -m 0 (asumiremos un solo dispositivo para cada ioctl).
Después de ejecutar, deberías tener, en tu carpeta de salida, una carpeta para cada ioctl.
MangoFuzz es nuestro simple fuzzer prototipo y está basado en Peach (específicamente MozPeach).
No es un fuzzer particularmente sofisticado pero encuentra errores. También fue construido para ser fácilmente expandible. Hay 2 componentes en este fuzzer, el motor de fuzzing y el ejecutor. El ejecutor se puede encontrar aquí, y el motor de fuzzing se puede encontrar aquí.
El ejecutor se ejecuta en el teléfono, escuchando datos que el motor de fuzzing le enviará.
Simplemente compílalo para la arquitectura de tu teléfono, haz adb push al teléfono y ejecútalo con el puerto en el que quieras que escuche.
La interfaz con MangoFuzz es bastante simple. Necesitarás un objeto Engine y un objeto Parser, en los que alimentarás tu motor.
A partir de aquí, analizas jpits con tu Parser, y luego ejecutas el Engine. ¡Fácil!
Hemos proporcionado algunos scripts de ejecución simples para empezar.
Para ejecutar contra controladores específicos puedes usar runner.py en una de las carpetas ioctl en el directorio de salida (creado por nuestros scripts de postprocesamiento).
ej. ./runner.py -f honor8/out/chb -num 1000. Esto le dice a MangoFuzz que ejecute 1000 iteraciones contra todos los pares de valor de comando ioctl pertenecientes al ioctl/controlador chb.
Si en cambio queremos ejecutar contra un dispositivo completo (teléfono), puedes usar dev_runner.py. ej. ./dev_runner.py -f honor8/out -num 100.
Esto seguirá iterando sobre los archivos del controlador, cambiando aleatoriamente entre ellos durante 100 iteraciones cada uno.
Ten en cuenta que antes de que el motor de fuzzing pueda comunicarse con el teléfono, necesitarás usar ADB para configurar el reenvío de puertos, ej. adb forward tcp:2022 tcp:2022