AFL++
AFL++ es un fork del fuzzer AFL original que ofrece un mejor rendimiento de fuzzing y características más avanzadas mientras mantiene la estabilidad. Un beneficio importante sobre libFuzzer es que AFL++ tiene soporte estable para ejecutar campañas de fuzzing en múltiples núcleos, lo que lo hace ideal para esfuerzos de fuzzing a gran escala.
Cuándo Usar
| Fuzzer | Mejor Para | Complejidad |
|---|---|---|
| AFL++ | Fuzzing multi-núcleo, mutaciones diversas, proyectos maduros | Media |
| libFuzzer | Configuración rápida, mono-hilo, harnesses simples | Baja |
| LibAFL | Fuzzers personalizados, investigación, casos de uso avanzados | Alta |
Elige AFL++ cuando:
- Necesites fuzzing multi-núcleo para maximizar el rendimiento
- Tu proyecto se pueda compilar con Clang o GCC
- Quieras estrategias de mutación diversas y herramientas maduras
- libFuzzer haya llegado a un punto muerto y necesites más cobertura
- Estés fuzzeando bases de código de producción que se beneficien de la ejecución paralela
Inicio Rápido
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Llama a tu código con los datos proporcionados por el fuzzer
check_buf((char*)data, size);
return 0;
}
Compila y ejecuta:
# Primero configura el script wrapper de AFL++ (ver Instalación)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Instalación
AFL++ tiene muchas dependencias, incluyendo LLVM, Python y Rust. Recomendamos usar una distribución actual de Debian o Ubuntu para fuzzing con AFL++.
| Método | Cuándo Usar | Compiladores Soportados |
|---|---|---|
| Repositorios de Ubuntu/Debian | Ubuntu reciente, solo características básicas | Ubuntu 23.10: Clang 14 y GCC 13Debian 12: Clang 14 y GCC 12 |
| Docker (desde Docker Hub) | Versión específica de AFL++, soporte para Apple Silicon | Desde 4.35c: Clang 19 y GCC 11 |
| Docker (desde el código fuente) | Probar características no publicadas, aplicar parches | Configurable en Dockerfile |
| Desde el código fuente | Evitar Docker, necesitar parches específicos | Ajustable mediante la variable de entorno LLVM_CONFIG |
Ubuntu/Debian
Antes de instalar afl++, verifica la dependencia de la versión de clang del paquete con apt-cache show afl++, e instala la versión correspondiente de lld (por ejemplo, lld-17).
apt install afl++ lld-17
Docker (desde Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker (desde el código fuente)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
Desde el código fuente
Consulta el Dockerfile para conocer los requisitos de versión de Ubuntu y las dependencias. Configura LLVM_CONFIG para especificar la versión de Clang (por ejemplo, llvm-config-18).
Configuración del Script Wrapper
Crea un script wrapper para ejecutar AFL++ en el host o en Docker:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -ti \
--privileged \
-v ./:/src \
--rm \
--name afl_fuzzing \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Uso: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
Advertencia de Seguridad: Los scripts afl-system-config y afl-persistent-config requieren privilegios de root y deshabilitan funciones de seguridad del sistema operativo. No hagas fuzzing en sistemas de producción ni en tu entorno de desarrollo. Usa una máquina virtual dedicada.
Configuración del Sistema
Ejecuta después de cada reinicio para obtener hasta un 15% más de ejecuciones por segundo:
./afl++ <host/docker> afl-system-config
Para obtener el máximo rendimiento, deshabilita las mitigaciones de seguridad del kernel (requiere el cargador de arranque grub, no compatible con Docker):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ <host/docker> afl-system-config
Verifica con cat /proc/cmdline; la salida debe incluir mitigations=off.
Cómo Escribir un Harness
Estructura del Harness
AFL++ es compatible con harnesses estilo libFuzzer:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Valida el tamaño de la entrada si es necesario
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Llama a la función objetivo con los datos del fuzzer
target_function(data, size);
// 3. Devuelve 0 (distinto de cero reservado para uso futuro)
return 0;
}
Reglas del Harness
| Haz | No Hagas |
|---|---|
| Reinicia el estado global entre ejecuciones | Dependas del estado de ejecuciones anteriores |
| Maneja los casos límite con elegancia | Salgas en caso de entrada no válida |
| Mantén el harness determinista | Uses generadores de números aleatorios |
| Libera la memoria asignada | Crees fugas de memoria |
| Valida los tamaños de entrada | Proceses entrada sin límites |
Ver También: Para técnicas detalladas de escritura de harnesses, patrones para manejar entradas complejas y estrategias avanzadas, consulta la skill técnica fuzz-harness-writing.
Compilación
AFL++ ofrece múltiples modos de compilación con diferentes ventajas y desventajas.
Árbol de Decisión del Modo de Compilación
Elige tu modo de compilación:
- Modo LTO (
afl-clang-lto): Mejor rendimiento e instrumentación. Pruébalo primero. - Modo LLVM (
afl-clang-fast): Como alternativa si LTO falla al compilar. - Plugin GCC (
afl-gcc-fast): Para proyectos que requieran GCC.
Compilación Básica (Modo LLVM)
./afl++ <host/docker> afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Compilación con GCC
./afl++ <host/docker> afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Importante: La versión de GCC debe coincidir con la versión utilizada para compilar el plugin GCC de AFL++.
Con Sanitizers
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Ver También: Para configuraciones detalladas de sanitizers, problemas comunes y flags avanzados, consulta las skills técnicas address-sanitizer y undefined-behavior-sanitizer.
Flags de Compilación
Ten en cuenta que -g no es necesario; lo añaden por defecto los compiladores de AFL++.
| Flag | Propósito |
|---|---|
-DNO_MAIN=1 | Omitir la función main al usar un harness libFuzzer |
-O2 | Nivel de optimización de producción (recomendado para fuzzing) |
-fsanitize=fuzzer | Habilitar el modo de compatibilidad libFuzzer y añade el runtime del fuzzer al enlazar el ejecutable |
-fsanitize=fuzzer-no-link | Instrumentar sin enlazar el runtime del fuzzer (para bibliotecas estáticas y archivos objeto) |
Gestión del Corpus
Creación del Corpus Inicial
AFL++ requiere al menos un archivo de semilla no vacío:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
Para proyectos reales, recopila entradas representativas:
- Descarga archivos de ejemplo para el formato que estás fuzzeando
- Extrae casos de prueba del conjunto de pruebas del proyecto
- Usa entradas mínimas válidas para tu formato de archivo
Minimización del Corpus
Después de una campaña, minimiza el corpus para conservar solo la cobertura única:
./afl++ <host/docker> afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
Ver También: Para estrategias de creación de corpus, diccionarios y selección de semillas, consulta la skill técnica fuzzing-corpus.
Ejecución de Campañas
Ejecución Básica
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
Configuración de Variables de Entorno
./afl++ <host/docker> AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
Interpretación de la Salida
La interfaz de usuario de AFL++ muestra estadísticas de fuzzing en tiempo real:
| Salida | Significado |
|---|---|
| execs/sec | Velocidad de ejecución: cuanto más alta, mejor |
| cycles done | Número de pases completados de la cola |
| corpus count | Número de casos de prueba únicos en la cola |
| saved crashes | Número de fallos únicos encontrados |
| stability | % de aristas estables (debería estar cerca del 100%) |
Estructura del Directorio de Salida
out/default/
├── cmdline # ¿Cómo se invocó el SUT?
├── crashes/ # Entradas que hacen fallar el SUT
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Entradas que cuelgan el SUT
├── queue/ # Casos de prueba que reproducen el estado final del fuzzer
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Estadísticas de la campaña
└── plot_data # Datos para gráficos
Análisis de Resultados
Ver estadísticas en vivo de la campaña:
./afl++ <host/docker> afl-whatsup out
Crear gráficos de cobertura:
apt install gnuplot
./afl++ <host/docker> afl-plot out/default out_graph/
Re-ejecución de Casos de Prueba
./afl++ <host/docker> ./fuzz out/default/crashes/<test_case>
Opciones del Fuzzer
| Opción | Propósito |
|---|---|
-G 4000 | Longitud máxima de entrada de prueba (por defecto: 1048576 bytes) |
-t 1000 | Tiempo de espera en milisegundos para cada caso de prueba (por defecto: 1000ms) |
-m 1000 | Límite de memoria en megabytes (por defecto: 0 = ilimitado) |
-x ./dict.dict | Usar archivo de diccionario para guiar las mutaciones |
Fuzzing Multi-Núcleo
AFL++ destaca en fuzzing multi-núcleo con dos ventajas principales:
- Más ejecuciones por segundo (escala linealmente con los núcleos físicos)
- Fuzzing asimétrico (por ejemplo, un trabajo con ASan, el resto sin sanitizers)
Inicio de una Campaña
Inicia el fuzzer principal (en segundo plano):
./afl++ <host/docker> afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error &
Inicia fuzzers secundarios (tantos como núcleos tengas):
./afl++ <host/docker> afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error &
./afl++ <host/docker> afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Monitorización de Campañas Multi-Núcleo
Lista todos los trabajos en ejecución:
jobs
Ver estadísticas en vivo (se actualiza cada segundo):
./afl++ <host/docker> watch -n1 --color afl-whatsup state/
Detener Todos los Fuzzers
kill $(jobs -p)
Análisis de Cobertura
AFL++ rastrea automáticamente la cobertura mediante instrumentación de aristas. La información de cobertura se almacena en fuzzer_stats y plot_data.
Medición de Cobertura
Usa afl-plot para visualizar la cobertura a lo largo del tiempo:
./afl++ <host/docker> afl-plot out/default out_graph/
Mejora de la Cobertura
- Usa diccionarios para fuzzing consciente del formato
- Ejecuta campañas más largas (cycles_wo_finds indica meseta)
- Prueba diferentes estrategias de mutación con fuzzing multi-núcleo
- Analiza las brechas de cobertura y añade entradas de semilla específicas
Ver También: Para técnicas detalladas de análisis de cobertura, identificación de brechas de cobertura y mejora sistemática de la cobertura, consulta la skill técnica coverage-analysis.
CMPLOG
CMPLOG/RedQueen es el mejor mecanismo de resolución de restricciones de caminos disponible en cualquier fuzzer. Para habilitarlo, el objetivo del fuzzing debe estar instrumentado para ello. Antes de compilar el objetivo de fuzzing, configura la variable de entorno:
./afl++ <host/docker> AFL_LLVM_CMPLOG=1 make
No se necesita ninguna acción especial para compilar y enlazar el harness.
Para ejecutar una instancia del fuzzer con un objetivo de fuzzing instrumentado con CMPLOG, añade -c0 a los argumentos de la línea de comandos:
./afl++ <host/docker> afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Integración con Sanitizers
Los sanitizers son esenciales para encontrar errores de corrupción de memoria que no causan fallos inmediatos.
AddressSanitizer (ASan)
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Nota: El límite de memoria (-m) no es compatible con ASan debido a la reserva de memoria virtual de 20 TB.
UndefinedBehaviorSanitizer (UBSan)
./afl++ <host/docker> AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz
Problemas Comunes con Sanitizers
| Problema | Solución |
|---|---|
| ASan ralentiza el fuzzing | Usa solo 1 trabajo ASan en la configuración multi-núcleo |
| Agotamiento de pila | Aumenta la pila con ASAN_OPTIONS=stack_size=... |
| Incompatibilidad de versión de GCC | Asegúrate de que el GCC del sistema coincida con la versión del plugin de AFL++ |
Ver También: Para configuración completa de sanitizers y resolución de problemas, consulta la skill técnica address-sanitizer.
Uso Avanzado
Consejos y Trucos
| Consejo | Por Qué Ayuda |
|---|---|
| Usa harnesses LLVMFuzzerTestOneInput siempre que sea posible | Si una campaña de fuzzing tiene al menos un 85% de estabilidad, este es el estilo de fuzzing más eficiente. Si no, prueba con fuzzing de entrada estándar o de archivos |
| Usa diccionarios | Ayuda al fuzzer a descubrir palabras clave específicas del formato y bytes mágicos |
| Establece tiempos de espera realistas | Previene falsos positivos por carga del sistema |
| Limita el tamaño de la entrada | Las entradas más grandes no exploran necesariamente más espacio |
| Monitorea la estabilidad | Una baja estabilidad indica comportamiento no determinista |
Fuzzing de Entrada Estándar
AFL++ puede fuzzear programas que leen de stdin sin un harness libFuzzer:
./afl++ <host/docker> afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_stdin
Esto es más lento que el modo persistente, pero no requiere código de harness.
Fuzzing de Entrada de Archivos
Para programas que leen archivos, usa el marcador de posición @@:
./afl++ <host/docker> afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_file @@
Para un mejor rendimiento, usa fmemopen para crear descriptores de archivo a partir de la memoria.
Fuzzing de Argumentos
Fuzzeea argumentos de línea de comandos usando argv-fuzz-inl.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_COMPILER
#include "argv-fuzz-inl.h"
#endif
void check_buf(char *buf, size_t buf_len) {
if(buf_len > 0 && buf[0] == 'a') {
if(buf_len > 1 && buf[1] == 'b') {
if(buf_len > 2 && buf[2] == 'c') {
abort();
}
}
}
}
int main(int argc, char *argv[]) {
#ifdef __AFL_COMPILER
AFL_INIT_ARGV();
#endif
size_t len = strlen(argv[1]);
check_buf(argv[1], len);
return 0;
}
Compila y fuzzeea:
./afl++ <host/docker> afl-clang-fast++ -O2 fuzz_argv.cc -o fuzz_argv
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_argv @@
Soporte para macOS
AFL++ en macOS tiene soporte experimental. Consulta la documentación oficial de AFL++ para conocer las limitaciones actuales.
Soporte para Windows
AFL++ no es compatible directamente con Windows. Usa WSL2 o Docker en Windows para ejecutar AFL++.