REVERSING: Estudio y programación de un KEYGEN

1. Introducción y objetivos


En este artículo vamos a explicar superficialmente cómo se generan ciertas licencias de software. Para ello vamos a realizar el estudio de un código en cuestión, con la finalidad de poder programar posteriormente un generador de claves que sea capaz de generar una clave correcta a partir de una cadena de texto cualquiera.

Para ello necesitaremos tener al menos unos conocimientos básicos en programación en ensamblador (ASM), y en ingeniería inversa. Como herramientas vamos a emplear el debugger OllyDbg y el compilador DevCpp para programar el generador de claves (Keygen) en C/C++.

De estar interesado en realizar una auditoría de ciberseguridad a sus aplicaciones, puede echar un vistazo a nuestros servicios de auditorías de aplicaciones y procesos de caja negra, blanca y gris.


2. Contexto y datos obtenidos


Tras desensamblar en estático un binario encargado de la activación de una licencia de software, se ha obtenido la siguiente pieza de código. Dicha pieza de código se encarga de comprobar que un determinado usuario tenga una licencia correcta para el uso de un software determinado.

Tras la ejecución de esta parte del código se muestra un texto correspondiente a la credencial de validación de la licencia del software. Esta credencial se genera a partir de una cadena de texto almacenada en el propio binario. Nuestra labor será la de entender cómo se genera esa credencial (password) a partir de cualquier cadena de texto (nombre de usuario), para posteriormente poder programar un generador de claves (keygen).


KEYGEN

3. Análisis estático: Bloques básicos


Lo primero que debemos hacer mediante el análisis estático del código obtenido, es dividirlo en bloques básicos para poder tener una mejor comprensión, como podemos ver en la siguiente imagen.



4. Análisis estático: Flujo de ejecución


Una vez obtenidos los bloques básicos y habiendo interpretado lo que realiza cada bloque, procedemos a realizar un diagrama de flujo con el que poder entender mejor cómo se realiza la ejecución de la pieza de código a nivel de proceso.



Como podemos observar existe una estructura de control en los siguientes bloques, las cuales conducirán el flujo principal de ejecución.



5. Análisis dinámico del código


A partir de este punto vamos a realizar un estudio más profundo del código obtenido. Para ello debemos introducir el código en nuestro debugger “OllyDbg”, con la finalidad de poder realizar su ejecución paso a paso y poder ver con más detalle qué es lo que hace el programa durante su ejecución.

Para poder introducir el código en el debugger, hemos desensamblado con OllyDbg un binario cualquiera de 32bits, para posteriormente editar y reemplazar en cualquier función de dicho binario nuestro código ensamblador, con alguna mínima modificación que veremos a continuación para que funcione correctamente.

Una vez hemos editado y reemplazado, procedemos a establecer el nuevo “entrypoint” en la primera instrucción de nuestro código, para que la ejecución del programa comience en dicha instrucción.



Una vez lanzada la ejecución del código podemos observar que se reserva un espacio destinado a introducir la cadena de caracteres introducida por argumento. Dicha cadena de caracteres la introduce en [EBX- 19A0], siendo EBX=2BBA9C, sacamos la conclusión de que la cadena va a ser introducida en 2BBA9C-19A0= 2BA0FC  

Es por ello que procedemos a copiar en esa zona de memoria la cadena de caracteres proporcionada en el código original: “3jd9cjfk98hnd”



Continuando con la ejecución del código, en la siguiente captura podemos ver cómo pasa la cadena de caracteres al registro EAX, y cómo EAX (la cadena) se guardar en la pila [EBP-14].



En la siguiente captura podemos observar la cadena de caracteres contenida en el registro EAX ya guardada en la pila en [EBP-14], es decir, en B1FF80 – 14 = B1FF6C



En la siguiente captura podemos ver cómo se llama a la función “strlen” para calcular la longitud (el número de caracteres) de la cadena y el resultado lo guarda en el registro EAX, con un valor de “D” en hexadecimal equivalente a “13” en decimal. Como podemos comprobar, efectivamente la cadena “3jd9cjfk98hnd” tiene 13 caracteres.



Continuando con la ejecución del código, en la siguiente captura podemos ver que se realiza un salto (jmp) a una comparación que salta a otra posición de memoria (<main+71>  es decir, 47C33C), si la comparación entre una variable que inicialmente se le da el valor de “0” es menor a “D” (“13” decimal). Acabamos de entrar en un bucle encargado de recorrer todos y cada uno de los caracteres de la cadena para operar con ellos.

