Bastionado, seguridad en sistemas: Reto LvL3 de la NcN2013 pre { background:#eeeeee; border:1px solid #A6B0BF; font-size:120%; line-height:100%; overflow:auto; padding:10px; color:#000000 } pre:hover { border:1px solid #efefef; } code { font-size:120%; text-align:left; margin:0;padding:0; color: #000000;} .clear { clear:both; overflow:hidden; }

Bienvenido al blog

Bienvenidos al blog de seguridad en sistemas

jueves, 3 de octubre de 2013

Reto LvL3 de la NcN2013

Al final me puse el martes por la noche un rato a jugar con el reto de tercer nivel de la NcN el cual consistía en un fichero ELF (binario). Aunque se que hay gente que se había pasado el reto, preferí no cotillear y intentar pasarlo por mi cuenta. Al final me salieron cuatro formas de pasarlo, que no quiere decir que no hayan más.

Lo primero que hice es comprobar de que tipo de binario se trata, para ello utilice file
$ file level.elf 
level.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xb589d432799bf15343387fea63d4bdc00faa177c, not stripped
Como vemos se trata de un binario para Linux de 64 bits. Durante el tiempo del reto me encontré con que no disponía de un entorno de 64 bits, lección aprendida para la próxima. Lo que hice fue bajarme una Ubuntu de x86_64 para poder usar el GDB con el binario. 

El siguiente paso fue meter el binario en el entorno linux y ejecutarlo para ver de que se trataba. Al ejecutarlo nos pide introducir un carácter, al introducir uno aleatorio nos tira del programa, por tanto parece que el binario comprueba carácter a carácter si la entrada es valida:

$ ./level.elf 

|  >  Type to win, only what I want to read... 
|  >  (Carácter)
|
|  -> I DON'T THINK SO

Probando todos los caracteres validos en ASCII llegas a la conclusión de que si introduces un espacio te aparece un "*" y se queda esperando el siguiente carácter, por lo que según parece comprueba carácter a carácter si la contraseña es valida. 

Llegado a este punto lo mejor es pedirle a un amigo que tenga licencia de IDA para que te cargue el binario desensamblandote el código y puedes seguir el flujo del programa de forma más sencilla que con un GDB. Recordar que se debe usar el binario del ida de 64 bits y no el de 32 bits (Spankito, gato eres un sol xD). La explicación del programa está al final del post, lo he dejado así para hacer más sencillo la lectura para aquellos que solo quieran conocer las posibles soluciones.

Por tanto, una vez analizado el código con el IDA sabemos que hay cuatro formas de pasarse el reto, voy a ponerlas de más sencilla a más difícil:


Primera forma: 


Usar un script simplon que que ejecute el programa y vaya probando con los 127 caracteres de la tabla ASCII, cuando encuentre "*" o el programa no termine, que almacene o muestre ese carácter y siga probando. Cuando haya metido los 10 caracteres ya tenemos la contraseña.

Está fue la solución de Balidani. Podre parecer idiota pero a mi no se me ocurrió, me metí de lleno con el IDA y el GDB, caí en la cuenta al final. La más fácil y la última opción a barajar. 


Segunda forma:


Modificar el EIP para que directamente salte a la función "no_me_jodas_manolo" sin necesitar conocer la contraseña de entrada que nos permitiría acceder a dicha función. Para esto solo necesitábamos una distro linux de 64 bits y un GDB.

Al cargarlo con el gdb, y por manía mía ponerlo en notación de Intel, lo único que hay que hacer es decirle a gdb que arranque el programa con la orden "start" una vez hecho esto debemos de desensamblar la función "no_me_jodas_manolo": "disassemble no_me_jodas_manolo" donde obtendremos la posición de memoria donde empieza la función:


En este caso me salto la primera instrucción puesto que es un push para salvar el registro "rbp" que no necesito para nada, por tanto la posición de memoria es 0x40077d. Ahora lo único que hay que hacer es decirle que el registro "PC" vale esa posición de memoria con la orden "set $PC=0x40077d" y darle a la orden de continuar "continue":



Está fue la segunda forma con la que me pase el reto. Con cuatro ordenes de GDB listo.


Tercera forma:


Sabemos que al principio hay un bucle que coge la cadena "facebookctf_rocks", donde se usa el contador ascendente del bucle "[rpb+var_8]", el cual se incrementa de 1 en 1, para recorrer la cadena. Teniendo en cuenta que dicho contador es multiplicada por 4 para acceder a la cadena, entendemos que recorre la cadena "facebookctf_rocks" leyendo 4 en 4 bytes. La cadena contiene los siguientes valores:



Para hacer las cosas más fáciles vemos que cada 4 bytes la cadena solo tiene datos el byte menos significativo, por lo que lo único que hay que hacer es coger esos byte menos significativos de las siguientes 10 parejas de 4 bytes, componer la cadena y traducir esos byte a su carácter ASCII correspondiente:

"0x20,0x53,0x55,0x52,0x50,0x52,0x49,0x53h,45h,21h"

Es decir:

" SURPRISE!"

Esta fue la solución que empleo @julianvilas. En mi caso también fue la primera solución que me salió.


Cuarta forma:


Esta es desde luego la forma más difíciles de las anteriores y donde más chicha había al usar varias instrucciones de 64 bits combinadas con instrucciones de 32 bits. Puede parecer una tontería pero en mi caso no había tocado 64 bits en mi vida y me volví loco al principio, pese a ser bastante sencillito. 

La solución era coger la función "no_me_jodas_manolo" y entender su funcionamiento para obtener la cadena resultado final. Esta función lo único que hacia era coger de la cadena de 4 bytes en 4 bytes "serious_business" y la cadena "the_truth" de 8 bytes en 8 bytes. Sumaba ambas y el valor era la posición de la memoria donde se encontraba el carácter. 

Veamos un ejemplo para entender su funcionamiento:

El valor de la  cadena "serious_business" es este:



El valor de la  cadena "the_truth" es este:



Los cuatro primeros bytes de "serious_business" valen  "0x05 0x00 0x00 0x00" es decir "0x05" y los 8 primeros bytes de "the_truth" valen "0x68 0x12 0x40 0x00 0x00 0x00 0x00 0x00" es decir "0x68 0x12 0x40". Sumamos ambas cadenas "0x68 0x12 0x40" + "0x05" = "0x6D 0x12 0x40". 

Como se trata de un little endian la dirección contenida es "0x40 0x12 0x6D" o 0x40126D. Accedemos a esa posición de memoria y cogemos el byte el cual contiene el carácter que buscamos. En este caso dicho valor es "39".

Si usamos GDB solo hay que poner "p/x *posición de memoria" es decir "p/x *0x40126d":



Si usamos IDA le damos a buscar dirección o "G" y introducimos la posición buscada. Luego pinchamos sobre la Hex-View puesto que queremos verlo en crudo y que no nos lo intente desensamblar:



Como vemos el valor es 0x39 que en ASCII corresponde con la letra 9, es decir, que el primer valor de la cadena del resultado vale "9". 

El segundo valor sería lo mismo pero cogiendo los siguientes 4 bytes de "serious_business" y los siguientes 8 bytes de "the_truth", es decir, 0x19 + 0xB0 0x12 0x40 = 0xC9 0x12 0x40. Vamos a la posición 0x4012C2 cuyo byte es el valor 0x65 y por tanto el segundo carácter vale "e".

Esta fue la tercera manera con que me pase el reto. 


Explicación del funcionamiento del programa:


Al cargar el programa nos vamos directos a los primeros bloques después del main donde vemos lo siguiente:



Analizando un poco vemos que la última instrucción del main antes de saltar al bloque "4010F3" es almacena en la posición de memoria "[rpb+var_8]" el valor 0. En el bloque "4010F3" lo que hace es obtener el primer carácter de la cadena almacenada en "facebookctf_rocks" y compararla con el valor leído por teclado el cual lo almacena en "[rpb+var_4]". Al finalizar comprueba si la comparación es distinta de 0 y por tanto se trata de distinto caracteres. Si son distintos salta a un bloque que pone la variable  "[rpb+var_C] a 0"

Si no se cumple la condición de ser distinto de 0 es porque son los mismos caracteres, entonces realiza dos comprobaciones más. La primera si el carácter introducido es 0x51 y la segunda si el carácter introducido no es 0x71. Teniendo en cuenta los caracteres ASCII está comprobando si no hemos apretado la letra "Q" y luego la letra "q". En caso de que si sea va al mismo bloque que si hubiéramos puesto un carácter distinto del leido en "facebookctf_rocks". 

Vamos que no tiene mucho sentido introducir "q" o "Q" porque aunque estuviera en la cadena "facebookctf_rocks" acabaría por salirse del bucle.

Por último, y solo en caso de que el carácter introducido sea correcto incrementa la variable "[rpb+var_8]" en 1. Por lo que si tenemos en cuenta que la condición empieza en 0, que es un igual o menor que 9, sabemos que el password a buscar es una cadena de 10 caracteres. 

Una vez pasado esta comprobación de la cadena de entrada vemos que en caso de que todo haya salido bien y hayamos introducido los 10 caracteres salta al bloque "401154":


Está lo único que hace es ver si la posición "[rpb+var_10]" vale 1 (hemos apretado "q" o "Q") y saltar a la función seeyaaaaa o de lo contrario saltar a otro bloque que lo que hace es comprobar si  "[rpb+var_C]" es 0 y por tanto el carácter fue incorrecto saltando a la función game_over. Si  "[rpb+var_C" valía 1 salta a un bloque donde se ejecutan dos funciones success y no_me_jodas_manolo. La primera lo que muestra es una cadena de texto por pantalla que al traducirlo al ASCII es la que muestra lo de congratulation, la segunda es la encargada de generar el código que muestra que nos hemos pasado la prueba. 

El funcionamiento de "no_me_jodas_manolo" está explicado en la cuarta forma de pasarse el reto:



Simplificando era un bucle que iba obteniendo la cadena "serious_business" cuyo posición a recorrer se almacenaba en [rbp+var_c] obteniendo los 4 bytes a partir de esa posición y a su vez recorría la cadena "the_truth" con el puntero almacenado en [rpb+var_8] obteniendo 8 bytes en 8 bytes. Sumaba la posición más bajas de ambas cadenas y ese valor era la posición de la memoria cuyo byte contenida el valor hexadecimal del carácter mostrado como resultado. 

No hay comentarios:

Publicar un comentario