Kyodai v10.21 Cómo hacer un Keygenerator por Black Fenix


En este tutorial vamos a tratar un juego de las típicas fichas ¿chinas? tipo Mahjongg, tiene unos molestos nags iniciales y algún otro que aparece de vez en cuando mientras estamos jugando. Nuestro objetivo será hacer un generador de números de série (keygenerator).

SoftIce para Windows
IDA Pro
ProcDump
Compilador de C (para el keygenerator)

El fichero ejecutable con el que yo he trabajado es el kyo1021.exe (versión OpenGL), ya que el otro kyodai.exe (versión DirectX) no funciona en mi máquina. Me imagino que dará igual ya que ambos están comprimidos con el ASPack, puedes comprobar esto si abres el ProcDump, click en PE Editor, seleccionas el ejecutable y luego haces click en el botón sections. Verás alguna sección con el nombre .aspack. Bueno ahora que ya sabemos que el archivo esta comprimido, lo primero será descomprimirlo ya que su desensamblado en este estado no nos permitiría ver el código original (pero si parte del código de descompresión del ASPack). Para descomprimirlo usaremos el mismo ProcDump. Primero ejecutamos el KyoDai y luego abrimos el ProcDump, en la lista de tareas (Task) buscamos el Kyodai (ver foto 1).


Foto 1

Nota: Si no aparece prueba a hacer click con el botón derecho sobre las tareas y luego click en Refresh (refrescar lista).

Notas sobre ASPack: Desconozco la versión utilizada para comprimir el kyodai, pero parece ser que tiene más dos capas de encriptación/compresión para proteger la rutina que descomprime el ejecutable. Es decir, primero desencripta/descomprime la rutina que descomprimirá el ejecutable original y luego pasa el control a esta ,mediante un JMP EAX, esto es lo que se llama varias capas de encriptación/compresión. Despues de la última descompresión se salta al punto de entrada original del programa de una manera poco común, primero empuja la dirección de salto a la pila y luego ejecuta un RET. Hay que destacar que la dirección empujada se computa al terminar la descompresión y se modifica el código del PUSH en tiempo de ejecución (código automodificable o self modifying code). Menciono esto a título informativo ya que no es necesario averiguar todo esto para completar el ejercicio que se pretende.

Ahora hacemos click sobre la tarea del kyodai con el botón derecho y luego click en Dump Full. Le damos un nombre al ejecutable (p.ej. kyoaspack.exe) y aceptamos. Ya tenemos el ejecutable descomprimido, este no es funcional debido a que el punto de entrada sigue estanto en la rutina de descompresión del ASPack cuando debería apuntar al código original del ejecutable, ademas de otros detalles como la no validez de la tabla de importación. Aunque el ejecutable no sea funcional no tiene importancia ya que solo necesitamos examinar el código y no vamos a realizar ninguna modificación sobre este, por lo que tal y como lo tenemos nos servirá. Vamos a buscar con SoftIce la dirección de la rutina que se encarga de calcular el número de serie. Ejecutamos el programa, yo he utilizado la versión OpenGL ya que por motivos que desconozco la versión DirectX no me funciona. Ahora vamos al menu Ayuda y hacemos click en registar, nos aparecerá una ventana (ver foto 2):


Foto 2

Teclamos un nombre p. ej. Black Fenix
Y un serial p. ej. 445566774455

Hacemos click en Aceptar y nos dirá que lo siente mucho pero que los datos no son correctos, ya lo sabiamos jejeje.
Ahora podriamos intentar buscar una referencia al mensaje de error pero como el archivo está comprimido vamos a usar otro metodo. Utilizaremos un breakpoint sobre la función del Kernel HMEMCPY, esta función es utilizada para copiar de memoria a memoria y suele utilizarse para copiar los datos introducidos en las casillas de edición de Windows a un buffer particular de la aplicación.

Activamos el SoftIce (ctrl+d) Pondremos un BPX con:

BPX HMEMCPY

