Hur man använder AFL och AFL++ för att effektivt fuzza binärfiler

Senaste uppdateringen: 28/02/2026
Författare: Isaac
  • El fuzz testing automatiza el envío de entradas malformadas para descubrir fallos de seguridad en software y sistemas.
  • AFL y AFL++ son fuzzers inteligentes que usan compiladores instrumentados para guiarse por cobertura y encontrar rutas de ejecución nuevas.
  • El análisis de crashes con herramientas como AFLTriage y GDB permite valorar la explotabilidad de vulnerabilidades como Integer Underflow.
  • Integrar el fuzzing en el ciclo de desarrollo mejora la robustez, ayuda a cumplir normas de seguridad y reduce el riesgo de incidentes críticos.

Fuzzing con AFL en binarios

Om du är intresserad av ciberseguridad orientada a binarios y explotación, tarde o temprano vas a cruzarte con el fuzzing, y en particular con AFL y AFL++. No hablamos de una herramienta teórica de laboratorio, sino de algo que se usa a diario para encontrar fallos muy reales en programas muy conocidos, igual que ha ocurrido con vulnerabilidades en aplicaciones tan populares como 7-Zip.

En las siguientes líneas vamos a ver, de forma detallada y con un tono cercano, cómo usar AFL y AFL++ para fuzzear binarios, qué es exactamente el fuzz testing, por qué es tan potente a nivel de seguridad y cómo integrarlo en un flujo de desarrollo moderno. Además, tomaremos como referencia un caso real de vulnerabilidad (un Integer Underflow que provoca ejecución remota de código) y repasaremos herramientas complementarias como GDB con extensiones, AFLTriage y módulos de explotación.

Qué es AFL / AFL++ y por qué se consideran smart fuzzers

Herramienta AFL para fuzz testing

AFL (American Fuzzy Lop) y su evolución AFL++ son herramientas de fuzzing de código abierto diseñadas para probar binarios de forma automática, buscando fallos como desbordamientos, errores de memoria o comportamientos inesperados que deriven en crashes o incluso en vulnerabilidades explotables.

La gracia de AFL no es solo mandar datos aleatorios sin más, sino que se trata de un fuzzer inteligente o coverage-guided fuzzer. A partir de una o varias entradas válidas de ejemplo (los llamados testfall), AFL genera variaciones constantemente y, gracias a la instrumentación del programa, detecta cuándo una nueva mutación hace que el binario recorra un camino diferente de ejecución.

Para conseguir esta instrumentación, AFL cuenta con sus propios compiladores, como afl-gcc y afl-g++, que insertan pequeños “ganchos” en el código durante la compilación. Estos ganchos informan a AFL de que cierta entrada ha descubierto rutas nuevas en el flujo del programa, lo que le permite centrarse en mutaciones que realmente aportan cobertura adicional y no perder el tiempo con datos que no cambian nada.

Este enfoque convierte a AFL en algo así como el “dirb de los binarios”: una herramienta muy conocida, sencilla de usar una vez se entiende el flujo básico, y suficientemente potente como para descubrir fallos serios en programas de producción, aunque existan fuzzers más avanzados o especializados.

Introducción al fuzz testing y su papel en ciberseguridad

El fuzz testing, o simplemente fuzzing, es una técnica de pruebas automatizadas de seguridad y robustez. La idea consiste en mandar a una aplicación un aluvión de datos malformados, aleatorios o poco habituales, con el objetivo de ver si el programa se rompe, se cuelga o se comporta de forma inesperada.

Cuando se somete un software a fuzzing, se persigue localizar errores como desbordamientos de búfer, fallos de validación de entradas, problemas de manejo de memoria o condiciones de carrera. Muchos de estos fallos pueden desembocar en ejecución de código arbitrario, filtración de información o denegaciones de servicio.

En un flujo de trabajo típico, el fuzz testing sigue tres fases claras: en primer lugar se genera el conjunto de datos de prueba (ya sean completamente aleatorios, generados siguiendo un formato concreto o mutando entradas válidas); después se ejecuta el binario bajo prueba mientras se monitoriza su comportamiento; y finalmente se registran y analizan los crashes o comportamientos raros para que los desarrolladores puedan investigar el origen del problema.

