
Fuzzing de Sistemas Embebidos usando Puntos de Interrupción de Hardware
Este es el código complementario del artículo: 'Fuzzing de Sistemas Embebidos usando Interfaces de Depurador'. Una preimpresión del artículo se puede encontrar aquí https://publications.cispa.saarland/3950/. El código permite a los usuarios reproducir y extender los resultados reportados en el artículo. Por favor, cite el artículo anterior al reportar, reproducir o extender los resultados.
.
├── benchmark # Scripts para construir el conjunto de pruebas de fuzzer de Google y ejecutar experimentos
├── dependencies # Contiene un Makefile para instalar dependencias de GDBFuzz
├── evaluation # Datos de experimentos en bruto, presentados en el artículo
├── example_firmware # Aplicaciones de ejemplo embebidas, utilizadas para la evaluación
├── example_programs # Contiene un programa de ejemplo compilado y configuraciones para probar GDBFuzz
├── src # Contiene la implementación de GDBFuzz
├── Dockerfile # Para crear una imagen Docker con todas las dependencias de GDBFuzz instaladas
├── LICENSE # Licencia
├── Makefile # Makefile para crear la imagen docker o instalar GDBFuzz localmente
└── README.md # Este archivo README
La idea de GDBFuzz es aprovechar los puntos de interrupción por hardware de los microcontroladores como retroalimentación para el fuzzing guiado por cobertura. Por lo tanto, GDB se utiliza como una interfaz genérica para permitir una amplia aplicabilidad. Para el análisis binario del firmware, se utiliza Ghidra. El código contiene una configuración de evaluación comparativa para evaluar el método. Además, se incluyen archivos de firmware de ejemplo.
GDBFuzz permite el fuzzing guiado por cobertura para sistemas embebidos, pero, con fines de evaluación, también puede fuzzing de aplicaciones de usuario arbitrarias. Para el fuzzing en microcontroladores, recomendamos una instalación local de GDBFuzz para poder enviar datos de fuzzing al dispositivo bajo prueba sin problemas.
GDBFuzz ha sido probado en Ubuntu 20.04 LTS y Raspberry Pi OS de 32 bits. Los requisitos previos son java y python3. Primero, cree un nuevo entorno virtual e instale todas las dependencias.
virtualenv .venv
source .venv/bin/activate
make
chmod a+x ./src/GDBFuzz/main.py
GDBFuzz lee la configuración de un archivo de configuración con las siguientes claves.
[SUT]
# Ruta al archivo binario del SUT.
# Esto puede ser, por ejemplo, un archivo .elf o un archivo .bin.
binary_file_path = <path>
# Dirección del nodo raíz del CFG.
# Los puntos de interrupción se colocan en los nodos de este CFG.
# ej. 'LLVMFuzzerTestOneInput' o 'main'
entrypoint = <entrypoint>
# Número de entradas que deben ejecutarse sin alcanzar un punto de interrupción hasta
# que se rotan los puntos de interrupción.
until_rotate_breakpoints = <number>
# Número máximo de puntos de interrupción que se pueden colocar en un momento dado.
max_breakpoints = <number>
# Lista negra de funciones que deben ignorarse.
# ignore_functions es una lista separada por espacios de nombres de funciones, ej. 'malloc free'.
ignore_functions = <space separated list>
# Uno de {Hardware, QEMU, SUTRunsOnHost}
# Hardware: Un componente externo inicia un servidor gdb y GDBFuzz puede conectarse a este servidor gdb.
# QEMU: GDBFuzz inicia QEMU. QEMU emula binary_file_path e inicia gdbserver.
# SUTRunsOnHost: GDBFuzz inicia el programa objetivo dentro de GDB.
target_mode = <mode>
# Establezca esto en False si desea iniciar ghidra, analizar el SUT,
# e iniciar el puente ghidra bridge manualmente.
start_ghidra = True
# Lista separada por espacios de direcciones donde se colocan puntos de interrupción por software (para código de manejo de errores). La ejecución de estos se considera un fallo.
# Ejemplo: software_breakpoint_addresses = 0x123 0x432
software_breakpoint_addresses =
# Si todos los puntos de interrupción por software activados se consideran como fallo
consider_sw_breakpoint_as_error = False
[SUTConnection]
# La clase 'SUT_connection_class' en el archivo 'SUT_connection_path' implementa
# cómo se envían las entradas al SUT.
# Las entradas pueden, por ejemplo, enviarse a través de Wi-Fi, Serial, Bluetooth, ...
# Esta clase debe heredar de ./connections/SUTConnection.py.
# Consulte ./connections/SUTConnection.py para obtener más información.
SUT_connection_file = FIFOConnection.py
[GDB]
path_to_gdb = gdb-multiarch
# Escrito en address:port
gdb_server_address = localhost:4242
[Fuzzer]
# En Bytes
maximum_input_length = 100000
# En segundos
single_run_timeout = 20
# En segundos
total_runtime = 3600
# Opcional
# Ruta a un directorio donde cada archivo contiene una semilla. Si no desea usar semillas, deje el valor vacío.
seeds_directory =
[BreakpointStrategy]
# Las estrategias para elegir bloques básicos se encuentran en
# 'src/GDBFuzz/breakpoint_strategies/'
# Para el artículo utilizamos las siguientes estrategias
# 'RandomBasicBlockStrategy.py' - Elegir aleatoriamente bloques básicos no alcanzados
# 'RandomBasicBlockNoDomStrategy.py' - Como el anterior, pero no usa relaciones de dominancia para derivar nodos transitivamente alcanzados.
# 'RandomBasicBlockNoCorpusStrategy.py' - Como el primero, pero evita hacer crecer el corpus de entrada y por lo tanto se comporta como fuzzing de caja negra con medición de cobertura.
# 'BlackboxStrategy.py', - No coloca ningún punto de interrupción
breakpoint_strategy_file = RandomBasicBlockStrategy.py
[Dependencies]
path_to_qemu = dependencies/qemu/build/x86_64-linux-user/qemu-x86_64
path_to_ghidra = dependencies/ghidra
[LogsAndVisualizations]
# Uno de {DEBUG, INFO, WARNING, ERROR, CRITICAL}
loglevel = INFO
# Ruta a un directorio donde se almacenan los archivos de salida (ej. gráficos, archivos de registro).
output_directory = ./output
# Si se establece en True, un cliente MQTT envía elementos de la interfaz de usuario (ej. gráficos)
enable_UI = False
Un archivo de configuración de ejemplo se encuentra en ./example_programs/ junto con un programa de ejemplo que fue compilado usando nuestro harness de fuzzing en benchmark/benchSUTs/GDBFuzz_wrapper/common/. Inicie el fuzzing durante una hora con el siguiente comando.
chmod a+x ./example_programs/json-2017-02-12
./src/GDBFuzz/main.py --config ./example_programs/fuzz_json.cfg
Primero vemos la salida de Ghidra analizando el ejecutable binario y posteriormente mensajes cuando los puntos de interrupción son reubicados o alcanzados.
Dependiendo del output_directory especificado en el archivo de configuración, ahora debería haber una carpeta trial-0 con la siguiente estructura
.
├── corpus # Una carpeta que contiene el corpus de entrada.
├── crashes # Una carpeta que contiene entradas que causan fallos, si las hay.
├── cfg # El grafo de flujo de control como lista de adyacencia.
├── fuzzer_stats # Estadísticas de la campaña de fuzzing.
├── plot_data # Tabla que muestra en qué tiempo relativo de la campaña de fuzzing se alcanzó cada bloque básico.
├── reverse_cfg # El grafo de flujo de control inverso.
Al establecer start_ghidra = False en el archivo de configuración, GDBFuzz se conecta a una instancia de Ghidra ejecutándose en modo GUI. Por lo tanto, el plugin ghidra_bridge debe iniciarse manualmente desde el administrador de scripts. Durante el fuzzing, los bloques de programa alcanzados se resaltan en verde.