Ingeniería inversa de un “crackme” (parte 1)

Gabriel Martí
9 min readOct 30, 2022

--

Reversing 101. Análisis estático.

Photo by Alexander Sinn on Unsplash

El objetivo de este post es explicar cómo resolver un crackme, paso a paso, para una persona que se inicia en ingeniería inversa.

Se prevee que este post sea la primera publicación de tres, donde intentaré explicar las tres posibles maneras de resolver estre “crackme” usando diferentes herramientas y puntos de vista.

¿Qué es un crackme?

El nombre de la palabra crackme procede del vocablo crack, que significa grieta, pero también romper. Así que crackme se podría traducir por “rómpeme”. En este caso se refiere a un pequeño programa ejecutable donde el objetivo es obtener una flag, una contraseña o descubrir un acceso.

Para ello, el reverser, lo que hará es analizar el código compilado usando herramientas de ingeniería inversa para descubrir que hace el programa y hacerse así con dicha contraseña o flag.

El ejecutable del crackme

El fichero ejecutable (nombrado binario muchas veces), lo recibí de un antiguo compañero hace unos días para que le explicara como resolverlo.

Puedes descargar el programa ejecutable que se muestra en este post desde el siguiente enlace:

https://github.com/gabimarti/crackmes-solutions/raw/master/diverse/xf-crackme/crackme

En ese mismo repositorio (crackmes-solutions) puedes encontrar otros crackmes que resolví hace tiempo.

Entorno de trabajo

Para llevar a cabo las actividades de ingeniería inversa es recomendable hacerlo siempre sobre una máquina virtual y un entorno controlado. Que dicha máquina tenga sistema operativo Windows o Linux dependerá de muchos factores que, en este caso, no tienen importancia. Se podría utilizar cualquier sistema indistintamente siempre y cuando dispongamos de las herramientas adecuadas. En este caso yo voy a usar una distribución de Kali Linux, pero se podría usar perfectamente cualquier otra distribución o incluso una VM con Windows.

El proceso que voy a llevar a cabo, es un proceso de Análisis Estático. Es decir, obtener la información que necesitamos sin tener que ejecutar el código del programa. Lo haré únicamente al final para comprobar que lo obtenido es lo correcto.

Pasos preliminares

Aunque los crackme’s no son archivos maliciosos, es conveniente tomar siempre precauciones frente un archivo ejecutable del que no se conoce su procedencia.

Así que, además de llevar a cabo todas las operaciones dentro de una máquina virtual preparada a tal efecto, es conveniente analizar el ejecutable con un antivirus para verificar que no trae cargas maliciosas.

La manera más rápida es subir el archivo a VirusTotal.

Analisis del Crackme en VirusTotal

Como se puede observar por la imagen anterior, no contiene, a priori, contenido malicioso.

Identificando el binario

Cuando se trata de analizar un binario de malware siempre se llevan a cabo otras operaciones para tener más datos con los que identificar la amenaza y compartir en comunidades. Como ya he indicado, en este caso no estamos analizando ningún malware y no es imprescindible llevar a cabo estas acciones. Pero si nuestro objetivo es trabajar habitualmente haciendo tareas de ingeniería inversa, no está de más acostumbrarse a llevarlas a cabo.

Primero, identificar el tipo de archivo ejecutable usando el comando file, que nos indica que es un ejecutable ELF de 64 bits para Linux. Ese dato ya lo conocía previamente y simplemente es para corroborarlo.

Obtención de la información del tipo de fichero

Seguidamente calcular los hashes, con los comandos o herramientas que dispongamos en el sistema. Por ejemplo: md5sum y sha256sum.

En este caso voy a usar otra herramienta que nos provee de más información. Se trata de Mandiant CAPA.

Esta herramienta detecta capacidades en archivos ejecutables. Lo analiza y dice lo que cree que el programa puede hacer. Por ejemplo, nos podría sugerir que el archivo es una puerta trasera, que puede instalar servicios o que lleva a cabo comunicaciones HTTP.

Tras ejecutarla, se puede comprobar que no hay capacidades sospechosas. Si que indica algo que nos puede dar una pista de lo que hace, pero no voy a mencionarlo. No quiero hacer spoiler de lo que viene más adelante.

Comprobación de las capacidades del programa ejecutable