Existen distintas estrategias de fuzzing. Algunas herramientas se centran en fuzz generacional, creando entradas que respetan un formato bien definido (por ejemplo, una estructura de archivo o un protocolo de red); otras se basan en mutación de entradas válidas, que es precisamente el enfoque donde AFL y AFL++ destacan. También hay enfoques más aleatorios y otros que se apoyan en conocimiento específico del sistema a probar para apuntar justo a los puntos más frágiles.

En ciberseguridad moderna, el fuzz testing es un aliado fundamental: ayuda a encontrar vulnerabilidades de manera proactiva, antes de que el software llegue a producción, y contribuye al cumplimiento de normativas y estándares como ISO 27001 o requisitos asociados a protección de datos. Además, reduce el riesgo reputacional que supondría que un fallo grave explotable llegue a manos de atacantes.

Un caso real: Integer Underflow y vulnerabilidad RCE en un descompresor

Para entender el valor real de AFL y el fuzzing, es muy útil fijarse en una vulnerabilidad concreta basada en un Integer Underflow dentro de un descompresor de archivos muy popular. Se trata de un fallo de ejecución remota de código (RCE) que se dispara al procesar ficheros comprimidos con el algoritmo Zstandard, con una puntuación CVSS alta, aunque no máxima, precisamente porque requiere un contexto algo más específico (por ejemplo, que se procesen archivos concretos).

Un Integer Underflow aparece cuando una variable entera con límites inferiores bien definidos se utiliza en operaciones de resta hasta traspasar su valor mínimo. Como muchos tipos enteros no admiten valores negativos, al “pasarse por abajo”, en lugar de obtener un número por debajo de cero, se produce un salto al valor máximo posible del rango, debido a cómo se representan internamente esos números.

  Signera skript och härda ExecutionPolicy med AppLocker och WDAC

Imagina una variable entera que, en teoría, nunca debería ser negativa. Si vas restando una constante hasta llegar a 0 y sigues restando, no obtendrás -1, -2, etc., sino que el valor dará la vuelta y pasará a ser un número gigantesco en el extremo superior del rango permitido. Esa cifra absurda, si se usa como índice o tamaño en operaciones de memoria, puede provocar lecturas o escrituras fuera de los límites previstos.

El problema se vuelve crítico cuando esa variable entera que puede hacer underflow se usa para manejar un buffer o para calcular offsets. Un pseudo-ejemplo típico sería utilizar una variable controlada indirectamente por el usuario como desplazamiento en una función tipo memcpy, en un bucle que descuenta una constante en cada iteración hasta que supuestamente llegue a cero o a un número no positivo.

En un escenario así, si el valor inicial nunca consigue que la condición de salida se cumpla de forma “limpia”, el underflow terminará convirtiendo el número en algo enorme, el bucle seguirá corriendo y se producirá una corrupción masiva de memoria. Esto se traduce en crashes y, con suficiente trabajo de explotación, en la posibilidad de ejecutar código arbitrario.

Cómo AFL ayuda a localizar este tipo de vulnerabilidades

Una vez entendido el concepto de Integer Underflow, se ve claro por qué AFL y AFL++ son tan útiles: a partir de uno o pocos archivos de entrada perfectamente válidos, el fuzzer puede generar decenas de miles de variaciones, hasta tropezar con aquella que dispara el bug concreto en la ruta de descompresión afectada.

Durante el fuzzing, AFL monitoriza el binario instrumentado para saber cuándo una entrada hace que se ejecute un camino diferente del control de flujo. Cada vez que detecta un nuevo recorrido, marca esa entrada como interesante y la conserva como base para futuras mutaciones. Así se exploran no solo los caminos habituales de ejecución, sino también aquellas combinaciones de datos que activan rutas raras del código, como el proceso de descompresión con parámetros extraños.

En el caso del Integer Underflow, AFL puede encontrar una muestra que provoque que la variable implicada nunca alcance el valor de salida esperado en el bucle. El resultado es un crash detectable, que quedará almacenado en la carpeta correspondiente de resultados de AFL, listos para ser analizados con más calma.

