En la entrada anterior, descargamos y revisamos muy por encima lo que Cutter nos presentaba con sus paneles y ventanas. Hoy daremos inicio al análisis del primer crackme de IOLI

En la entrada anterior, descargamos y revisamos muy por encima lo que Cutter nos presentaba con sus paneles y ventanas. Hoy daremos inicio al análisis del primer crackme de IOLI. Para la gente acostumbrada al Reversing, esto sera cosa de todos los días. Pero si eres nuevo, te invito a analizar a detalle lo que a continuación se presenta.

Si estas completamente iniciando, te invito a leer el material (tutoriales, writeups, videos) que se ha escrito en la comunidad de CrackLatinos (CLS), es impresionante. En estos tutoriales omito algunas cosas mas asociadas a entender el detalle de las instrucciones y la estructura de los binarios.

Comenzamos por iniciar Cutter y presionar “Select”, navegamos hasta la carpeta del crackme0x00 y lo seleccionamos. En mi caso queda de la siguiente manera:

Open

Continuamos por dar a open y nos aparecerá la ventana de “Load Options”, de momento continuamos con los valores por defecto y damos a “Load”

Load

Tras acabar la identificación y análisis de nuestro binario, nos deberá aparecer una ventana como la siguiente:

initial-load

Footprint

Iniciamos realizando una identificación y exploración del binario, por lo que cambiamos al panel de dashboard para ver que tenemos con este binario. La primera columna nos dice la siguiente información:

info1

Tenemos un formato ELF32, escrito en C. Continuamos con la siguiente columna:

info2

Primero tenemos que tiene 3 File Descriptors, la base address es 0x08048000 (con el ajuste a 0), es user-space, sin canarios ni crypto. No es estático, mas abajo veremos que tiene una biblioteca de la cual depende.

info3

Finalmente, en la tercera columna podemos observar que pertenece a una arquitectura i386 para Linux. Y como era de esperarse con intel, es little endian.

info4

La parte inferior nos indica que la entropía es moderadamente alta y que el binario depende de libc, lo cual lo convierte en LDD como se menciono atrás.

Entrypoints

Ok, antes de analizar la ventana de funciones, analizaremos cuantos puntos de entrada tiene este binario, para ello en la barra de menus seleccionaremos “Windows->Entry Points” lo cual nos abrirá una pestaña más en la parte inferior:

entrypoints1

Al seleccionar “Entry Points” en la parte inferior, observaremos que nos vamos a otro panel en donde solo tenemos una entrada, la cual apunta a la dirección 0x08048360, la cual también es etiquetada como entry0 y es la posición donde actualmente nos encontramos. Un punto importante que aun no he mencionado a detalle es que, en la parte superior se encuentra la barra de address/section, que nos marca en rojo la posición y sección en la que nos encontramos actualmente, esto es bastante cómodo cuando queremos movernos rápidamente sobre .text o otras secciones.

Hit: Podemos dar click derecho sobre la barra de desplazamiento y nos abrirá un menú para seleccionar algún otra ventana que no tengamos en este momento, inclusive nos da un vistazo rápido a todas las ventanas posibles.

Strings

Continuamos aun sin ir a la ventana de funciones, esta vez pasaremos rápido por strings. Para ello damos a la pestaña de strings que se encuentra en la parte inferior:

strings1

Como dato a analizar, las direcciones de los primeros strings son diferentes a los de los últimos. También podemos observar que aparecen algunos mensajes probablemente asociados a la ejecución.

strings2

Funciones

Ahora si, la sección de funciones, en donde nos moveremos, renombraremos y predecimos que sucede durante la ejecución con ayuda del análisis estático.

functions1

Podemos observar rápidamente, que ya radare2 identifico correctamente la función main, que tal como recordaremos es la función que se encuentra en todo programa escrito en C, y que ademas tiene la función de unir como pegamento todas las partes e iniciar el programa.

Como también podemos observar por la manera en la que se presentan los nombres de las funciones, algunas empiezan por “sym.imp”, indicándonos que han sido importadas. Esto se puede observar mas claramente si seleccionamos en el panel derecho la pestaña de “Imports” como aparece a continuación:

functions and imports

También podemos ver que se marca en verde la función main, esto es debido a que se encuentra explícitamente creada dentro del código como veremos en otros crackmes. En negritas siempre estará marcado aquella posición en donde nos encontremos. Para ir a una función debemos dar doble click izquierdo, por lo que clickeamos a main:

goto-main

Arriba de main, se encuentra una función llamada fcn.08048384, la cual recibe ese nombre tras no poder ser identificada como una función dinámica y se deja un nombre genérico que haga referencia a su ubicación dentro de la sección .text. Las funciones pueden ser renombradas dando click derecho y seleccionando “rename”, tal como veremos un poco mas adelante. Otras opciones importantes son “xrefs” y “Add comment” las cuales veremos mas a detalle un poco mas adelante.

functions-options

Continuemos con la siguiente parte, el Disassembly.

Desensamblador