Llegados a este punto, podemos estar tranquilos. El crackme es un crackme y no hay nada malicioso. 😬

Ejecución del programa

Aunque, como he dicho, voy a hacer un análisis estático, lo primero que haremos es ejecutar el programa para ver que nos muestra y/o nos pide.

Tal y como se observa en la imagen, hay que pasar una cadena de texto como argumento, y evidentemente, se trata de encontrar el flag que nos lleva a la victoria.

Ejecución del programa para comprobar que pide o que hay que hacer

Vamos pues a resolver esto.

La Herramienta

Voy a usar Ghidra en este caso. Ghidra es una herramienta Open Source desarrollada por la NSA y que nos permite desensamblar el código del ejecutable y ver las instrucciones en lenguaje ensamblador.

Empezamos creando un proyecto e importando a este el fichero del programa ejecutable.

Creación de proyecto en Ghidra e importación del fichero ejecutable

Tras importarlo nos va a mostrar todo un conjunto de información relativa al ejecutable de la cual ya conocemos en parte, como el formato ELF del archivo binario y los hashes.

Detalles del binario una vez importado al proyecto

Tras importar dicho archivo, la primera vez nos va a preguntar si queremos que se analice el ejecutable, a lo que responderemos afirmativamente.

La función principal

Tras el análisis del código, el primer paso es encontrar la función principal, es decir, la primera que se ejecuta una vez se ha cargado el ejecutable en memoria.

Cabe decir, que el propio compilador mete ciertas funciones para “acondicionar” el ejecutable al entorno, como establecer entorno gráfico o consola, cargar librerías, etc,. Y todas estas se ejecutan antes de llegar a la función principal, y no son las que nos interesan.

Normalmente, la función principal se llama main, y Ghidra nos suele dar este nombre en la ventana del Arbol de Símbolos (Symbol Tree).

Inicio de la función main

Si bajamos y observamos el código en ensamblador veremos diferentes partes de este donde se cargan diferentes cadenas de texto para mostrarse por pantalla. Entre ellas, está el saludo que nos muestra al inicio el programa y otras cadenas de texto que mostrará en función de lo que le pongamos como argumento.

Parte del código de la función main

Vayamos a ver entonces, que cadenas de texto tiene este ejecutable. En algunos casos, si se trata de un crackme sencillo, se podría dar el caso de encontrar la flag definida en una cadena de texto.

Así que accedemos al menu Windows y elegimos la opción Defined Strings.

Menu de Ghidra donde se muestra como acceder a la ventana de strings definidos

Lo que nos abrirá una ventana donde se muestran todos los strings detectados dentro del ejecutable. No parece haber rastro de ninguna cadena que pueda ser el flag.

Listado parcial de los strings definidos dentro del ejecutable

Un bucle

Volvemos al punto anterior, donde estábamos con la función main y seguimos recorriendo el código, hacia abajo, donde llegamos a la etiqueta LAB_0010076d. En este bloque de código se aprecia un bucle, evidenciado por una inicialización con el valor 0x00 en la dirección 0010076d, una comparación con el valor final 0x15 en la dirección 00100774, y un incremento del contador en 1 en la dirección 00100799.

Parte del código donde se aprecia un bucle con operaciones

Aquí se nos devela ya que este bucle, que se ejecuta 22 veces, está recorriendo una zona de memoria. Y esta zona de memoria está indicada por la etiqueta data, que indica una dirección la cual es cargada sobre el registro RAX.

La flag encontrada

Al finalizar el bucle se hace un salto a otra zona de código donde se cargan sobre los registros RDX y RAX unos valores respectivamente. Uno de ellos el pasado como argumento al programa y el otro el resultado de las operaciones del bucle.

Después estos valores son comparados usando la función de comparación de cadenas strcmp y en caso de que sea iguales, se muestra el mensaje de que la flag ha sido encontrada.

Parte del programa que informa de que se ha encontrado la flag tras comparar dos cadenas de caracteres

Pero, ¿dónde está la flag? 🤔

Descifrado de datos

Volvamos a la zona de memoria data. Se puede observar que sobre esa dirección de memoria se lleva a cabo una operación XOR. ¿Para qué se usa XOR? Pues para cifrar y descifrar datos. Si no sabes cómo funciona y quieres entender mejor el cifrado XOR puedes leer este otro artículo mío: “El cifrado XOR”.

