Introducción al Reversing – 0x08 Memcpy

Buenos días, primero quiero agradecer a Ricardo Narvaja por poder compartir mis tutoriales sobre reversing en su web. La parte de explotación están protegidos con password los .7z así que si queréis ver mi solución a la parte 8 del curso siendo el reto uaf en https://ricardo.crver.net/WEB/RADARE/  tan solo tenéis que solucionarlo y la flag será la contraseña jeje. En este tutorial veremos una aplicación denominada memcpy  de la web http://pwnable.kr donde nos pedirá una serie de inputs pasándole un size comprendido entre varios valores.

Una vez introduzco todos me genera una violación de segmento jeje por lo tanto habrá que realizar ingeniería inversa para ver que sucede.

Abrimos radare2 y vemos las strings primero para saber por donde comenzar y cual es el objetivo de este reto ya que aun no sabemos si el objetivo es obtener una shell o solo ver la flag ya que este binario esta ejecutando en un servidor remoto por el cual para su conexión tan solo deben de usar nc pwnable.kr 9022

Si abrimos radare2 y vemos las strings lógicamente no veremos la flag ya que desde el servidor nos dieron disponible el source code para compilarlo sin la flag, y vemos que estábamos en lo cierto con el comando iz.

Si echamos un vistazo rápido a la función main, nos damos cuenta que para llegar a la flag se debe ejecutar primero dos bucles. El primero de todos es para especificar el size del memcpy así que empezaremos por este.

Pasando input por scanf

Seteamos un breakpoint justo al inicio del bucle y vamos analizando que sucede justo en la dirección de memoria 0x0804895d.

Justo después de la llamada a la función scanf la variable local_6h que fue pasada como argumento tiene el contenido que le pasamos, en este caso le pasamos 8 y se le mueve al registro eax. Luego compara con la variable local_54h que en este caso contiene 8 y luego viene un salto condicional jb en el cual saltará si es menor imprimiendo una string y saliéndose del programa.

Sino salta realizara el mismo procedimiento pero comparando con el valor mayor es decir con 0x10 que es 16 realizando el salto condicional jbe saltando si es menor o igual. De esta manera el programa comprueba que los valores que introducimos son entre 8 y 16 básicamente en la primera vuelta al bucle y mismo procedimiento para los demás variando el tamaño de los size.

16-byte aligned

Una vez visto el primer bucle vamos a por el siguiente pero partiendo del error que nos dio el programa ya que si no pasamos el size correcto por input genera una violación de segmento y deja de ejecutarse y nosotros debemos saber el ¿cómo? y el ¿por qué?. Tenemos que contestarnos a estas preguntas básicamente porque no tenemos que ganar una shell, únicamente que se ejecute el programa completo para poder ejecutar la función puts y nos de la flag en el servidor. Este reto es puro reversing y entendiendo bien las instrucciones en ensamblador se puede sacar.

Usamos GDB para ver el registro eip cuando se genera la violación de segmento.

Apunta a la dirección 0x80487cc a la instrucción movntps XMMWORD PTR [edx],xmm0
Es la primera vez que veo esa instrucción así investigaremos en google y además setearemos un breakpoint justo en esa dirección para saber lo que ocurre.
Si buscamos sobre la instrucción movdqa nos indica que si el operando de origen o de destino es un operando de memoria, el operando debe alinearse en un límite de 16 bytes (16- byte boundary) o se generará una excepción de protección general (#GP). Ahh, vale entonces debe estar alineado sino saltará la excepción que nos ocurrió. Si vemos lo que significa “16- byte boundary” en google si la alineación es de 16 bytes (128 bits), significa que la dirección de memoria de los datos debe ser un múltiplo de 16. Un ejemplo sería con esta dirección 0x10 siendo múltiplo ya que los últimos bits en binario son cero 0b00010000 sin embargo 0x12 que en binario es 0b00010010 los últimos bits no son cero sino 0010.
Bien por lo tanto el registro edx será una dirección de memoria del heap que se usa como destino que deberá estar alineada 16 bytes sino dará violación de segmento ya que por defecto el tamaño del heap que se alloca con malloc es de 8 bytes por defecto.
Ejecutamos y vemos que edx vale 0x088c0460 con el comando px.

Si vemos en binario es 0b00001000100011000000010001100000 estando los últimos bits a cero. Eso significa que si avanzo un paso más en la ejecución no debería de dar error, ¿cierto? Y así es, no dio error jeje. Pero tenemos el problema que no sabemos en que valor de los que introdujimos se paro… Nosotros introdujimos (8,16,32,64,128,256,512,1024,2048,4096) para ello tendremos que avanzar paso a paso para saber cual fue el valor último. Avanzando vemos que cuando el registro eip apunta a la dirección de memoria 0x08048aa6 el registro eax vale 0x80 que es nuestro valor 128 jeje. Bien ahora ya sabemos o tenemos controlado por que input esta y viendo que con los 4 primeros (8,16,32,64) no dio ningún fallo al realizar las instrucciones XMM vamos a ver que ocurre con este.
Vemos que el registro edx vale la dirección de memoria 0x088c04a8 que en binario es 0b00001000100011000000010010101000 siendo los últimos bits 1000 y no esta a cero como vimos por tanto no será múltiplo de 16 y generará la violación de segmento jeje.

Y vimos que estábamos en lo cierto radare2 no me deja avanzar justo cuando el registro eip apunta a la dirección de memoria 0x080487cc
La solución sería entonces elegir un valor comprendido entre 128 y 245 cuyos últimos 4 bits sean cero para cumplir ese alineamiento de 16-byte boundary jeje. Debido a que se de la existencia de writeups de este reto y la resolución del mismo es puro reversing creo que esta más que dada la solución así que vamos a resolver.
Una solución sería realizar bruteforce obviando los tres primeros (8,16 y 32) y esperar a la respuesta de la flag por parte del servidor. No requiere tiempo aunque mi solución es random pero al segundo intento conseguí el alineamiento de 16 bytes de todos los sizes enviados excepto el último es cuestión de esperar un poco unos minutos jeje. Aquí mi exploit.

Y la solución que queríamos era ejecutar el programa sin ningún fallo ya que le pasamos el alineamiento correctamente al registro edx seteando los últimos 4 bits a cero para así ser múltiplo de 16 como vimos un poco mas arriba gracias a nuestro input con el buffer size. Cuando ejecutamos el exploit y finaliza el bruteforce ya obtenemos los chunk sizes que necesitamos ya solo sería modificar el exploit y meter esos valores para que el programa funcione sin fallo. La clave esta frase “When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte boundary or a general-protection exception (#GP) will be generated.” De esta web: http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc183.htm

También me ayudo mucho entender fácilmente que es “16-byte boundary” esto: https://stackoverflow.com/questions/10224564/what-does-alignment-to-16-byte-boundary-mean-in-x86

Espero que os haya gustado y ayudado el tutorial, un saludo naivenom!.