fuzzing parte 2

Fuzzing Parte 2 – Fuzzing con AFL

Como escribí en el último capítulo, en este artículo solo explicaré cómo fuzzear cuando hay acceso al código fuente usando AFL. Para demostrarlo, tomé un antiguo programa de código abierto que encontré en GitHub llamado calc. Como sugiere el nombre, es una calculadora simple escrita en C. La elegí porque era probable que fuzzing encontrara fácilmente muchos bloqueos en ella y, como verá, esta hipótesis resultó ser correcta.

Lo primero es lo primero, tenemos que determinar un objetivo. Decidí fuzzear la función main(), en lugar de encontrar alguna función específica para fuzzear. El razonamiento es que el programa analiza la entrada sin procesar en una serie de funciones antes de entregar la entrada procesada a las funciones que realmente hacen los cálculos. Dado que el programa tamiza las entradas incorrectas durante el análisis, no es justo fuzzear ninguna función específica.

El main() original se ve más o menos así (lo abrevié un poco):

Función principal

En general, contiene dos ramas: obtiene entrada a través de argc,argv[], o a través de la entrada estándar (stdin). No estoy seguro de por qué, pero el creador de AFL recomienda explícitamente no pasar desapercibido argc,argv[], al igual que todas las demás fuentes en línea. Así que lo cambié un poco: primero eliminé la sección que se ocupa de la entrada proveniente de argc, argv, y luego traté de optimizarlo en la medida de lo posible. Después de todo, cada vez que el fuzzer intenta otra entrada, se ejecuta esta función. Por lo tanto, las ineficiencias pueden conducir a ralentizaciones graves. Aquí está el resultado final:

Función principal después de la modificación

Observe el bucle que agregué. Le indica a AFL que use el modo persistente. En el modo predeterminado de AFL, cada vez que AFL ejecuta los programas, utiliza la llamada al sistema fork() para crear un nuevo subproceso. Esta llamada al sistema tiene una sobrecarga importante, lo que ralentiza considerablemente todo el proceso de fuzzing. Cuando se usa el modo persistente, el fuzzer usa el mismo proceso una y otra vez. El único requisito es borrar todas las variables y búferes cada vez que se ejecuta el bucle.

Llamé al archivo con la función modificada harness.c, y para compilar el programa con este archivo y no con main.c, cambié la forma del archivo automake:

archivo automake antes del cambio

a

archivo automake después del cambio

Ahora es el momento de fuzzear el programa. Ejecuté automake para generar los archivos de creación y luego se hizo con el comando:

comando de fabricación automática

Para que el programa se compile con instrumentación AFL.

Lo único que queda por hacer ahora es crear la carpeta de entrada inicial para AFL, a la que llamé 'in', y la carpeta de salida 'out'. En la carpeta de entrada, se colocan archivos de texto simples (sin formato) que contienen entradas legítimas, como "20/2" o "5*4". Dado que queremos ejecutar un par de procesos AFL (cada proceso se ejecuta en su propio núcleo de CPU), creamos una carpeta de entrada separada para cada proceso.

carpeta de entrada para cada proceso

Para fuzzear, uno simplemente ejecuta el comando:

Comando de ejecución AFL para el primer uso

Para el primer proceso, siendo el segundo

Comando de ejecución AFL para segundo uso

Y así sucesivamente y así sucesivamente.

Como puede ver en la siguiente captura de pantalla, AFL tiene una GUI que brinda información importante sobre el proceso de fuzzing. Por ejemplo, las medidas de la cobertura se dan en cobertura del mapa, Y debajo hallazgos en profundidad la GUI también proporciona el número de bloqueos e información relacionada.

GUI de progreso de AFL
GUI de progreso de AFL

Después de que se haya encontrado una cantidad suficiente (el número exacto depende de usted) de bloqueos, generalmente después de un largo período de tiempo sin un nuevo bloqueo único, puede finalizar el proceso. Para cada proceso de AFL que ejecute, se generará una carpeta separada en la carpeta de salida:

Carpeta de salida

Dentro de estas carpetas, los datos se organizan así:

Contenido de la carpeta de salida

Es importante explicar qué información contiene cada uno de estos:

  • plot_data: pone la información en forma conducente a la generación de una trama.
  • fuzzer_stats: estadísticas sobre el proceso de fuzzing.
  • fuzz_bitmap: un mapa de bits en el que cada byte corresponde a una rama del programa.
    “AFL mantiene un “mapa de bits fuzz”, con cada byte dentro del mapa de bits que representa un conteo de la cantidad de veces que se ha tomado una rama particular dentro del programa fuzzed. AFL no realiza un mapeo uno a uno entre una rama en particular y un byte dentro del mapa de bits. En cambio, la instrumentación integrada de AFL coloca una identificación constante aleatoria de dos bytes en cada rama. Cada vez que la ejecución llega a una rama instrumentada, AFL realiza un XOR de la ID de la nueva rama y la última ID de rama vista antes de llegar a la nueva rama. Esto captura tanto la rama actual como la ruta única que se tomó para llegar a ella (como cuando se llama a la misma función desde varias ubicaciones en el código). Luego, AFL aplica una función hash al valor XOR para determinar qué entrada en el mapa de bits representa esa combinación de ramas. Cada vez que se ejerce una combinación de rama particular, el byte apropiado se incrementa dentro del mapa de bits”.
  • cmdline: el comando que se proporcionó a AFL. En la práctica el nombre del programa.
  • .cur_input: la entrada actual.
  • cola: todas las entradas que se intentaron hasta ahora.
  • se bloquea y se cuelga: los resultados. Cada archivo en estas carpetas contiene entradas que causaron fallas o bloqueos. En el nombre de cada archivo se especifica la señal del kernel del bloqueo (no relevante en bloqueos, por supuesto), la ID de la entrada utilizada por AFL para crear la entrada que causó el bloqueo, el tiempo transcurrido desde que AFL comenzó a ejecutarse, y la mutación utilizada para generar la entrada a partir de su semilla.
Ir al contenido