Este viernes me he puesto al día con el tute de @RicardoNarvaja de CrackLatinos, el cual planteaba un ejercicio que para resolverlo hay que hacer uso del manejador de excepciones y ver como podemos explotar un programa haciendo uso del mismo.
El binario que vamos a analizar es ABOS2 (el antiguo), con MD5 = 1efa6d3f257f9a98ac65529edc0820b4, ya que Ricardo lo ha actualizado para que al alcanzar los símbolos coincidan con el binario y sea mas fácil su análisis.
Para comprobarlo podemos hacer uso de la herramienta (rahash2) del set de tools que vienen con radare2.
- rahash2 -a md5 ABO2_VS_2017.exe
Bueno como siempre hacemos debemos de tener una idea general acerca del binario, para ello ejecutamos el comando:
- rabin2 -I ABO2_VS_2017.exe
Vemos que es un binario de x86, como la mayoría de los que hemos ido analizando en este blog.
Abrimos el programa con Radare2 y analizamos un poco el mismo para ver que tiene:
- r2 ABO2_VS_2017.exe
- aaa --> analizamos el programa
- afl --> listamos las funciones del binario
- idp ABO2_VS_2017.pdb --> Cargamos los símbolos del binario
- s main --> nos vamos a la dirección de la memoria virtual del main()
Tras situarnos en la primera instrucción del main(), nos vamos al modo PANELS con:
- V!
Como vemos tenemos una llamada a messageboxA(), una llamada a gets() y una llamada a exit()
De las cuales la función vulnerable es gets() , ya que lee la cadena hasta que encuentre un final de cadena.
Esto puede provocar que se produzca un Buffer Overflow, ya que nuestro compilador reserva un espacio determinado para la variable (el que le indicamos + extra(opcional)) cuando se compila y genera el ejecutable, y si nosotros guardamos en esa variable un tamaño mayor de datos del reservado, se produce el "overflow".
Para que sea mas fácil su entendimiento hacemos lo siguiente:
- Re-nombramos la variable "s" por "buffer"
- Mostramos el desensamblado de la función main() con la variable cambiada
- Miramos el "stack frame" de la función main() y nos damos cuenta que la variable buffer se encuentra a 0x400 del horizonte (en radare EBP)
Vale sabemos que si machacamos ese tamaño reservado por le compilador para la variable Buffer, mas el tamaño del EBP guardado (4 bytes), lo siguiente que modificaríamos sería el "return address", por lo tanto podríamos saltar a la dirección donde queremos del programa.
Todo esto dando por supuesto que no tenemos ni NX canary, ni DEP. (que ya lo hemos comprobado con la herramienta rabin2).
Un detalle a destacar es que, tras desensamblar la función main(), vemos que hay una llamada a exit(), por lo tanto si producimos un buffer overflow en el gets() no llegaremos a redirigir el flujo ya que nunca llegaremos a la instrucción ret de la función.
Por lo que hay que buscar una alternativa, en este caso será hacer uso del controlador de excepciones SEH, el cual sobrescribiremos y saltaremos produciendo una excepción en la función gets(). Para ello la protección Safe-SEH debe de estar deshabilitada, podemos comprobarlo con este powershell incrustado en el tute.
El SEH es una estructura que esta compuesta por los siguientes campos:
Donde el primer campo apunta al siguiente controlador de la lista, y el segundo apunta a la dirección encargada de gestionar esa excepción.La posición del controlador de excepciones en el stack es la siguiente:
Por lo tanto, hemos de introducir un valor bastante grande en con el gets() para poder machacar el valor de SEH. Para ello nos vamos a la primera instrucción de nuestra función donde se ejecuta el gets() y vemos el controlador de excepciones donde se encuentra:
Vemos que tenemos el primero en la dirección 0x18FF78 y que el código que se encarga de gestionar la excepción comienza en la dirección 0x401B70.
Tal y como hemos visto en teoría, en nuestro stack debe de aparecer en la dirección 0x18FF78 la dirección del siguiente controlador de excepciones (en este caso 0x18FFC4) y justo debajo en la dirección 0x18FF7C, debe de estar la dirección 0x401B70, encargada de gestionar la excepción.
Para ver en que momento machacamos el puntero SEH, podemos hacer lo que recomendó el maestro Ricardo, introducir una input aleatoria y detectar la longitud necesaria para sobrescribir la dirección 0x18FF7C , aunque si sabemos que el buffer empieza en EBP-0x400 = 0x18FB40.
La diferencia es 0x43C = 1084, por lo que vamos a introducir una entrada con python con 1084 "A" y sobrescribiendo la dirección que gestiona la excepción con "BBBB", tal y como muestra la siguiente imagen:
Vemos que se ha sobrescrito, aun así para que se produzca la excepción debemos de concatenar posteriormente un mayor número de caracteres, hasta que se produzca la excepción, y esta haga al programa saltar a la dirección 0x42424242.
Tal y como vemos cuando salta la excepción si nos fijamos en el stack, podemos fijarnos que en la tercera posición del stack se encuentra la dirección que apuntaba al siguiente elemento en la lista de SEH, (definido en la posición 0x18FF78). Esto es una característica del controlador de excepciones sobre x86.
Por lo tanto si encontramos en nuestro binario alguna dirección en la que tengamos esta secuencia de comandos (POP X, POP X , RET). Podemos controlar el flujo de nuestro programa, si en vez de sobrescribir el SEH con "BBBB" apuntamos a la dirección donde se ejecutan esas instrucciones, ya que saltaríamos a la dirección que indiquemos cuando sobrescribimos en el stack la direccion 0x18FF78 (next SEH pointer).
1080* "A" (relleno) + "\x90\x90\x90\x90" (puntero next SEH que es el código que se ejecutará tras el POP, POP, ret) + "\x1D\x2A\x40\x00" (dirección de las instrucciones POP, POP ,ret en nuestro binario)
Para introducir este input, es necesario hacer uso de un lenguaje de scripting como python y Popen/Pcommunicate para introducir comandos no imprimirles.
Tras ejecutar el programa y "attachearnos" a este usando x32dbg, podemos ver como ser produce la excepción y saltamos a la dirección de nuestro next SEH pointer, ejecutando el código en hexadecimal que hemos introducido 0x90909090 (nop, nop.nop,nop). Junto con el resto de caracteres añadidos posteriormente que serían (\x1D\x2A\x40\x00 + \x43\x43....)
Vale una vez tenemos el flujo controlado, ya podemos hacer lo que queramos, en el ejercicio nos pide que ejecutemos una calculadora, por lo tanto introducimos la shellcode de una de las entradas anteriores, y añadimos a la misma un exitprocess() para que salgamos del controlador de excepciones. Ya que sino, al terminar nuetra shellcode nuestro binario "peta" y esto provoca una excepción, que llamaría a su vez al controlador de excepciones entrando en un bucle infinito... (vamos que pasaríamos mas tiempo aquí que un político en un club de alterne, y ejecutaríamos infinitas calculadoras :) :P)
Nuestro código en python quedaría de la siguiente manera:
En este último código, hemos sustituido las 4 instrucciones nop que teníamos, por un salto de 6 instrucciones (\xeb\x06) + 2 nop para que este alienado con 4 (\x90\x90). Así saltamos a la primera instrucción justo después de la dirección del (pop, pop ,ret).
Un saludo
Krilin4
Comentarios
Publicar un comentario