Ok, la pestaña donde mas pasaremos tiempo analizando las instrucciones del binario, la del desensamblado del binario. Como podemos ver gracias al panel de funciones, ya nos encontramos en la función main y podemos analizar todo el código desensamblado de esta función:

dis-main

Lo primero que observamos es lo que sucede en la primera posición:

(fcn) main 127
  main (int argc, char **argv, char **envp);
          ; var char *s1 @ ebp-0x18
          ; var char *s2 @ esp+0x4
          0x08048414      push ebp

La función tiene dos variables, la primera bien definida detrás del Base Pointer por 0x18 y la otra sujeta al Stack Pointer por un más 0x4.

0x08048414      push ebp
0x08048415      mov ebp, esp
0x08048417      sub esp, 0x28
0x0804841a      and esp, 0xfffffff0

Las siguientes instrucciones corresponden al Stack Frame de main, en donde se reserva 0x28 entre el EBP y el ESP.

0x0804841d      mov eax, 0
0x08048422      add eax, 0xf
0x08048425      add eax, 0xf
0x08048428      shr eax, 4
0x0804842b      shl eax, 4
0x0804842e      sub esp, eax
0x08048430      mov dword [esp], str.IOLI_Crackme_Level_0x00
0x08048437      call sym.imp.printf

Las siguientes instrucciones descienden a ESP para poder cargar en el stack, la dirección de la sección rodata en donde se encuentra uno de los strings que observábamos hace varias imágenes. Posteriormente es llamada a la función printf de libc, la cual despliega el parámetro que recibe (via ESP). Este nos da un mensaje de bienvenida al nivel 00.

Otro tema interesante, es que EAX se queda inicializado con el valor de 0x10.

0x0804843c      mov dword [esp], str.Password:
0x08048443      call sym.imp.printf

El siguiente mensaje es como el anterior, en donde se mueve la ubicación del string “Password” al ESP y se llama a printf para que imprima dicho argumento.

0x08048448      lea eax, [s1]
0x0804844b      mov dword [s2], eax
0x0804844f      mov dword [esp], 0x804858c
0x08048456      call sym.imp.scanf

Continuamos con la primera variable que teníamos definida, s1. Aquí lo que hacemos es cargar la ubicación de s1 en EAX para posteriormente cargar el apuntador a s2 y subir al ESP el valor de 0x804858c, correspondiente al espacio de s1 donde se almacenara la información de scanf, función que es llamada en la ultima linea. Muy importante mencionar que en la ubicación tenemos la instrucción “and eax, 0x35320073”, lo cual mantiene el almacenamiento de la variable hasta su ejecución y reserva de manera peligrosa mucho espacio para nuestro string.

0x0804845b      lea eax, [s1]
0x0804845e      mov dword [s2], str.250382
0x08048466      mov dword [esp], eax
0x08048469      call sym.imp.strcmp

Ahora lo que vemos a continuación, es una comparación entre s1 y s2, teniendo s1 el valor que hayamos definido en la variable y siendo s2 el valor string de 250382 (rodata).

           0x0804846e      test eax, eax
       ,=< 0x08048470      je 0x8048480
       |   0x08048472      mov dword [esp], str.Invalid_Password
       |   0x08048479      call sym.imp.printf
      ,==< 0x0804847e      jmp 0x804848c
      |`-> 0x08048480      mov dword [esp], str.Password_OK_:
      |    0x08048487      call sym.imp.printf
      `--> 0x0804848c      mov eax, 0
           0x08048491      leave

En el ultimo bloque que analizaremos, tenemos que en el acomulador EAX se registra el resultado de la comparación, la cual si es 0, levanta el flag de ZF, caso contrario el flag ZF se mantiene en 0. Al llegar a “JE 0x8048480”, si ZF es 0, brincamos a 0x08048480, en donde imprimiremos el mensaje “Password OK :)”, caso contrario que ZF sea diferente a 0, imprimiremos el mensaje “Invalid Password”

Aunque hasta este punto, ya debe ser evidente la solución a este crackme, no olvidemos revisar la grafica de la función main.

Grafico

Una de las pestañas que nos ilustra mejor como las funciones interactúan y donde tienen saltos condicionales, es la sección de Graph, la cual luce de la siguiente manera:

graph

Con un poco mas de zoom (con el scroll del mouse) en el bloque principal antes del “if”:

if

Como podemos observar tenemos un recuadro donde se encuentra las mismas instrucciones que estábamos revisando, solo que con los comentarios adicionales que nos devuelve radare2. Adicionalmente podemos ver una linea verde y otra linea roja que descienden del JE, estas corresponden a los saltos condicionales; verde para verdadero o en nuestro crackme, ZF==0 y rojo para ZF!=0.

En caso verdadero como ya sabemos, imprime el mensaje ‘bueno’:

true

En caso falso como ya sabemos, imprime el mensaje ‘malo’:

false

Validación

Hemos llegado a la parte final, donde validamos la conclusión a la que hemos realizado después del análisis estático, en la cual la contraseña que debemos ingresar en el crackme es 250382.

Validemos:

validacion

Excelente! Hemos resuelto el primer crackme de IOLI.


Bueno eso ha sido todo de momento, espero que les haya gustado. Saludos,