Al cabo de unos minutos u horas de ejecución, es común que AFL señale el primer crash relevante. No todos los fallos que encuentre serán explotables, pero sí son un indicador claro de que algo en el manejo de memoria, la validación de entradas o la lógica interna del binario no está funcionando como debería.

Ese es el momento en el que entran en juego herramientas de apoyo como AFLTriage o un depurador como GDB, que permiten profundizar en el crash, observar el estado de los registros y la memoria, y valorar si el fallo puede explotarse de forma fiable o si es simplemente un cuelgue poco útil desde el punto de vista del atacante.

Instalación básica de AFL / AFL++ y entorno de análisis

Montar el entorno mínimo para fuzzear con AFL en un sistema Linux estándar es bastante directo. En muchas distribuciones basta con instalar el paquete correspondiente a los compiladores instrumentados, por ejemplo mediante un comando como apt install afl-g++ o el paquete equivalente de AFL++ que traiga toda la suite.

Al hacerlo, se instalan tanto las herramientas de fuzzing (como afl-fuzz) como los compiladores especializados (afl-gcc, afl-g++, y sus variantes modernas en AFL++). Estos compiladores son los que se usarán cuando quieras recompilar tu objetivo con instrumentación, algo imprescindible para sacar todo el partido al fuzzing guiado por cobertura.

Además del fuzzer en sí, resulta prácticamente obligatorio contar con un buen entorno de debugging. Un GDB extendido con plugins como PEDA o GEF hace la vida mucho más fácil gracias a comandos extra, formateo cómodo de memoria, visualizaciones del stack y atajos para revisar registros y mapas de memoria.

Si aún no tienes GDB, se puede instalar con el gestor de paquetes de la distribución. Después, solo hay que integrar el plugin elegido (por ejemplo, descargando GEF o PEDA desde sus repositorios y cargándolos en el archivo de configuración de GDB o con el comando source dentro del propio debugger).

Contar con este “GDB con esteroides” te permitirá analizar los binarios instrumentados que se derrumban bajo fuzzing, descargar los inputs que provocan el crash y seguir paso a paso qué está ocurriendo en la ejecución exactamente en el punto del fallo.

Compilar un binario objetivo con AFL para fuzzing

Una vez que tienes AFL o AFL++ listo, el siguiente paso para un fuzzing serio es compilar el objetivo con los compiladores de AFL, y hacerlo con las opciones adecuadas para facilitar tanto el análisis como la explotación potencial, en caso de que el objetivo del ejercicio sea de investigación ofensiva.

  SmartScreen blockerar legitima appar: vad som händer och vad man ska göra

En proyectos grandes, como puede ser un descompresor o una suite de utilidades de archivo, es buena idea habilitar símbolos de depuración (opción DEBUG o flags como -g) y reducir al mínimo las optimizaciones (por ejemplo, usar -O0 en lugar de -O2). Esto ayuda muchísimo a la hora de entender qué está pasando en el código fuente en relación con el crash observado.

Normalmente tendrás que localizar el makefile o fichero de construcción adecuado y modificar las líneas donde se definen las flags de compilación. Eso puede incluir activar una variable de DEBUG, forzar un nivel de optimización concreto y, sobre todo, sustituir CC y CXX por afl-gcc y afl-g++ (o las herramientas equivalentes de AFL++).

Por ejemplo, podrías lanzar la compilación con algo del estilo de CC=afl-gcc y CXX=afl-g++ junto con el makefile apropiado. El resultado será un binario instrumentado que incorporará los ganchos necesarios para que AFL pueda medir la cobertura a lo largo de la ejecución.

Cuando termine la compilación, conviene comprobar que el binario resultante tiene realmente símbolos de depuración y la arquitectura adecuada, usando un comando tipo file sobre el ejecutable. Así te aseguras de que no estás fuzzing a ciegas y de que podrás depurar de forma cómoda los crashes que aparezcan.

Preparación de testcases y ejecución de AFL

Antes de lanzar AFL contra tu binario, necesitas preparar un conjunto de entradas mínimas válidas. Si estás probando un descompresor que falla con archivos comprimidos con un algoritmo concreto, tendrás que crear al menos un archivo bien formado que utilice ese método de compresión.

