Pwning windows console application – Primitiva de escritura, leaks y ROP! 0x2

Buenas a todos hacia mucho tiempo que no escribía una entrada en el blog y ya va siendo hora de realizarlo. Usaremos un reto propuesto por Ricardo Narvaja y que podéis encontrar en el grupo de telegram CLS Exploits por si queréis poner en practica vuestras habilidades. El reto lo encontraréis con el nombre de Desafío2 por eso el título del artículo como 0x2. El principal objetivo es obtener ejecución de código como por ejemplo la calculadora de Windows o una shell para tener RCE. Bien es cierto, que también puede ser usado este binario para realizar nuestro adiestramiento en tareas de ingeniería inversa y que nos imprima por salida estándar una string mostrando que hemos ganado «you are a winner man», pero el pwning es más divertido como van a observar a continuación. En cuanto a ingeniería inversa daré cosas por obvias y si quieren profundizar os recomiendo esta saga de entradas usando radare2

Este reto me ha encantado porque para poder tomar ventaja de algún tipo tienes que darte cuenta de una serie de vulnerabilidades y planear una estrategia con el fin de ejecutar código y obtener un RCE o ejecutar la calc.exe. Cuando ejecutamos el programa vemos que siempre se ejecuta en bucle lo cual nos puede facilitar mucho la explotación ya que si estamos en bucle podemos tomar ventaja multitud de veces, siempre y cuando no existan problemas de distintas índoles que ya veremos más adelante. Otra cuestión es que estoy redactando la entrada una vez he podido explotarlo y si que me gustaría ver los diferentes Fails que he tenido y así aprender de los errores a la hora de redactarlos, así que pido disculpas y para la siguiente redactare las notas según voy resolviendo el reto.

Integer Overflow

Vamos a comenzar con lo más sencillo imprimiendo por salida estándar con la función printf la string «you are a winner man». Para ello habrá una serie de condiciones necesarias y explotar un integer overflow.

Analizando el desensamblado por encima vemos que nuestro input de entrada es a través de la lectura de un fichero de texto denominado file.txt por lo cual vamos a escribir en este fichero 32 bytes y así poder identificarlas rapidamente cuando hagamos el proceso de debugging.

4141414141414141 4141414141414141
4343434343434343 4242424242424242

Cuando empezamos a debuggear nos damos cuenta seguidamente después del prólogo de la función main ,se mueve con la instrucción lea un puntero del stack en el stack.

Es bastante reseñable mencionarlo y gracias a esto desencadenará una serie de situaciones en la que podremos tomar ventaja en la explotación del programa y obtener ejecución de código ejecutando la calculadora.

Seguidamente abre el fichero file.txt y comprueba si existe algún tipo de error, si no existe el fichero no saltará en el salto condicional llamando a la función exit para salirse del programa. La siguiente comprobación es si el fichero file.txt esta vacío o no y si esta vacío salta con el salto condicional y se sale de la función (no del programa con el call a exit).

Si nos damos cuenta la lógica de la aplicación varía en cuanto a si no existe fichero o si el fichero esta vacío simplemente el bucle va a dejar de efectuarse de igual modo y por ende, va a salirse el programa pero de distinta manera. Es un dato a tener en cuenta para luego jeje. Continuamos y una vez abierto el fichero sin errores y no estando vacío vemos que el contenido del fichero ha sido copiado al stack justo debajo de la dirección de memoria del stack que se seteo al principio de la función en rsp+98h+var_58.

Lo siguiente que nos pedirá será dos input por consola (width y height) realizando su multiplicación y almacenando el resultado en el stack en Área: rsp+98h+var_78. En modo resumen podemos apreciar en el desensamblado que si el primer input width almacenado en el stack en rsp+98h+var_74 es menor que cero saltará en el salto condicional jl y nos imprimirá por salida estándar usando la función printf la string «you are a looser man %p». Nos damos cuenta que nos realiza un leak de .text y sin aprovechar nada del binario.

Mas adelante vemos otro check o comparación de 0xc(12) con lo almacenado en el stack como resultado de la multiplicación de ambos inputs. Por lo tanto ya sabemos que el resultado de la multiplicación no puede ser superior a ese numero y también que el primer input no puede ser un numero menor que cero. Vamos a poner de primer input 12 y segundo un 1 para probar y ver que pasa.

En la comparación apreciamos que no es mayor y siendo igual en el salto condicional ja no salta siendo justo lo que queríamos. Seguidamente podemos deducir que llama a un strcpy ya que los argumentos que se le pasa en los registros rdx y rcx son el puntero de la string que va a copiar y el destino, respectivamente. Ahora nos damos cuenta que en el registro rcx se mueve con la instrucción mov la dirección de memoria del stack que se seteo al principio justo antes del bucle y justo después del prólogo de la función main.