Pulsaremos G para continuar y hacemos click en el botón Aceptar. SoftIce se activará debido al breakpoint, ahora estamos dentro de la función HMEMCPY, por lo que vamos a salir de aquí pulsando F12, veremos que estamos en la DLL USER (mira la ventana de código del SoftIce), por lo que seguiremos pulsando F12 hasta que veamos que dice algo de kyo1021.exe o kyodai.exe (según la versión que uses).
Cuando estes en el código del kyodai veras que el valor del registro EAX contiene la longitud del nombre que introdujiste (en mi caso 0Bh = 11 caracteres). Bien esto indica que por aquí cerca debe haber la dirección donde se copió el nombre. Pulsaremos F12 hasta que aparezca el código siguiente:

0046483D call sub_41EA00
00464842 mov edx, [ebp-4] // copia a EDX el puntero al nombre introducido
00464845 mov eax, ds:49BA74h
0046484A mov eax, [eax]
0046484C add eax, 52B4Ch

En mi caso la dirección es D2ECC8, podemos comprobar que es lo que buscamos si ejecutamos la línea 464842 con F8 y luego tecleamos:

d EDX

Ahora vamos ha hacer un seguimiento de las operaciones que se realizen con el nombre, para ello usaremos un breakpoint de memória (BPM), así cuando se intente acceder a la posición donde se haya el primer caracter, el SoftIce se activará y veremos que calculos se hacen. Tecleamos:

BPM EDX

y desactivamos el HMEMCPY con

BD 0

Ahora pulsamos G y SoftIce se activará de nuevo, estaremos al principio de la rutina que se encarga de calcular el serial válido. Ahora apuntaremos esta dirección (en mi caso 474B3C ) para poder examinar el código desensamblado desde el IDA Pro. Ponemos un BPX sobre esta dirección por si deseamos examinar esta rutina posteriormente.

BPX 474B3C

Y salimos del SoftIce con G. Ahora abrimos el IDA y vamos al menu Jump/Jump to Address, introducimos la dirección que obtuvimos en SoftIce (en mi caso 474B3C), veremos que el IDA la interpreta como datos, pero no problemo ya que si nos situamos sobre la dirección y pulsamos C (code) IDA desensamblará a partir de esa dirección y veremos lo mismo que veiamos antes en el SoftIce.

Ahora con ayuda del IDA y del SoftIce vamos a analizar el funcionamiento de la rutina.