Lo habitual es crear un directorio, por ejemplo llamado testcases, y guardar ahí una o varias muestras funcionales. Estas servirán de punto de partida para que AFL genere mutaciones cada vez más agresivas, tratando de descubrir nuevos caminos de ejecución y, eventualmente, crashes.

El comando típico para arrancar AFL se parece a algo como: indicar un nombre de sesión o sincronicación, la carpeta de entrada con testcases, la ruta de salida donde se almacenarán los resultados y algunos parámetros adicionales como el timeout para evitar quedarse atascado en bucles infinitos.

En la línea de comandos, se especifica también cómo se ejecuta el programa objetivo usando el marcador @@, que AFL reemplazará automáticamente por la ruta de cada testcase que vaya generando. Así, puedes indicar algo como ./binario argumentoss @@ y AFL se encargará de ir lanzando el ejecutable miles de veces con diferentes archivos.

Es importante saber que AFL puede quejarse de la configuración del sistema al principio (por temas de límites de recursos, core dumps, etc.). En ese caso, suele existir un script o herramienta auxiliar que ajusta parámetros del kernel y de la sesión para optimizar el entorno de fuzzing, lo cual conviene ejecutar con permisos adecuados antes de repetir la prueba.

Análisis de crashes con AFLTriage y GDB

Tras un tiempo de fuzzing, AFL irá rellenando la carpeta de salida con nuevas entradas interesantes y, sobre todo, con archivos que han provocado crashes. Estos suelen guardarse en un subdirectorio específico (por ejemplo, crashes dentro de la carpeta de la sesión).

Para no volverse loco revisando uno por uno esos inputs, existen herramientas como AFLTriage que permiten ejecutar automáticamente el binario contra cada archivo de crash y generar informes estructurados con detalles sobre dónde y cómo se ha producido el fallo.

Al lanzar AFLTriage, se le indica el directorio de entradas (la carpeta de crashes), un directorio de salida donde guardar los reportes y el comando para ejecutar el binario con @@. La herramienta analizará cada caso y generará archivos de texto con información de interés para el analista.

El contenido típico de estos informes incluye el tipo de señal que ha provocado el crash, la dirección donde ha fallado, un stacktrace básico y, en muchos casos, la función o macro donde se ha roto el código (por ejemplo, una operación concreta de copia, un bucle de compresión o una macro tipo COPY_CHUNKS que resta tamaños en cada iteración).

Una vez localizado un crash interesante, el siguiente paso es reproducirlo en GDB. Para ello se lanza el depurador con el binario y el archivo de entrada culpable como argumentos, y se ejecuta el programa desde dentro de GDB. Cuando se reproduce el crash, se puede inspeccionar el estado de registros clave como RAX, RDX, etc., revisar la instrucción exacta que se está ejecutando y consultar mapas de memoria (vmmap) para ver en qué región se está intentando leer o escribir.

De crash a posible explotación: módulos de análisis en GDB

Ver que un binario se cae no siempre significa que el fallo sea explotable. Aquí entran en juego extensiones de GDB como el módulo möjlig att utnyttja, diseñado para analizar el contexto de un crash y ofrecer una clasificación aproximada sobre si el fallo podría aprovecharse o no.

Este tipo de extensiones estudian aspectos como la validez del puntero de instrucción, el estado del stack pointer, los permisos de memoria de la dirección donde se intenta leer o escribir y la naturaleza de la señal que ha provocado el crash. Con todo ello, proporcionan un veredicto orientativo sobre el potencial de explotación (por ejemplo, categorizando el bug como probablemente explotable, posiblemente explotable o poco interesante).

  Så här förhindrar du skadliga länkar i Microsoft Teams

En el caso del Integer Underflow que termina escribiendo en memoria de solo lectura, el plugin puede sugerir que se trata de un caso con potencial explotable porque se está realizando una escritura fuera de los límites previstos en una región en la que no se debería tocar nada.

No todos los escenarios serán tan directos, pero esta combinación de AFL para descubrir el crash, AFLTriage para agrupar y describir los casos y GDB con extensiones para valorar la explotabilidad, forma un pipeline muy sólido de auditoría de binarios. A partir de ahí, el trabajo restante es puramente de investigación y desarrollo de exploit, algo que puede variar enormemente en complejidad según el binario y las protecciones activas.

