Fuzzing con American Fuzzy Lop – Quickstart

La codificación segura es una práctica muy difícil. Normalmente, cuando los programadores desarrollan software, su objetivo es hacer que el software funcione y no se rompa. En este proceso, se pueden desarrollar vulnerabilidades en los casos en los que se ha utilizado una función heredada en lugar de una más segura. En consecuencia, el software heredado es especialmente vulnerable.

C es uno de esos lenguajes que es inherentemente muy versátil y potente, pero tiene un inconveniente crítico: la seguridad del software basado en C depende de los conocimientos del programador. Esto significa que si el programador conoce bien la codificación segura, su software será también seguro. Por otro lado, y esto constituye el mayor trozo, si el programador no es lo suficientemente sofisticado, habrá lagunas en su software que, en última instancia, conducirán a un exploit.

Para la gente como yo, que sabe de programación pero es nueva en la industria de la seguridad, es muy importante estudiar el código vulnerable y entender las posibles consecuencias. Esto ayuda a refinar las habilidades de codificación y a desarrollar una actitud de atacante DURANTE la fase de codificación en lugar de DESPUÉS de codificar todo el software.

Sinceramente, es bastante engorroso estudiar el código fuente completo de una aplicación cuando se buscan vulnerabilidades como los desbordamientos de búfer. Aunque este método tiene sus propios méritos, no es el más fácil para encontrar vulnerabilidades simples que pueden ser críticas. Dichas vulnerabilidades deben ser resueltas inmediatamente y la forma más fácil de encontrarlas es a través de una técnica llamada Fuzzing.

El Fuzzing es una técnica para encontrar vulnerabilidades «fáciles» en el código enviando datos generados «aleatoriamente» a un ejecutable. En general, existen tres tipos de fuzzers:

  1. Mutación: Un tipo de fuzzing «tonto» en el que se generan muestras de entrada malformadas y se proporcionan al ejecutable. Esta entrada puede o no ajustarse al tipo de entrada que espera la aplicación, por lo que la probabilidad de encontrar bugs reales no es alta.
  2. Generación: Un tipo de fuzzing «inteligente» que requiere unos datos de prueba iniciales a partir de los cuales el algoritmo del fuzzer puede generar una entrada malformada desde cero. Este tipo de fuzzing es mejor que el fuzzing tonto en muchos casos porque el programa recibe la entrada que espera.
  3. Evolutivo: Este tipo de fuzzers utilizan la retroalimentación de cada «fuzz» para aprender con el tiempo el formato de la entrada.
    1. En este post, veremos el fuzzing con American Fuzzy Lop (AFL). Se trata de un tipo de fuzzer evolutivo que es adecuado para fuzzear programas que toman la entrada desde STDIN o un archivo.

Hay una gran cantidad de fuzzers en la naturaleza incluyendo Peach y syzkaller. Entonces, ¿por qué AFL?

  1. El caso de uso. Este es el punto más importante a considerar. Mi caso de uso fue para fuzz una aplicación que toma la entrada de un archivo. Es importante tener en cuenta que AFL no tiene la capacidad de hacer fuzzing sobre redes.
  2. Es simple de instalar.
  3. La interfaz de usuario de AFL contiene una tonelada de información incluyendo estadísticas en tiempo real del proceso de fuzzing.

Instalación de AFL

¡Instalar AFL es fácil y lo he hecho más fácil para usted escribiendo un simple (pero crudo) script de shell que lo instalará por usted! Ejecute el script con sus privilegios de usuario y se instalarán todas las dependencias, AFL y las herramientas relacionadas. El script de shell se puede encontrar aquí: https://github.com/nikhilh-20/enpm691_project/blob/master/install_afl.sh

Elige la aplicación a fuzzear

En este post, sólo veremos cómo fuzzear aquellas aplicaciones de las que tenemos el código fuente. Esto se debe a que AFL instrumenta el código fuente para monitorizar la ejecución, los errores y otras cosas relacionadas con el rendimiento. También es posible fuzzear directamente un ejecutable pero eso es experimental y está fuera del alcance de este post (consejo: requiere QEMU).

Elige cualquier sistema de código abierto de GitHub para hacer fuzzing. Cuanto más conocida sea tu elección, menos número de vulnerabilidades tendrá probablemente. ¡Otros están buscando bugs también! Un método sencillo que utilizo para encontrar código vulnerable es utilizar GitHub Search. Esto es lo que hago:

  1. Busca una función vulnerable, digamos strcpy.
  2. Los resultados se cuentan por millones. Ve a la categoría de commits de los resultados. Aquí es donde encontrarás los repositorios en los que se utilizó strcpy (o quizás se eliminó). Estos repositorios son un buen punto de partida para empezar a fuzzear.

Instruyendo la aplicación

Por razones de privacidad, no puedo revelar el repositorio que estoy utilizando.

Clonar el repositorio git.

nikhilh@ubuntu:~$ git clone https://github.com/vuln; cd vuln

Configurar una variable de entorno, AFL_HARDEN=1. Esto activa ciertas opciones de endurecimiento de código en AFL mientras se compila, lo que facilita la detección de bugs de corrupción de memoria.

nikhilh@ubuntu:~/vuln$ export AFL_HARDEN=1