Parte del código donde se resalta el acceso a datos en memoria y operaciones XOR sobre estos

Así que, el siguiente paso sería ver que tenemos en esa zona de memoria apuntada por data, ¿no?

Los datos

Buscamos dentro del arbol de símbolos la etiqueta data y al clicar sobre esta nos aparece en la ventana de al lado el contenido de esta sección de memoria. Esto es una zona de memoria definida dentro del ejecutable pues, recordemos, estamos haciendo un análisis estático y no se está ejecutando el programa.

Zona de datos del programa donde tenemos los bytes tratados por el programa con operaciones XOR

Si desplegamos todo el contenido veremos que empiezan a cuadrar los datos, ya que es un conjunto de 22 bytes, como un array, marcados desde [0] hasta [21]. Y esto coincide con el número de vueltas que lleva a cabo el bucle mencionado anteriormente.

Conjunto de bytes de la zona de datos que presuntamente contienen la flag

Todos los indicios apuntan a que estos datos de esta zona de memoria es la flag que estamos buscando. Pero estan cifrados ya que hemos visto que se utilizaba una operación XOR sobre estos.

La contraseña

Como todo cifrado, tiene que haber una contraseña. Pero ¿dónde se encuentra la contraseña de este cifrado?

Volvamos el bucle, y la contraseña la tenemos ahí, delante de nuestros ojos, en la misma operación XOR. Solo hay que tener en cuenta que los datos se estan cargando sobre un registro EAX que es de 32 bits, y estamos operando en cada vuelta de bucle con un byte, es decir 8 bits, por lo tanto, solo hay que coger los 8 bits de menos peso de este valor.

Contraseña del cifrado XOR

Así pues, la contraseña es el valor 0xab.

Ya sabemos dónde están los datos, qué operación se lleva a cabo y cuál es la contraseña del cifrado. Solo nos queda descifrar estos datos para tener la solución.

Descifrando los datos

¿Vamos a ejecutar algún programa? ¿Como lo hacemos?

Tenemos dos opciones:

  1. Método largo, y aprendes a programar algo.
  2. Método corto, y aprendes a usar una herramienta online.

Voy a explicar la dos, ya que estamos.

Método largo

Implementamos una función de descifrado XOR que recorre un array con una longitud indicada y aplica la operación XOR con la clave indicada para cada uno de los bytes. Es lo que hace exactamente el programa del crackme, pero en este caso vemos el código en lenguaje C.

Definimos la flag cifrada sobre un array indicando sus valores en hexadecimal, tal y como los vemos en el desensamblado con Ghidra y tras llamar a la función de descifrado mostramos el contenido de dicho array, carácter a carácter por pantalla.

Programa para descifrar la flag

Solo queda compilar el programa y ejecutarlo para que nos desvele la flag.

Resultado de compilación y ejecución del programa para descifrar la flag

Como seguro que el lector va a querer probar el programa, dejo a continuación un enlace a gist para que solo tenga que copiar y pegar el código. 😉

Código del programa para descifrar la flag colgado en GitHub

Método corto

Nos vamos a CyberChef que es una herramienta online con diferentes utilidades para operar con datos. En el campo input ponemos la cadena de bytes hexadecimal. En las recetas añadimos primero la opción “From Hex” para que convierta el texto hexadecimal en valores binarios y a continuación añadimos la receta “XOR” poniendo la clave de descifrado en el campo “Key”, y finalmente en la ventana de Output tendremos el resultado que tiene que coincidir exactamente con el obtenido con el método anterior.

Así que ya tenemos nuestra flag que es: BEH{D3buggers_ar3_fun}

Y todo sin haber usado un debugger, que será la segunda manera de resolver este crackme y que dejo para otra ocasión.

El paso final es comprobar que esta es realmente la clave que estábamos buscando.

Comprobación del funcionamiento de la flag

Efectivamente, es correcta.

¡¡¡ Happy Reversing !!!

A continuación, dejo el enlace de la segunda parte de este artículo.

https://gabimarti.medium.com/ingenier%C3%ADa-inversa-de-un-crackme-parte-2-1743e9dcfd6

--

--

Gabriel Martí

Ex-Docente CFGM, CFGS Ciberseguridad. Actualmente Consultor en Ciberseguridad. Intereses en robótica, ciberseguridad, reversing. Twitter @gmarti @310hkc41b