Como pueden observar tenemos un problema ya que ha copiado 12 bytes y no llegamos justo a rsp+98h+var_50 siendo donde empieza la comparación con 0x42424242 y así poder imprimir por salida estándar la string «you are the winner man».

Tendremos de alguna forma que desbordar el entero para así poder copiar con el strcpy las B’s justo en esa posición. Si multiplicamos 100*42949673 nos da como resultado de la operación 4 debido a un integer overflow descartando el byte superior ya que en realidad el resultado de la operación es 0x100000004. Ahora sí, podremos copiar 100 bytes y como 0x4 es menor a 0xc no saltará en el salto condicional ja

Ejecutamos de nuevo aprovechando el integer overflow y vemos que ahora si hemos ganado!.

Leak dirección de memoria de una función .text

Para el leak simplemente con poner un numero negativo será suficiente para que imprima el puntero con el testigo de formato del printf y así disponer del leak del .text de una función bastante interesante llamada system.

Bruteforce low byte-write primitive y return-to-text. Leak del stack y otras oportunidades…

Si recopilamos con lo visto anteriormente apreciamos que en el stack hay un puntero al stack que se usa como destino en el strcpy y casualmente en el byte 17 cuando se copia con la función strcpy, es el ultimo byte de la dirección del stack con lo cual podemos pisar el ultimo byte y así tener primitiva de escritura en otro sitio. Por ello debemos saber que las direcciones del stack son aleatorias y si queremos tener primitiva de escritura en un valor de retorno para poder volver a otro sitio de la función main debemos saber que existirá un factor aleatorio que tenemos que asumir, pero siendo solo un byte la aleatoriedad es asumible y puede resultar explotable y así seguir en nuestra estrategia de explotación del binario.

En este escenario de explotación hemos attacheado con IDA y una vez asumido que el último byte al cual vamos a escribir es justo la dirección de memoria del stack en la cual cuando entre en la función strcpy estará la dirección de retorno de la función main 0x13F8C121F donde tendrá que volver. En nuestro file.txt tendremos que poner el payload necesario para poder escribir en el último byte y así pisarlo y como dije anteriormente es en la posición o byte 17. Tenemos otro problema ahora y es que tenemos que aprovechar el integer overflow para poder entrar en la función del strcpy sino nunca entrará y no tendremos ventaja en la explotación. Según unos cálculos para un size de 17 bytes tenemos que usar en el otro input (height) lo siguiente: 1085102592318504960.

File.txt:

4141414141414141 4141414141414141
a0

Como pueden observar hemos elegido 0xa0 obviando el factor de aleatoriedad ya que sabemos que es justo el ultimo byte de la dirección del stack 0x1ffca0 pero es bien sabido que con bruteforce sale bastante rápido y se puede controlar fácil cuando se escribe el exploit, de hecho, cuando estuve investigando tomaba en cuenta la aleatoriedad y no tardaba mucho. Con esto conseguimos que en esta iteracción del bucle hayamos modificado el valor de destino de la función strcpy apuntando a otro sitio. Ahora os preguntaréis y, ¿por qué quieres que apunte allí? Bueno basicamente es porque para la siguiente vez en el bucle podremos escribir en el valor de retorno de la función strcpy y retorne en otro sitio y así poder triggear un leak. Elegí el retorno de esta función por su poca distancia desde la primitiva de escritura inicial y en donde hay que escribir (esto es importante ya que contamos con el último byte para bruteforcear), además que cuando sale de la función en el registro rdx contiene la dirección de memoria del stack y si saben ustedes cuando se llama a la función printf el argumento correspondiente al registro rdx (los argumentos son registros porque estamos en 64 bits, no se pushean al stack) es un puntero.

Por lo tanto vamos a tomar ventaja del printf que nos hace leak del .text y usar la primitiva de escritura retornando allí y realizando leak del stack. Una vez tenemos listo el file.txt ejecutamos y escribimos nuestro input por consola con el correspondiente integer overflow y analizamos que sucede.

Como podéis observar hemos escrito correctamente el último byte donde queríamos y también observamos que ahora si está la dirección donde retorna el strcpy a la función main en 0x13F8C121F. Ahora que tenemos la primitiva de escritura donde queremos tenemos que armar un nuevo file.txt para copiar los dos últimos bytes del retorno y haga un return-to-text justo en la instrucción «0x13F8C11B6 lea rcx, aYouAreALoooser» para que así el registro rcx tenga ese puntero y no rompa cuando se ejecute printf.

File.txt:

4141414141414141 b611

Ahora nuestro fichero tiene de contenido 10 bytes y por lo tanto en nuestros dos inputs no hará falta un integer overflow simplemente con poner en el primero 10 y en el segundo 1 es válido. Seguimos ejecutando y vemos el resultado.