En cada iteración del bucle toma carácter a carácter y los va multiplicando uno a uno por la longitud total de la cadena, es decir, por “D”. Los resultados de cada operación los va sumando y guardando en una zona de la pila que se mostrará en próximas capturas.

Podemos ver los comentarios en el código para una mayor claridad.



En la siguiente captura podemos ver cómo se toma en EAX el primer carácter de la cadena, el número “3”, equivalente a “33” en hexadecimal.



Posteriormente multiplica el valor hexadecimal del primer carácter (“33h”) por el número total de caracteres de la cadena (“Dh”), dando un resultado de 297h, el cual procede a guardarlo en la pila. También podemos observar cómo el registro  EDX es incrementado en “1” para que en la próxima iteración procese el próximo carácter de la cadena.




Continuando con la ejecución del código, en la siguiente captura podemos ver cómo se realiza la multiplicación del segundo carácter (“j”) equivalente a “6A” por “D” (longitud de la cadena), dando un resultado de 562h.



Misma operación con el tercer carácter. Aquí también podemos observar cómo los resultados de las multiplicaciones se van sumando y guardando en la pila. Hasta el momento con un valor de “7F9”.



Posteriormente podemos observar cómo guarda en el stack (la pila) la suma de los resultados de las distintas multiplicaciones.



Una vez haya recorrido toda la cadena de caracteres, es decir, haya realizado las 13 iteraciones, comprobará a través de la instrucción “CMP EAX, DWORD PTR SS:[EBP-18]” que EAX ya no será menor de “D” (“13” en decimal) y por lo tanto saldrá del bucle para continuar con la ejecución del programa. En ese momento podremos observar cuál ha sido el resultado total de las operaciones realizadas (“3AA7”).



Posteriormente se procede a mostrar el resultado final en pantalla mediante la llamada a la función “printf”. Una vez finalizada la llamada a la función “printf” contenida en la librería del sistema “msvcrt”, se procede a restablecer los parámetros guardados al principio del código en la pila, para finalmente devolver el control del flujo del programa mediante la instrucción “RETN”.



6. Programando el generador de claves (Keygen)


Habiendo llegado a este punto ya podemos proceder a programar nuestro “keygen”.

Como bien hemos comentado en párrafos anteriores, este tipo de código lo podemos encontrar en ciertos software donde se solicita un registro para poder usar el programa, en el que a partir de un “nombre” se genera una “contraseña” específica.

A continuación podemos observar el código programado con sus correspondientes comentarios.



Una vez compilado el código podemos observar la correcta ejecución del binario, pasándole por argumento la cadena de caracteres contenida en el código original. Podemos comprobar cómo los resultados obtenidos son los correctos, tal cómo se presentaban en la depuración con OllyDbg, dando como resultado final “3AA7h”, equivalente en base decimal a “15015”.



El código programado ha sido diseñado para que pueda aceptar cualquier cadena introducida por argumento (*argv[]) por el propio usuario, por lo que no necesita más modificaciones para su adaptabilidad.

Así pues, para el nombre de usuario “Congratulations!”, podemos observar que la credencial de validación es «26080», como podemos observar en el resultado que nos arroja el binario en la siguiente imagen.



7. Conclusiones


En este artículo hemos analizado el código básico de un generador de claves, y hemos programado nuestro propio keygen a través del estudio realizado mediante el análisis estático y dinámico del código original.

De esta manera hemos podido ver cómo se generan ciertas credenciales de validación, a partir de distintas cadenas de texto introducidas para un nombre de usuario.

Huelga decir la importancia que tiene en el software comercial el testar las distintas licencias que presentan, para prevenir que distintos grupos cibercriminales sean capaces de crackear sus licencias y puedan aprovecharse de las funcionalidades del software.

De estar interesado en realizar una auditoría de ciberseguridad a sus aplicaciones, puede echar un vistazo a nuestros servicios de auditorías de aplicaciones y procesos de caja negra, blanca y gris.

De estar interesado en adquirir conocimientos de hacking ético puede optar por matricularse en nuestro curso básico de hacking ético, o si prefiere adquirir conocimientos más avanzados en ciberseguridad ofensiva puede adquirir nuestro curso avanzado.

No puedes copiar el contenido