Establezca ciertas banderas del compilador, para que la aplicación se compile de manera que nos facilite encontrar (y explotar) vulnerabilidades. Lo ideal sería utilizar variables de entorno para establecer lo que necesitamos, pero hay que personalizar bastante. así que editaremos directamente el Makefile.

Asegúrate de que el compilador utilizado es afl-gcc o afl-clang en lugar de gcc y clang respectivamente. Esto es lo que permite a AFL instrumentar el código fuente.

Agrega banderas del compilador:

-fno-stack-protector desactiva el protector de pila que nos permitirá explotar los desbordamientos de búfer.

-m32 sólo es necesario si estás usando una máquina de 32 bits, de lo contrario no.

Una vez que hayas terminado con estos cambios, es hora de compilar la aplicación. Ejecuta make. Cuando lo hagas, DEBES ver declaraciones como estas en el log:

Instrumentadas 123 localizaciones (32 bits, hardened-mode, ratio 100%).

Si no ves estas declaraciones, significa que AFL no ha activado el código de la aplicación para fuzzing. En otras palabras, no ha instrumentado el código fuente con éxito.

Muestras de prueba

AFL es un fuzzer de tipo evolutivo. Significa que, al igual que los fuzzers basados en la generación, también requiere datos de prueba iniciales para entender qué tipo de datos espera la aplicación objetivo. Cuando se dirige a sistemas de código abierto, esto es fácil de encontrar. Sólo tienes que buscar en su directorio de pruebas y encontrarás todos los datos de prueba que necesitas.

nikhilh@ubuntu:~/vuln$ mkdir afl_in afl_out

nikhilh@ubuntu:~/vuln$ cp test/* afl_in/

Comienza el fuzzing

Ahora que tenemos nuestras muestras de prueba, ¡estamos listos para fuzzear!

Oh, espera… también tenemos que cambiar a dónde van las notificaciones de crash de la aplicación. By default, when an application crashes, the core dump (basically, the contents of RAM are stored in a file to help in debugging) notification is sent to the system’s core handler. We don’t want this. Why? By the time this notification reaches AFL, it’ll be classified as a timeout rather than a crash.

nikhilh@ubuntu:~/vuln$ sudo su

password for nikhilh:

root@ubuntu:/home/nikhilh/vuln# echo core > /proc/sys/kernel/core_pattern

root@ubuntu:/home/nikhilh/vuln# exit

NOW, we are ready to fuzz!

nikhilh@ubuntu:~/vuln$ afl-fuzz -i afl_in -o afl_out -S slaveX — ./vuln @@

Command line flags used:

  1. -i — This marks the test input directory. Aquí es donde almacenamos los datos de prueba iniciales.
  2. -o- Este es el directorio donde el AFL escribe información útil con respecto a las caídas, cuelgues, etc.
  3. -S – Este es el modo Esclavo. Básicamente, el AFL modificará aleatoriamente la entrada causando fuzzing no determinista.
  4. La opción -M es el modo Maestro que es fuzzing determinista, que básicamente significa que cada bit de la entrada está siendo modificado de alguna manera. (¡Esto es lento! … Obviamente.)
  5. @@ – Esta es la posición donde estará el archivo de prueba de entrada. AFL sustituye esto para usted automáticamente. Si su ejecutable toma la entrada de STDIN, entonces esto no es necesario.

Resultados del fuzzing

Esto tomará algún tiempo para mostrar. Muchas veces, la gente hace fuzzing durante más de 24 horas (y puede acabar sin nada). En mi caso, creo que la aplicación era un poco demasiado vulnerable, por lo que tuve 516 caídas únicas en una hora. Sin embargo, ¡eso no significa que haya 516 vulnerabilidades!

Puedes salir de la sesión de fuzzing con un Ctrl-C.

Analysis Phase

Now that we have results, we need to analyze them to see which ones are exploitable. To this end, we will use one of AFL’s utilities called afl-collect. This will have been installed through the installation script as well.

nikhilh@ubuntu:~/afl-utils$ afl-collect -d crashes.db -e gdb_script -r -rr ~/vuln/afl_out/slaveX ./output_dir — ~/vuln/vuln

To understand what each command line flag does, refer to its help section.

nikhilh@ubuntu:~/afl-utils$ afl-collect — help

If you see lines such as these in the output, celebrate! You’ve found something interesting to try and exploit.

*** GDB+EXPLOITABLE SCRIPT OUTPUT ***

slaveX:id:000000,sig:11,src:000000,op:havoc,rep:2……………:EXPLOITABLE

slaveX:id:000046,sig:11,src:000004,op:havoc,rep:4……………:EXPLOITABLE

AFL le mostrará qué entrada causó el fallo de la aplicación. En este caso, el archivo: id:000046,sig:11,src:000004,op:havoc,rep:4 causó un StackBufferOverflow en la aplicación. Dichos archivos se pueden encontrar en ../afl_out-slaveX/crashes/

¡Hecho!

¡Eso es todo para iniciarse rápidamente en el fuzzing! El proceso es realmente sencillo y muy cómodo ya que está todo automatizado. El siguiente paso sería analizar por qué la entrada causó un Buffer Overflow y buscar una forma de explotarlo. Recuerda que no todas las vulnerabilidades pueden llevar a un exploit.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.