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! |