Otra parte clave del análisis, especialmente cuando se publica un CVE y un parche asociado, es revisar cómo se ha corregido el fallo en el código fuente. Esto suele implicar cambios en el tipo de datos usado (por ejemplo, pasar de un byte con signo a uno sin signo, ampliando rango) y la inclusión de comprobaciones adicionales (ifs que devuelven códigos de error si ciertos límites se sobrepasan) para evitar el underflow o la corrupción de memoria.

Otros fuzzers y herramientas relevantes en el ecosistema

Aunque AFL y AFL++ son de las opciones más conocidas para fuzzear binarios nativos, el ecosistema de fuzz testing es bastante más amplio y conviene tenerlo en el radar si trabajas de forma habitual con programvarusäkerhet.

Entre las opciones más utilizadas está LibFuzzer, una librería desarrollada originalmente por Google que se integra directamente con el código C/C++ y realiza fuzzing a nivel de funciones concretas, ideal para testear componentes individuales con entradas muy controladas.

También existe Peach Fuzzer, una plataforma más generalista y configurable que permite definir modelos precisos de protocolos, formatos de archivo y estructuras complejas. Es muy popular en sectores donde la fiabilidad es crítica, como sistemas industriales o entornos IoT.

En el ámbito web, herramientas como OWASP ZAP incluyen módulos de fuzzing orientados a aplicaciones web, capaces de enviar peticiones manipuladas, cargas malformadas y pruebas de inyección a endpoints HTTP para detectar vulnerabilidades como inyecciones SQL, problemas de validación de inputs o errores en el procesamiento de formularios.

Todo ello complementa al fuzzing de binarios tipo AFL/AFL++, ya que permite cubrir desde el código nativo de bajo nivel hasta las capas de aplicación más expuestas al usuario final. El objetivo común en todos los casos es el mismo: automatizar la búsqueda de fallos antes de que lo hagan los atacantes.

Cómo integrar el fuzz testing en el ciclo de desarrollo

Para que el fuzzing aporte valor real de forma constante, no basta con lanzar AFL o AFL++ de manera puntual y olvidarse. Es mucho más eficaz integrar el fuzz testing en el ciclo de desarrollo de software, de forma similar a como se hace con los tests unitarios o de integración.

Det första steget är att definiera qué partes del sistema son más críticas: parsers de archivos, módulos de red, componentes que traten datos de usuarios o servicios expuestos al exterior. Sobre esos objetivos se pueden diseñar campañas de fuzzing específicas, con testcases cuidadosamente elegidos.

Después toca elegir la herramienta adecuada para cada caso. Para binarios nativos complejos, AFL++ o LibFuzzer suelen ser candidatos naturales, mientras que para APIs web o aplicaciones HTTP, una herramienta orientada a web tendrá más sentido. Lo importante es que el fuzzing se ejecute de forma repetible y automatizable.

La integración con sistemas de CI/CD resulta muy útil: se puede configurar el pipeline para que, en determinadas ramas o antes de ciertos despliegues, se lance una batería de fuzzing con un tiempo limitado, guardando cualquier crash nuevo encontrado y fallando la build si aparecen vulnerabilidades potencialmente graves.

Finalmente, hay que asumir que el fuzzing es un proceso iterativo. Cada vez que se corrige un fallo, se debería volver a fuzzear el componente afectado para verificar que el parche funciona y que no se ha introducido ninguna regresión o vulnerabilidad nueva. De esta manera, el fuzz testing se convierte en una capa más de protección continua y no en una auditoría aislada.

En conjunto, todo lo anterior muestra cómo AFL, AFL++ y el fuzz testing en general permiten pasar de una seguridad basada solo en revisiones manuales a un enfoque mucho más automatizado y exhaustivo, capaz de descubrir errores que de otro modo se quedarían ocultos. Trabajar con casos reales, como vulnerabilidades de underflow en descompresores muy utilizados, ayuda además a entender que no se trata de teoría, sino de problemas que ya están siendo explotados, por lo que mantener el software actualizado y sometido a fuzzing periódico puede marcar la diferencia entre un sistema comprometido y uno resistente.