Boom! Leak de la dirección del stack. Bien, ahora que tenemos el leak del stack el factor de aleatoriedad no será un problema ya que ahora que tenemos ese leak simplemente es ir calculando diferencias en la memoria para poder ir armando nuestro exploit de forma manual. Es curioso porque el leak nos devuelve la dirección de memoria del stack justo de la ultima copia del file.txt al stack en el byte 0xa, correspondiente a nuestro 10 que introdujimos.

Como podéis observar aun seguimos teniendo la primitiva de escritura en el mismo sitio por lo cual esto nos supone un problema ya que ahora no podemos escribir nunca más en otro lugar debido a la distancia en la que copia el destination del strcpy no llega para sobreescribir el low-byte de la dirección de memoria. Por lo tanto debemos pensar en otra estrategia siendo lo comentado justo al principio del artículo. Sabemos que el puntero se setea después del prólogo de la función main y justo antes del inicio del bucle. Usaremos el return-to-text por lo cual con ir al inicio podremos setear de nuevo el puntero teniendo ventaja y seguir con la tarea de explotación. Elegimos la dirección en 0x13F8C10D0 justo después del prólogo.

File.txt

4141414141414141 d010

El input es igual que lo anterior 10 y 1. Ejecutamos y vemos si podemos volver a tener nuestra preciada primitiva de escritura.

Listo allí estamos de nuevo bypasseando el bucle y retornando justo despues del prólogo pudiendo setear de nuevo el puntero del destination del strcpy. Ahora que tenemos el leak sabemos que es la dirección de memoria 0x1ffd0a y la primitiva de escritura esta en 0x1ffceo y este puntero esta almacenado en el stack en la dirección de memoria 0x1ffcf0.

Esta información se obtiene attacheando y debbugeando lógicamente. Realizando la diferencia entre ellas siempre tendremos control de la distancia que se encuentra pudiendo siempre calcular a la hora de explotar manual o escribir un exploit. También nos damos cuenta que no hace falta escribir un exploit ya que teniendo el file.txt podemos escribir los bytes en little-endian y no necesariamente carácteres de teclado.

Hijack call exit ptr to main

Si restamos 0x1ffd0a-0x2a obtenemos justo la dirección donde se copia usando la primitiva de escritura siendo siempre así en 0x1ffce0.

Ahora haremos que el destination del strcpy sea así mismo, es decir, justo en la dirección que contiene el destination. Para ello tenemos que armar nuevamente el file.txt sabiendo con nuestro cálculo que hay que sumarle 16 bytes más al resultado anterior, es decir, 0x1ffce0+16= 0x1ffcf0 siendo el último byte 0xf0 el elegido. 

File.txt

4141414141414141 4141414141414141
f0

En ambos input usaremos el integer overflow nuevamente correspondiente a: 17*1085102592318504960. Ejecutamos y vemos el resultado justo cuando se llama a la función strcpy.

Vemos que se realizó correctamente. Bien ahora pasemos a otra cosa sabiendo que podemos usar la función exit para hacer un hijack del call <ptr_function> y apunte a otro sitio.

Para ello es un «one shot or die» que quiere decir, que si no funciona ya nunca jamás podremos volver a usar la primitiva de escritura y adiós explotación pero la idea es bien básica simplemente ahora tenemos que coger la dirección donde apunta al call y convertirlo en el destination del strcpy para cuando se vuelva a ejecutar de nuevo el bucle escriba allí otra dirección que nos resulte a nosotros ventajoso en la función main, en este caso será en el inicio del prólogo. Es una fantástica estrategia si sale bien, ya que siempre que queramos renovar la primitiva de escritura simplemente con hacer que no existe el fichero file.txt se redijirá el flujo de ejecución hacia el exit y realmente no se saldrá del programa sino que irá al sitio que nosotros hemos indicado. Asi que vamos alla!. En nuestro file.txt tendrá el siguiente contenido ahora:

File.txt

08708e3f01

El contenido es la dirección del puntero en el call a exit y vemos que son 5 bytes lo que contiene nuestro fichero por lo tanto no hace falta usar el integer overflow usando 5 y 1.

Ahí lo tenemos listo, en el siguiente bucle copiará alli lo que nosotros le indiquemos ahora con otro contenido en file.txt. ¿Qué será? Bueno será la dirección de memoria correspondiente al inicio del prólogo de la función main.

File.txt

c0108c3f01

Los inputs será igual 5 y 1 ya que son 5 bytes lo que quiero que copie. Una vez ejecutado queremos que se ejecute de alguna forma la llamada al exit y sabemos que era no existiendo el file.txt por lo tanto una vez ejecutado cambiamos el nombre a file1.txt y vemos lo que ocurre.

Boom! Justo al principio y con primitiva de escritura para siempre de manera permanente ya que cuando queramos restablecerla simplemente tenemos que triggear la llamada a la función exit hijackeada. Nice :D.

