
Motor de fuzzing distribuido guiado por cobertura, compatible con objetivos de libFuzzer; escala a miles de trabajos concurrentes, emplea sanitizadores y destilación de corpus para descubrir errores.
¿Por qué no? Actualmente intentamos fuzzear objetivos muy grandes y muy lentos para los que libFuzzer, AFL y similares no escalan necesariamente bien. Para uno de nuestros ejemplos motivadores, ver SiliFuzz. Mientras trabajamos en Centipede, planeamos experimentar con nuevos enfoques de fuzzing diferencial a gran escala que los motores de fuzzing existentes no intentan hacer.
Características notables:
Soporte listo para usar para objetivos de fuzzing basados en libFuzzer. Para
usar tu LLVMFuzzerTestOneInput() favorito solo necesitas compilar tu objetivo
con las opciones de compilador y enlazador de Centipede.
Trabajo en curso. Probamos centipede en un equipo pequeño con un par de objetivos. A menos que formes parte del proyecto Centipede, o quieras ayudarnos, probablemente aún no quieras seguir leyendo.
Escalabilidad. La intención es poder ejecutar cualquier número de trabajos de forma concurrente, con muy poca sobrecarga de comunicación. Actualmente probamos con 100 trabajos locales y con 10 000 trabajos en un clúster.
Fuera de proceso. El objetivo se ejecuta en un proceso separado. Cualquier fallo en él no afectará al fuzzer. Centipede también se puede usar en el mismo proceso, pero este modo no es el objetivo principal. Si tu objetivo es pequeño y rápido, probablemente sigas queriendo libFuzzer.
La integración con los sanitizadores se logra mediante compilaciones separadas. Si durante el fuzzing quieres encontrar errores con ASAN, MSAN o TSAN, tendrás que proporcionar binarios separados para cada sanitizador, además de un binario principal para el propio Centipede. El binario principal no debe usar ninguno de los sanitizadores.
Ninguna parte de la interfaz interna es estable. Cualquier cosa puede cambiar en esta etapa.
Un programa que produce un flujo infinito de entradas para un objetivo y orquesta la ejecución.
Un binario, una biblioteca, una API, o más bien cualquier cosa que pueda consumir bytes como entrada y producir algún tipo de datos de cobertura como salida. Un objetivo de libFuzzer puede ser un objetivo de Centipede. Lee más aquí .
Una secuencia de bytes que se puede proporcionar a un objetivo. La entrada puede ser un conjunto arbitrario de bytes, o algún dato estructurado, p. ej. un proto serializado.
Un número que representa algún comportamiento único del objetivo. Por ejemplo, una característica 1234567 puede representar el hecho de que el bloque básico número 987 del objetivo se ha ejecutado 7 veces. Al ejecutar una entrada con el objetivo, el fuzzer recopila las características que se observaron durante la ejecución.
Un conjunto de características asociado a una entrada específica.
Información sobre el comportamiento del objetivo cuando ejecuta una entrada dada. La cobertura se representa normalmente como el conjunto de características que la entrada ha activado en el objetivo.
Una función que toma bytes como entrada y produce una pequeña mutación aleatoria de la entrada. Ver también: fuzzing sensible a la estructura .
Una función que sabe cómo introducir una entrada en un objetivo y obtener cobertura a cambio (es decir, ejecutar).
Un motor de fuzzing personalizable que permite al usuario sustituir el mutador y el ejecutor.
Una biblioteca que implementa la interfaz de ejecutor que espera el fuzzer Centipede. El runner sabe cómo ejecutar un objetivo instrumentado con sancov, recopilar la cobertura resultante y pasársela a Centipede. Los posibles objetivos de fuzzing de Centipede se pueden enlazar con esta biblioteca para que Centipede pueda ejecutarlos.
Un conjunto de entradas.
Un proceso de selección de un subconjunto de un corpus más grande, de modo que el subconjunto tenga las mismas características de cobertura que el corpus original.
Un archivo que representa un subconjunto del corpus y otro archivo que representa los conjuntos de características para ese mismo subconjunto del corpus.
Fusionar el shard B en el shard A significa: para cada entrada del shard B que tenga características que falten en el shard A, añadir esa entrada a A.
Un único proceso del fuzzer. Un trabajo escribe solo en un shard, pero puede leer varios shards.
Un directorio local o remoto que contiene datos producidos o consumidos por un fuzzer.
git clone https://github.com/google/centipede.git
cd centipede
CENTIPEDE_SRC=`pwd`
BIN_DIR=$CENTIPEDE_SRC/bazel-bin
bazel build -c opt :all
Lo que necesitarás para los siguientes pasos:
$BIN_DIR/centipede - el binario del motor (el fuzzer).$BIN_DIR/libcentipede_runner.pic.a - la biblioteca con la que necesitas enlazar tu objetivo de fuzzing (el runner).$CENTIPEDE_SRC/clang-flags.txt - las banderas de compilación de clang recomendadas para el objetivo.Puedes dejar estos archivos donde están o copiarlos a otro lugar.
Proporcionamos dos ejemplos de compilación del objetivo: un objetivo diminuto de un solo archivo y libpng. Una vez que hayas compilado tu objetivo, continúa con el paso de ejecución del objetivo de fuzzing.
Este ejemplo usa uno de los objetivos de fuzzing de ejemplo sencillos, también conocidos como puzzles, incluidos en el repositorio de Centipede.
NOTA: Los comandos siguientes usan las banderas de $CENTIPEDE_SRC/clang-flags.txt. Puedes elegir usar otro conjunto de banderas de instrumentación: clang-flags.txt solo proporciona una opción predeterminada simple.
FUZZ_TARGET=byte_cmp_4 # or any other source under $CENTIPEDE_SRC/puzzles
clang++ @$CENTIPEDE_SRC/clang-flags.txt -c $CENTIPEDE_SRC/puzzles/$FUZZ_TARGET.cc -o $BIN_DIR/$FUZZ_TARGET.o
Este paso enlaza el objetivo de fuzzing recién compilado con libcentipede_runner.pic.a y otras bibliotecas necesarias.
clang++ $BIN_DIR/$FUZZ_TARGET.o $BIN_DIR/libcentipede_runner.pic.a \
-ldl -lrt -lpthread -o $BIN_DIR/$FUZZ_TARGET
Salta al paso de ejecución.
LIBPNG_BRANCH=v1.6.37 # You can experiment with other branches if you'd like
git clone --branch $LIBPNG_BRANCH --single-branch https://github.com/glennrp/libpng.git
cd libpng
CC=clang CFLAGS=@$CENTIPEDE_SRC/clang-flags.txt ./configure --disable-shared
make -j
FUZZ_TARGET=libpng_read_fuzzer
clang++ -include cstdlib \
./contrib/oss-fuzz/$FUZZ_TARGET.cc \
./.libs/libpng16.a \
$BIN_DIR/libcentipede_runner.pic.a \
-ldl -lrt -lpthread -lz \
-o $BIN_DIR/$FUZZ_TARGET
Ejecutar localmente no ofrecerá la escala completa, pero puede ser útil durante la etapa de desarrollo del fuzzer. Recomendamos copiar tanto el fuzzer como el objetivo a un directorio local antes de la ejecución para evitar sobrecargar un sistema de archivos de red.
WD=$HOME/centipede_run
mkdir -p $WD
NOTA: Es posible que necesites añadir
llvm-symbolizer
a tu $PATH para que funcione parte de la funcionalidad de Centipede. El
simbolizador se puede instalar como parte de la distribución de
LLVM:
sudo apt install llvm
which llvm-symbolizer # normally /usr/bin/llvm-symbolizer
rm -rf $WD/*
$BIN_DIR/centipede --binary=$BIN_DIR/$FUZZ_TARGET --workdir=$WD --num_runs=100
Mira lo que hay en el directorio de trabajo
tree $WD
...
├── <fuzz target name>-d9d90139ee2ccc687f7c9d5821bcc04b8a847df5
│ └── features.0
└── corpus.0
ADVERTENCIA: No superes el número de núcleos de tu máquina con la bandera --j.
rm -rf $WD/*
$BIN_DIR/centipede --binary=$BIN_DIR/$FUZZ_TARGET --workdir=$WD --num_runs=100 --j=5
Mira lo que hay en el directorio de trabajo:
tree $WD
...
├── <fuzz target name>-d9d90139ee2ccc687f7c9d5821bcc04b8a847df5
│ ├── features.0
│ ├── features.1
│ ├── features.2
│ ├── features.3
│ └── features.4
├── corpus.0
├── corpus.1
├── corpus.2
├── corpus.3
└── corpus.4
Normalmente, cada shard de Centipede no cubre todas las características que cubre el corpus completo. Para destilar el corpus, un proceso de Centipede necesitará leer todos los shards. Actualmente, la destilación funciona así:
--distill_shards=N.
Esto hará que los primeros N trabajos produzcan N archivos de corpus
destilado independientes (uno por trabajo). Cada uno de los corpora
destilados debería tener las mismas características que el corpus completo,
pero las entradas podrían ser muy diferentes entre estos corpora destilados.Si además necesitas exportar el corpus destilado a un directorio de estilo
libFuzzer (directorio local con un archivo por entrada), añade
--corpus_dir=DIR.
Centipede genera un informe de cobertura simple en forma de archivo de texto. El
shard 123 genera un archivo workdir/coverage-report-BINARY.000123.txt antes de
que comience el fuzzing real, es decir, el informe refleja la cobertura observada
por el shard 123 después de cargar el corpus.
El informe muestra las funciones que están completamente cubiertas (todas las aristas del flujo de control se observan al menos una vez), no cubiertas o parcialmente cubiertas. Para las funciones parcialmente cubiertas, el informe contiene información simbólica de todas las aristas cubiertas y no cubiertas.
El informe tendrá un aspecto similar a este:
FULL: FUNCTION_A a.cc:1:0
NONE: FUNCTION_BB bb.cc:1:0
PARTIAL: FUNCTION_CCC ccc.cc:1:0
+ FUNCTION_CCC ccc.cc:1:0
- FUNCTION_CCC ccc.cc:2:0
- FUNCTION_CCC ccc.cc:3:0
Por determinar