:00474B3C mov edx, [ebp-8] // carga puntero al nombre
:00474B3F movzx eax, byte ptr [edx+eax-1] // lee último carácter de la cadena
:00474B44 mov edx, [ebp-8] // carga puntero al nombre
:00474B47 movzx edx, byte ptr [edx] // lee primer carácter de la cadena
:00474B4A add eax, edx // suma los dos caracteres
:00474B4C mov ecx, 0Ah // prepara para dividir por 0Ah
:00474B51 cdq // extiende a qword EAX (borra EDX)
:00474B52 idiv ecx // divide
:00474B54 add edx, 30h // suma 30h al resto de la división, hasta aquí los calculos equivalen a: (1er caracter+ultimo caracter) MODULO 10 + 0x30
:00474B57 mov eax, edi // carga puntero EAX para copiar el digito
:00474B59 call sub_403B8C // copia el dígito al buffer apuntado en EAX
:00474B5E mov eax, [ebp-8] // carga puntero al nombre
:00474B61 call sub_403C64 // llama a rutina que comprueba si el nombre es NULL, retornando 0 en caso afirmativo o la longitud de la cadena en caso contrario.
:00474B66 mov esi, eax // copia longitud
:00474B68 sub esi, 2 // y le resta 2
:00474B6B jl short loc_474BA6 // si es inferior salta,longitud no valida
:00474B6D inc esi // le suma uno a la longitud para compensar el número de caracteres procesados
:00474B6E mov ebx, 2 // copia 2 a EBX
:00474B73 loc_474B73: // CODE XREF: CODE:00474BA4
:00474B73 mov eax, [ebp-8] // carga puntero a nombre
:00474B76 movzx eax, byte ptr [eax+ebx-2] // lee siguiente caracter de la cadena
:00474B7B mov edx, [ebp-8] // carga puntero a nombre
:00474B7E movzx edx, byte ptr [edx+ebx-1] // lee siguiente caracter de la cadena
:00474B83 add eax, edx // suma los caracteres
:00474B85 mov ecx, 0Ah // prepara para dividir entre 0Ah
:00474B8A cdq // extiende a qword EAX (borra EDX)
:00474B8B idiv ecx // divide
:00474B8D add edx, 30h // suma 30h al resto, hasta aquí los calculos equivalen a: (caracter actual +caracter siguiente) MODULO 10 + 0x30
:00474B90 lea eax, [ebp-0Ch] // carga puntero al buffer del serial válido
:00474B93 call loc_403B8C // llama rutina que copia el dígito calculado al buffer
:00474B98 mov edx, [ebp-0Ch] // copia puntero al buffer del serial válido
:00474B9B mov eax, edi // y copia de nuevo a EAX
:00474B9D call loc_403C6C // llama función que copia el digito calculado al buffer final
:00474BA2 inc ebx // pasa al siguiente caracter del nombre
:00474BA3 dec esi // resta 1 al contador de caracteres procesados
:00474BA4 jnz short near ptr unk_474B73 // continua el cálculo del serial válido
:00474BA6 loc_474BA6: // CODE XREF: CODE:00474B6B
:00474BA6 mov eax, [ebp-8] // carga puntero al nombre
:00474BA9 call loc_403C64 // obtine longitud
:00474BAE cmp eax, 8 // es >= 8
:00474BB1 jge short loc_474BC8 // si, salta
:00474BB3 mov ecx, edi // no, no sirve como nombre de registro
:00474BB5 mov eax, [ebp-4]
:00474BB8 mov eax, [eax+5DCh]
:00474BBE mov edx, 474BFCh
:00474BC3 call loc_4481A4 //
:00474BC8 loc_474BC8: // CODE XREF: CODE:00474BB1
:00474BC8 xor eax, eax // borra EAX
:00474BCA pop edx // restaura registros
:00474BCB pop ecx
:00474BCC pop ecx
:00474BCD mov fs:[eax], edx
:00474BD0 push 474BEAh
:00474BD5 lea eax, [ebp-0Ch]
:00474BD8 mov edx, 2
:00474BDD call sub_403A0C
:00474BE2 retn retn

A) El funcionamiento es este algoritmo es bastante sencillo, primeramente se leen los caracteres primero y último del nombre, operación que se realiza en las lineas de la 474B3C a 474B47, notese que se extiende el signo del caracter a 0 (instrucciones MOVZX sin signo), esto lo hace para que la suma que realiza posteriormente en la línea 474B4A no de un valor negativo (es importante tener esto en cuenta para cuando hagamos el keygen). Con el resultado de dicha suma realiza una operación de módulo 10 (la operación equivale a dividir la suma entre 10 y quedarse con el resto que está en EDX). Luego suma 30h al resultado del cálculo, este resultado equivale al código ASCII del primer dígito del número de série válido (en mi caso 36h = 6), luego la rutina llama a una función en la línea 474B59 que copiará este dígito a un buffer de cadena que se pasa en EAX en la línea 474B57.

B) Luego se obtiene la longitud de la cadena mediante la función que se llama en la línea 474B61, se hace una copia de su longitud a ESI que será usado posteriormente como contador y se le restan 2 caracteres, si el resultado es negativo, salta ya que la longitud de la cadena no es lo suficientemente larga. Notese que este cálculo podía haberse realizado antes que todos y así se optimizaría un poco. Si la longitud no es negativa continua la ejecución de la rutina (línea 474B73), se suma 1 al contador.

C) Se obtiene de nuevo el puntero al nombre introducido y se realiza un cálculo similar al realizado en el paso A), se coge un caracter y el siguiente, se suman (sin signo) se hace el modulo 10 de la suma y al resultado se le suma 30h, dando el siguiente dígito del serial válido. Luego se copia el dígito a un buffer (línea 474B93) y luego al buffer definitivo (474B9D) junto con los demas digitos y se continua hasta que el contador de caracteres llega a 0. Notese que es innecesario el uso de dos buffers, de nuevo o nos quieren despistar o la rutina está muy mal programada (será así todo el programa?).