Escribir la string calc en .data

En este punto agradecemos el leak del .text ya que gracias a él podemos calcular la diferencia siempre sabiendo dónde escribir la string calc en .data. Para ello usaremos la misma estrategia anterior por lo tanto seré más breve.

  1. Al volver al prólogo se ha restablecido el marco de la pila de la función main por lo tanto debemos calcular la distancia de nuestro leak 0x1ffd0a a donde esta situada ahora la primitiva de escritura del strcpy a la hora de la explotación.
  2. Usar el leak para el low-byte write primitive así mismo.
  3. Encontrar una dirección en .data donde escribir la string calc, yo usaré 0x13F8E7118
  4. Escribir calc allí.

Una vez hecho esto vemos el resultado. Vemos nuestra string en .data jeje

Return-Oriented Programming

Una vez realizado tenemos que triggear de nuevo la función exit hijackeada restableciendo nuestra primitiva de escritura y dar por concluida la explotación del binario usando un ropchain. Sabiendo que en el binario está la función system podemos hacer un ropchain que nos permita pasarle por argumento en el registro rcx el puntero de la string de calc en .data y así ejecutar código, en este caso, la calculadora de Windows. La estrategia ahora será la siguiente:

  1. Nuevamente hay que calcular la distancia de nuestro leak del stack a donde esta ahora la primitiva de escritura del strcpy debido a que se ha restablecido un marco de pila con el prólogo del main.
  2. Usar el leak para el low-byte write primitive así mismo.
  3. Seguidamente tenemos que calcular la distancia donde tenemos la primitiva en el stack a donde va a estar la dirección de retorno de la función strcpy. Una vez restablecido el prólogo sabemos que la primitiva de escritura esta siempre 16 bytes menos (Tenemos que tener en cuenta el leak y realizado ya los cálculos). Ahora mismo esta nuestra primitiva en 0x1FFBA0 , y 0x1FFBB0 siendo la direccion que la contiene. Bien, pues la dirección de retorno de strcpy siempre va a estar con respecto a 0x1FFBA0 a una distancia de 56 bytes.

Ahora veréis que esas direcciones no son iguales debido a que tuve que triggear de nuevo el call al exit hijackeado, pero bueno con tener calculadas las distancias desde el leak del stack hasta donde se restablecen los marcos de pila que van a ser solo dos veces, siempre será asi a la hora de explotar manualmente o con un script. Con esta prueba podéis comprobar que es posible realizar la llamada a call exit hijackeado todas las veces que queráis, ya que siempre se volverá a restablecer la primitiva de escritura debido a la instrucción lea después del prólogo, y siempre si explotamos sin usar el debugger usaremos en nuestra explotación solamente dos veces el call exit -> Dos marcos de pila nuevos. En este caso solo por hacer la prueba se ha restablecido 3 veces el marco de pila.

Quiero explicar el punto 2. Además de usar el leak para el low-byte write primitive en el file.txt debemos colocar el ropchain quedando de la siguiente manera:

Como podéis observar en la imagen superior tenemos el ropchain ya montado siendo nuestro objetivo retornar justo en el contenido de la dirección de memoria 0x1ffb38 y para ello necesitaremos un gadget que añada al registro rsp la distancia necesaria hasta allí en el stack.

Pero bueno de momento tenemos el ropchain con 3 direcciones de memoria:

  • Gadget pop rcx -> Hacer que el puntero de .data que contiene la string calc este en el registro rcx
  • Ptr calc en .data
  • Dirección .text de la función system.

En el punto 3, simplemente se realiza el cálculo que tenemos que tener en cuenta desde nuestro preciado leak hasta donde estamos ahora y tenemos que en la dirección 0x1ffac8 estará la dirección de retorno del strcpy. Usamos la primitiva para que se escriba a si misma con esa dirección.

Finalmente tenemos que escribir el gadget en la dirección de retorno en la última vuelta. Este gadget será:

Una vez ejecutamos hasta el retorno dentro de la función strcpy vemos como ahora estamos en nuestro gadget.

Hacemos pop del stack al registro rcx apreciando que las demás instrucciones interactuando con el registro rax no nos va a importar mucho debido a que luego cuando entre en la función system se van a xorear el registro rax a si mismo para hacer el canary asi que no hay problemas.


Vemos que hemos retornado en la función system nice!!!!

Le damos a continuar y vemos que ejecutamos nuestra calculadora.

Si tienen algún tipo de inquietud o duda no duden contactar conmigo escribiendo comentarios en esta entrada o directamente por privado en Telegram y Twitter. Hasta la próxima y espero que les haya sido de utilidad si andaban resolviendo el reto de Ricardo Narvaja. Un saludo, @naivenom.