D) Una vez terminado el cálculo del serial válido se cálcula la longitud del nombre introducido y se comprueba que sea mayor o igual a 8 carácteres, en caso contrario el nombre no será un nombre de registro válido. Notese de nuevo que esto podría realizarse antes de calcular el serial y así ahorrariamos operaciones innecesarias, puede que aquí el programador quisiera despistarnos. Pero nosotros somos más inteligentes y una maniobra tan simple como esta no nos distraerá lo más mínimo.

Una vez terminado el cálculo y estando en la línea 474BA6 si desensamblamos el contenido apuntado por la dirección apuntada (puntero a puntero) por EDI on: D *EDI veremos el serial válido.Podemos hacer un BPM sobre esta dirección para parar la ejecución cuando se compare con el que nosotros hemos introducido, para ello desactivaremos el resto de breakpoints con BD * y luego teclearemos:

BPM *EDI

Y pulsaremos G, ahora aterrizaremos en el código donde se comparan los dos números de série, que es algo así:

00403D9D mov ecx, [esi] // lee caracteres del serial válido
00403D9F mov ebx, [edi] // lee caracteres del serial introducido
00403DA1 cmp ecx, ebx 00403DA3 // compara los caracteres
00403DA3 jnz short loc_403DFD // salta si no són iguales, continua comparando en caso contrario
00403DA5 dec edx // decrementa condator
00403DA6 jz short loc_403DBD // si no hay más caracteres a comparar salta
00403DA8 mov ecx, [esi+4] // lee los siguientes 4 carácteres
00403DAB mov ebx, [edi+4] // lee los siguientes 4 carácteres
00403DAE cmp ecx, ebx // los compara
00403DB0 jnz short loc_403DFD // si no son iguales salta
00403DB2 add esi, 8 // incremeta punteros de los serial en 8 caracteres
00403DB5 add edi, 8
00403DB8 dec edx

// decrementa contador (se comparan de 4 en cuatro por lo que este contador a sido dividido previamente entre 4

00403DB9 jnz short loc_403D9D // salta para continuar si aún hay más
00403DBB jmp short loc_403DC3 // saltamos si ya no hay más carácteres a comparar


Podriamos analizar el código siguiente para ver donde podriamos parchearlo, pero como nuestro propósito es hacer un keygenerator no merece la pena. Vista la rutina de cálculo y el análisis de su código pasémos ahora a realizar el keygenerator. Yo he utilizado el compilador lccwin32 de C para realizarlo, pero creo que se puede compilar con cualquier otro sin ningún problema.


#include <windows.h>
#include <stdio.h>
#include <string.h>


int main()
{
char name[80];
long sum,length;

printf("\nKyodai v10.21 / KeyGen by Black Fenix");
printf("\n-------------------------------------\n");
printf("\nEnter registration name:");
gets(name);

// convierte la cadena OEM a char, esto es necesario para los carácteres especiales (acentos etc.)
OemToChar(name,name);

// obtiene longitud , así evitamos tener que llamar a strlen cada vez
length=strlen(name);

// Comprueba la longitud mínima
if (length<8)
{
printf("\nThe name must be at least 8 chars long.");
return 255;
}

// Realiza el computo del primer carácter
sum=(((unsigned char)name[length-1]+(unsigned char)name[0]) % 0xA)+0x30;

printf("\nYour serial number is: %c",sum);

for (int i=0;i<length-1;i++)
{

// Realiza el computo del resto de carácteres
sum=(((unsigned char)name[i+1]+(unsigned char)name[i]) % 0xA)+0x30;
printf("%c",sum);
}

// Espera pulsación de tecla
getchar();
return 0;
}

 


You are inside Reversed Minds pages.

por Mr. Silver / WKT!
La información aquí vertida es exclusivamente para uso educacional, no puedo hacerme responsable del uso que se haga de esta, por lo que atiendo a la honradez de cada uno :), recuerda que debes comprar el software que utilices :)