Deep Green Reversi v4.3.7 Cómo hacer un Keygenerator por Black Fenix


En este tutorial vamos a tratar una versión del juego Othello para Windows, el programa necesita ser registrado ya que si no aparece una ventana recordandonos lo malvados que somos :), pero, pregunto yo, Quién es capaz de pagar por un juego como el Othello?, Bueno en la vida del señor hay de todo, y más extraño aún es dedicarse a hacer un keygen para este juego, pero bueno, todo sea por complacer a los chicos de WkT!, veremos a ver si por lo menos tiene un buen esquema de registro.

SoftIce para Windows
IDA Pro
ProcDump
Compilador de C (para el keygenerator) lcc-Win32 es mi preferido.

Bueno, como ahora se a puesto de moda eso de comprimir/encriptar los ejecutables para que los posibles lamers no se dediquen a fisgonear, el Deep Green Reversi no podía ser menos. Vamos a ver con que narizes lo han comprimido, normalmente antes de desensamblar nada yo uso el Procdump para ver los nombres de las secciones del ejecutable, si veo alguna sospechosa o que yo sepa que puede pertenecer a un compresor/encriptador ya se a lo que me enfrento y así no pierdo el tiempo desensamblado algo sin sentido ;). Pues nada, abrimos el ProcDump, click en PE Editor, seleccionamos el ejecutable (Reversi.exe) y luego click en el botón Sections. Veremos que hay un montón de nombres, si nos desplazamos hacia abajo veremos lo siguiente:


Foto 1

Anda, que te parece, tenemos con nosotros al más popular de los compresores de Windows que se utilizan hoy en día, el ASPack.La verdad que este compresor es un coñazo, ultimamente me lo encuentro hasta en la sopa. Pero no hay que preocuparse, no necesitamos tocar el ejecutable ya que vamos hacer un keygenerator y no un parche. Vamos a descomprimirlo de la manera más sencilla que existe, el ejecutable no será funcional, pero nos da igual ya que solo usaremos el archivo descomprimido para analizar el código que necesitemos para la generación del keygen. Hacemos click en Cancel y de nuevo click en Cancel.

Ejecutamos el Deep Green Reversi, cuando ya esté en marcha vamos al ProcDump, y hacemos click con el botón derecho sobre la lista de tareas, aparecerá un submenu, click en Refresh list (esto es para que aparezca el Deep Green Reversi en la lista de tareas del ProcDump), ahora lo buscamos en la lista de tareas, hacemos click con el botón derecho sobre él y luego click en Dump Full. En unos instantes el ProcDump nos pedirá donde guardar el volcado y con que nombre, yo lo puse junto con el original pero con el nombre raspack.exe.

Vale ya tenemos el volcado, ahora lo dejaremos por un rato y recurriremos a el para analizar el código más detenidamente, ya que vamos a buscar con el SoftIce donde empieza la rutina de cálculo del número de serie. Ejecutamos el Deep Green Reversi y vamos a la pantalla de registro (menu Help/About/I would like to register now/Enter registration Information) que es algo así:

Introducimos los datos, yo he utilizado los siguientes:

Name: Black Fenix
E-mail: [email protected]
Registration number: 5555 - 66666

Por supuesto que son falsos tanto el mail como el número de registro ;). Ahora ponemos el cursor justo encima del botón Register, antes de hacer click activamos el SoftIce (Ctrl+D). Ahora vamos a utilizar el método del breakpoint sobre la función del kernel HMEMCPY, como ya muchos sabrán esta función se utliza muy a menudo para copiar memoria o cadenas a otras partes de memoria, y es probable que se utilize para copiar las cadenas que nosotros habiamos introducido. Tecleamos lo siguiente:

BC *

BPX HMEMCPY

Con la primera línea borramos los posibles breakpoints anteriores, con la segunda ponemos un BreakPoint sobre HMEMCPY. Pulsamos G y rapidamente hacemos click sobre Register. Ahora SoftIce se activará, estamos dentro de la función HMEMCPY del kernel, ahora tenemos que buscar la dirección donde se copia la cadena, para ello iremos pulsando F12 hasta que salgamos de las funciones de Windows y entremos en el código del Deep Green Reversi. Cuando veamos que estamos en el código del Deep Green Reversi (mira la ventana de código y veras que dice REVERSI.EXE!...) verás que el código que tenemos delante son POPs seguidos de un RET, esto quiere decir que estamos al final de alguna función y que por lo tanto aquí no encontraremos nada, deberemos de ir pulsando F12 hasta que no veamos ningún POP seguido de un RET, eso ocurrirá cuando veamos el siguiente código.

:0041009C call 4D62D4 // ESTAMOS AQUÍ
:004100A1 lea ecx, [ebp-4] // lee una dirección en ECX
:004100A4 push ecx //
:004100A5 mov edx, edi
:004100A7 lea eax, [ebp-8]
:004100AA call 514194
:004100AF inc dword ptr [esi+1Ch]
:004100B2 lea edx, [ebp-8]
:004100B5 pop eax
:004100B6 call 5143D8
:004100BB test al, al
:004100BD jnz 410122

 

Ahora vemos que EAX contiene el valor B, y B en decimal es 11, y 11 carácteres es la longitud del nombre que yo introduje (Black Fenix), si introdujiste otra cosa verás su longitud. Veamos estamos cerca, vamos a ver si pescamos algo util, vamos a comprobar que es lo que se esta guardando en ECX despues de la llamada, pulsa F8 para trazar una línea y ahora veras el contenido de ECX (en mi caso 198E660) mmmm... parece una dirección de memoria bastante lejana, ¿será la que buscamos? veamoslo, teclea

D ECX

8C 18 47 .. .. .. (en micaso)

Bueno no parecen datos útiles, a no ser que... ECX sea un puntero a un puntero a una cadena (en C -> **char), veamos, para comprobar a que apunta lo que apunta ECX teclea:

D *ECX

¿Que es lo que aparece en la ventana de datos? Pues claro, nuestro nombre, parece ser que querian despistarnos con el uso de punteros a punteros, pero nosotros somos más inteligentes y siempre comprobamos este tipo de situaciones ( ¿o no? ).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 *ECX

y desactivamos el HMEMCPY con

BD 0

Ahora pulsamos G y rápidamente el breakpoint de memória surtirá efecto. SoftIce estará activo de nuevo, ahora tenemos la seguridad de que se está haciendo alguna operación con el nombre. El código que SoftIce nos muestra es el siguiente:

:00505A85 mov ecx, [esi] // ESI apunta al nombre que introdujimos
:00505A87 mov ebx, [edi] // EDI apunta a una cadena que es '[Xorolc]'
:00505A89 cmp ecx, ebx // se comparán los cuatro primeros carácteres de cada nombre
:00505A8B jnz 505AE5 // si no son iguales salta (vaya parece ser que hay algún tipo de nombre "especial")
:00505A8D dec edx
:00505A8E jz 505AA5
:00505A90 mov ecx, [esi+4]
:00505A93 mov ebx, [edi+4]
:00505A96 cmp ecx, ebx
:00505A98 jnz 505AE5
:00505A9A add esi, 8
:00505A9D add edi, 8
:00505AA0 dec edx
:00505AA1 jnz 505A85
:00505AA3 jmp 505AAB

Vaya sorpresa, parece ser que el programa compara nuestro nombre (Black Fenix) con uno fijo ([Xorolc]), lo cual solo puede indicar que con ese nombre se consigue algo especial. Por el momento es probable que también se necesite un e-mail adecuado, y es probable que se use la misma rutina en la que estamos para compararlos, por lo que vamos a poner un BPX sobre la línea 505A85, tecleamos:

BPX 505A85

Pulsamos G y de nuevo se activará SoftIce en el breakpoint que acabamos de poner, si examinamos los contenidos de EDI y ESI veremos que apuntan al email que nosotros introducimos ([email protected]) y otro que parece el que buscamos '[email protected]'. Bueno parece que ya lo tenemos, pero si no quereis perder el tiempo mejor que no probeis estos nombres ya que para lo único que os servirá es para que el programa os muestros dos mensajes, uno dice : 'Do you feel guilty using this registration' (Te sientes culpables de usar esta manera de registro) si contestas que si, el programa te abrirá una conexión Internet para que te registres en línea, y si le dices que no te dirá 'This software will be disabled until you register', osea que el programa permanecerá desactivado hasta que te registres. Pues creo que toda esta mierda la ha puesto el programador para dar un poco por culo a los lamers que intentan pescar numeros de serie, pero quien sabe, la verdad es que ha mi me despitó un poco. Vamos a lo que ivamos, estamos en 505A85 y parece ser que este código es una rutina de comprobación de cadenas que se utiliza varias veces, por lo que vamos a salir de la rutina pulsando F12. Veremos el siguiente código:

:005143D8 push ebp  
:005143D9 mov ebp, esp  
:005143DB push ebx  
:005143DC mov eax, [eax]  
:005143DE mov edx, [edx]  
:005143E0 call sub_505A5C // venimos de aquí
:005143E5 setz al // ESTAMOS AQUI!!
:005143E8 and eax, 1  
:005143EB pop ebx  
:005143EC pop ebp  
:005143ED retn  

Pulsaremos de nuevo F12, ya que estamos es un sitio donde solamente se llama a la rutina de comprobación y se retorna un valor en EAX y luego se reajusta la pila. Ahora veremos el siguiente código:

:004100BB test al, al // comprueba resultado de la función de donde venimos
:004100BD jnz short loc_410122  
:004100BF xor ecx, ecx  
:004100C1 mov [ebp-0Ch], ecx  
:004100C4 lea edx, [ebp-0Ch]  
:004100C7 inc dword ptr [esi+1Ch]  
:004100CA mov eax, [ebx+2D8h]  
:004100D0 call sub_4D62D4  
:004100D5 lea ecx, [ebp-0Ch]  
:004100D8 push ecx  
:004100D9 lea edx, [edi+9]  
:004100DC lea eax, [ebp-10h]  
:004100DF call sub_514194  
:004100E4 inc dword ptr [esi+1Ch]  
:004100E7 lea edx, [ebp-10h]  
:004100EA pop eax  
:004100EB call sub_5143D8  
:004100F0 test al, al  
:004100F2 lea eax, [ebp-10h]  
:004100F5 setnz cl  
:004100F8 and ecx, 1  
:004100FB mov edx, 2  
:00410100 push ecx  
:00410101 dec dword ptr [esi+1Ch]  

La verdad es que apartir de la primera línea de este código 4100BB hasta la 4102DD se hacen las comprobaciones necesarias para ver si hemos caido en la broma que el programador del Deep Green Reversi nos ha jugado con el nombre especial "[Xorolc]", por lo que vamos a continuar trazando con F10 hasta llegar al código de la línea 4102DD, pero antes desactivaremos todos los breakpoints tecleando:

BC *

Pues nada, a trazar linea a línea con F10 hasta llegar a la linea 4102DD, mientras vas trazando, puedes ir comprobando las direcciones a las que apuntan los registros, sobre todo EAX y EDX, iras viendo el mail, el nombre etc. Cuando llegues a la línea 4102DD verás que se carga en EDX la dirección del nombre, ahora vamos a ver que se va hacer con ella:

:004102DD mov edx, [ebp-2Ch] // carga puntero al nombre
:004102E0 jmp short loc_4102E8 // salta incondicionalmente
:004102E2 lea edx, [edi+0B4h] // esta línea no se ejecuta
:004102E8 push edx // guarda puntero al nombre
:004102E9 mov eax, ds:dword_534944
:004102EE mov ecx, [eax]
:004102F0 mov eax, [ecx+578h]
:004102F6 push eax
:004102F7 call sub_40DDBC // llama a una rutina

Vemos que se pasa el nombre como parametro a una función, por lo que esto puede ser algo interesante, vamos a a continuar trazando pero esta vez entraremos dentro de la función que se llama en la línea 4102F7, para ello iremos pulsando F8 hasta que entremos dentro de la función y veamos lo siguiente:

:0040DDBC push ebp // ajusta la pila
:0040DDBD mov ebp, esp
:0040DDBF add esp, 0FFFFFB7Ch
:0040DDC5 push ebx
:0040DDC6 push esi
:0040DDC7 push edi
:0040DDC8 xor ebx, ebx // pone EBX a 0
:0040DDCA xor edi, edi // pone EDI a 0
:0040DDCC mov eax, [ebp+arg_4] // carga en eax puntero al nombre introducido
:0040DDCF mov esi, eax // copia a ESI
:0040DDD1 jmp short loc_40DDDA // salta incondicionalmente
     
:0040DDD3 loc_40DDD3: ; CODE XREF: sub_40DDBC+2A
:0040DDD3 movsx eax, byte ptr [esi] // lee caracter del nombre introducido
:0040DDD6 add ebx, eax // suma a EBX (la primera vez EBX=0)
:0040DDD8 inc edi // incrementa contador
:0040DDD9 inc esi // y puntero del nombre,
:0040DDDA loc_40DDDA: ; CODE XREF: sub_40DDD1
:0040DDDA mov edx, [ebp+arg_4] // carga puntero al nombre introducido
:0040DDDD push edx // lo pasa como parámetro a la siguiente llamada
:0040DDDE call sub_508114 // llama a una rutina que calcula la longitud del nombre (esto no sería necesario si se calculara fuera del bucle y se guardara en ECX)
:0040DDE3 pop ecx // restaura pila
:0040DDE4 cmp edi, eax // compara la longitud del nombre con un contador
:0040DDE6 jb short loc_40DDD3 // si es inferior salta hacia atras (click en el hipervinculo para verlo)
      // este bucle realiza una suma de los caracteres ASCII del nombre, el resultado lo va guardando en EBX tal y como se puede apreciar en la línea 40DDD6
:0040DDE8 xor edi, edi // Borra EDI (antes era el contador de carácteres)
:0040DDEA mov eax, [ebp+arg_8] // carga puntero al email
:0040DDED mov esi, eax // copia a ESI
:0040DDEF jmp short loc_40DDF8 // salta incondicionalmente
:0040DDF1 loc_40DDF1: ; CODE XREF: sub_40DDBC+48
:0040DDF1 movsx eax, byte ptr [esi] // lee caracter del email introducido
:0040DDF4 add ebx, eax // suma a la suma de caracteres
:0040DDF6 inc edi // incrementa contador
:0040DDF7 inc esi // y puntero al email
:0040DDF8 loc_40DDF8: ; CODE XREF: sub_40DDBC+33
:0040DDF8 mov edx, [ebp+arg_8] // carga puntero al email
:0040DDFB push edx // se pasa como parametro
:0040DDFC call sub_508114 // calcula longitud (esto no sería necesario si se calculara fuera del bucle y se guardara en ECX)
:0040DE01 pop ecx // restaura puntero al
:0040DE02 cmp edi, eax // compara contador con longitud
:0040DE04 jb short loc_40DDF1 // si es inferior a la longitud salta hacia atras
      // Este otro bucle continua la suma de caracteres pero esta vez con el email, se leen los valores de los caracteres y se van sumando uno a uno tal y como se ve en la línea 40DDF4, una vez terminada la suma se continua...
:0040DE06 push 0Ah // se empuja 0Ah (10) para pasarlo a una función
:0040DE08 lea ecx, [ebp+var_3F0] // lee dirección del buffer donde se copiaran los 4 digitos
:0040DE0E push ecx // y se pasa como parámetro
:0040DE0F push ebx // se pasa también la suma de los caracteres
:0040DE10 call sub_50CFEC // se llama a una rutina que calcula los 4 digitos
      // a primera vista pueden parecer los 4 primeros dígitos del número de serie, pero no lo son, aunque si se calculan de la misma manera que los verdaderos tal y como veremos más adelante, esto puede ser otra maniobra para intentar despistarnos, pero de nuevo no lo han conseguido ;), haz click sobre la llamada para ver el funcionamiento de la rutina
:0040DE15 add esp, 0Ch // reajusta la pila
:0040DE18 mov [ebp+var_404], 0D87h // prepara tabla de 5 dwords en la pila
:0040DE22 mov [ebp+var_400], 2234h
:0040DE2C mov [ebp+var_3FC], 2231h
:0040DE36 mov [ebp+var_3F8], 128Bh
:0040DE40 mov [ebp+var_3F4], 15ADh
:0040DE4A push Ah  
:0040DE4C lea eax, [ebp+var_3F0]
:0040DE52 push eax
:0040DE53 lea edx, [ebx+9C5h] // lee la suma de los caracteres ASCII sumandole a la vez 9C5h
:0040DE59 push edx // empuja suma para pasarla a la misma función que se llama en 40DE10
:0040DE5A call sub_50CFEC // llama a la rutina que calcula 4 digitos
      // estos si que corresponden a los 4 primeros dígitos
:0040DE5F add esp, 0Ch // reajusta la pila
:0040DE62 mov edx, [ebp+arg_C]

El código anterior es bastante fácil de comprender, primero se calcula la suma de todos los caracteres del nombre más la suma de los caracteres del email, luego se calcula un grupo de 4 digitos segun la suma resultante, este cálculo lo realiza la función en 50CFEC, que se llama dos veces, la primera vez se calculan 4 dígitos que no sirven para nada, es decir no forman parte del número de serie válido, la segunda vez esta función da como resultado el primer grupo de 4 dígitos válidos. Aquí esta la rutina en 50CFEC..

  arg_0 = dword ptr 8 // 1er Argumento -> 0Ah
  arg_4 = dword ptr 0Ch // 2º Argumento -> buffer para los digitos
  arg_8 = dword ptr 10h // 3er Argumento -> suma ASCII
:0050CFEC push ebp
:0050CFED mov ebp, esp
:0050CFEF mov eax, [ebp+arg_8] // carga 0Ah
:0050CFF2 mov edx, [ebp+arg_0] // y carga tambien la suma ASCII
:0050CFF5 cmp eax, 0Ah // compara EAX on 0Ah, notese que EAX siempre vale 0Ah ya que así se pasa en el código previo a la llamada, ver línea 40DE4A
:0050CFF8 push 61h // empuja 61H a la pila
:0050CFFA setz cl // pone CL a 1 si la EAX era igual a 0Ah (esto siempre ocurre)
:0050CFFD and ecx, 1 // borrar todo ECX excepto CL
:0050D000 cmp eax, 0Ah // compara de nuevo la EAX con 0Ah
:0050D003 push ecx // empuja ECX a pila
:0050D004 push eax // y la suma ASCII
:0050D005 mov ecx, [ebp+arg_4] // carga puntero donde se copiaran los 4 digitos calculados según la suma ASCII
:0050D008 push ecx // y de nuevo ECX
:0050D009 jnz short loc_50D00F // salta si EAX no era igual a 0Ah
:0050D00B mov eax, edx // copia suma de caracteres a EAX, notese que esto no ocurre debido a que siempre se pasa 0Ah ver línea 40DE4A
:0050D00D jmp short loc_50D011 // SALTA INCONDICIONALMENTE
:0050D00F loc_50D00F: // CODE XREF: sub_50D009
:0050D00F mov eax, edx // copia suma de caracteres a EAX, notese que el salto en 50D009
    // es una chorrada ya que siempre se acabará copiando la suma de caracteres a EAX, no se que tipo de compiladores usaran estos programadores de hoy en día, pero seguro que son de Micro$oft :), aunque puede que no sepan que existe una opción de optimización de código ;)
:0050D011 loc_50D011: // CODE XREF: sub_50D00D
:0050D011 push eax // pasa la suma como parámetro a función en 50CF5F
:0050D012 call sub_50CF5C // llama función que calculos los digitos y los copia en el buffer pasado
:0050D017 add esp, 14h // reajusta la pila
:0050D01A pop ebp
:0050D01B retn retn

Esta rutina lo único que realiza es comparar el primer argumento pasado (0Ah) y compararlo con 0Ah, en caso afirmativo (esto siempre ocurre), se empujan 61h a la pila seguido de 1 (el valor empujado en ECX), luego pasa estos datos junto a la suma y el buffer previamente pasado a la rutina en 50CF5C, que es la que realmente hace el cálculo de los digitos tal y como podemos comprobar en su código, podemos ir trazando con F8 hasta llegar aquí:

  var_24 = byte ptr -24h
  arg_0 = dword ptr 8
  arg_4 = dword ptr 0Ch
  arg_8 = dword ptr 10h
  arg_C = byte ptr 14h
  arg_10 = byte ptr 18h
:0050CF5C push ebp // ajusta pila
:0050CF5D mov ebp, esp
:0050CF5F add esp, 0FFFFFFDCh
:0050CF62 push ebx // guarda registros
:0050CF63 push esi
:0050CF64 push edi
:0050CF65 mov edi, [ebp+arg_8] // carga en EDI 0Ah
:0050CF68 mov esi, [ebp+arg_0] // carga en ESI la suma
:0050CF6B mov ebx, [ebp+arg_4] // carga en ebx puntero al buffer donde se copiaran los dígitos
:0050CF6E cmp edi, 2 // comprueba EDI (0Ah) con 2,
:0050CF71 jl short loc_50CFC0 // salta si es inferior, notese que esto nunca pasa ya que siempre vale 0Ah
:0050CF73 cmp edi, 24h // comprueba EDI (0Ah) con 24h
:0050CF76 jg short loc_50CFC0 // salta si es mayor, notese que esto nunca pasa ya que siempre vale 0Ah
:0050CF78 test esi, esi // comprueba que la suma pasada sea diferente a 0
:0050CF7A jge short loc_50CF88 // salta en caso afirmativo
:0050CF7C cmp [ebp+arg_C], 0 // si la suma era 0 comprueba el flag pasado
:0050CF80 jz short loc_50CF88 // si es 0 salta
:0050CF82 mov byte ptr [ebx], 2Dh // no, copia al buffer el caracter '-'
:0050CF85 inc ebx // incrementa puntero
:0050CF86 neg esi // niega la suma, quedando -1
:0050CF88 loc_50CF88: ; CODE XREF: sub_50CF5C+1E sub_50CF5C+24
:0050CF88 lea ecx, [ebp+var_24] // carga puntero al buffer
:0050CF8B loc_50CF8B: ; CODE XREF: sub_50CF5C+42
:0050CF8B mov eax, esi // copia suma a EAX para dividir
:0050CF8D xor edx, edx // borrar EDX, preparandolo para dividir
:0050CF8F div edi // divide entre EDI (siempre es 0Ah)
:0050CF91 mov [ecx], dl // copia el resto de la división al buffer, esto da un valor entre 0 y 9 con el cual se formará el grupo de dígitos
:0050CF93 inc ecx // incrementa puntero al buffer
:0050CF94 mov eax, esi // copia de nuevo la suma a EAX
:0050CF96 xor edx, edx // borra EDX, preparandolo para dividir
:0050CF98 div edi // divide entre EDI (siempre es 0Ah)
:0050CF9A mov esi, eax // copia la parte entera de la división a ESI, borrando con ello la suma anterior
:0050CF9C test eax, eax // comprueba si la parte entera es 0
:0050CF9E jnz short loc_50CF8B // si no es 0, salta atras y continua dividiendo para obtener resto de digitos
:0050CFA0 jmp short loc_50CFB9 // salta incondicionalmente
     
:0050CFA2 loc_50CFA2: ; CODE XREF: sub_50CF5C+62
:0050CFA2 dec ecx // decrementa puntero al buffer de dígitos
:0050CFA3 mov al, [ecx] // lee digito en AL
:0050CFA5 cmp al, 0Ah // lo compara con 0Ah
:0050CFA7 jge short loc_50CFB1 // si es mayor o igual salta
:0050CFA9 add eax, 30h // suma 30h al digito resultado en su código ASCII
:0050CFAC mov [ebx], al // lo copia al buffer de dígitos
:0050CFAE inc ebx // incrementa buffer de dígitos
:0050CFAF jmp short loc_50CFB9 // salta para comprobar si se han procesado todos los caracteres
     
:0050CFB1 loc_50CFB1: ; CODE XREF: sub_50CF5C+4B
:0050CFB1 add al, [ebp+arg_10] // lee el 61h pasado
:0050CFB4 add al, 0F6h // le suma F6h dando como resultado 57h que es la letra 'W'
:0050CFB6 mov [ebx], al // y copia la 'W' al buffer (esto siempre da 100h)
:0050CFB8 inc ebx // incrementa puntero al buffer
:0050CFB9 loc_50CFB9: ; CODE XREF: sub_50CF5C+44 sub_50CF5C+53
:0050CFB9 lea edx, [ebp+var_24] // copia puntero al buffer de dígitos en EDX
:0050CFBC cmp ecx, edx // comprueba si el buffer actual es igual al copiado
:0050CFBE jnz short loc_50CFA2 // si no es igual salta (esto es para ver si se han procesado todos los caracteres)
     
:0050CFC0 loc_50CFC0: ; CODE XREF: sub_50CF5C+15 sub_50CF5C+1A
:0050CFC0 mov byte ptr [ebx], 0 // Pone el último digito como NULL, esto es para indicar el final de la cadena
:0050CFC3 mov eax, [ebp+arg_4] // copia a EAX puntero al buffer donde estan los dígitos
:0050CFC6 pop edi // restaura registros
:0050CFC7 pop esi
:0050CFC8 pop ebx
:0050CFC9 mov esp, ebp
:0050CFCB pop ebp
:0050CFCC retn retn

Pues el código anterior es el corazón de la rutina de cáculo, su funcionamiento es bastante sencillo. Primero se comprueba que EDI, es mayor que 0Ah (10) y menor que 24h (36), si no es asi se salta al final de la rutina y se copia NULL al buffer de los dígitos (quedando una cadena vacía). Si EDI es 0Ah (cosa que siempre sucede). Luego verifica que la suma pasada es >= 0, si es inferior verifica el valor que se paso (flag calculado en la rutina anterior en ECX, que siempre es 1), si es no es 0 copia el caracter '-' como primer dígito del grupo (50CF82), incrementa el buffer y niega la suma quedando -1. Luego continua el cálculo de los dígitos a partir de 50CF88, El calculo es muy sencillo, lee la suma, la divide entre 0Ah (EDI) y el resto lo guarda en el buffer de dígitos (esto da un valor entre 0 y F), vuelve a leer la suma y divide de nuevo entre 0Ah, esta vez coge la parte entera y la guarda en la suma, comprueba si es diferente a 0 y en ese caso sigue calculando el resto de caracteres repitiendo este bucle de operaciones.
Una vez tiene los 4 dígitos en el buffer calcula su representación alfanumérica sumando 30h a cada caracter que sea inferior a Ah (10), los que sean superiores a este valor se sustituyen por la letra W (50CFB6), ya que no pertenecen al sistema decimal (base 10). Y para terminar añade el caracter nulo al final del buffer de dígitos para que sea una cadena imprimible (50CFC0) y retorna la dirección del primer carácter del buffer en EAX.Básicamente lo que hace la rútina de cálculo de los dígitos es pasar a la representación alfanumérica decimal, el valor de la suma hexadecimal pasada.

La primera vez que se realiza este cálculo no sirve para nada, ya que los dígitos resultantes no forman parte del número de série válido, quizas el programador estaba intentando despistarnos. La segunda vez que se hace el cálculo este si que es válido, pero se calculan los 4 primeros dígitos con la suma real + el valor 9C5h (ver línea 40DE53). Previamente se rellena una tabla en la pila con 5 DWORDs (ver lineas de 40DE18 a 40DE40 ). El valor que se obtiene de la tabla se pasa (40DE5A) como suma a la función que cálcula los dígitos, obteniendo así primer grupo válido de dígitos. Luego pasa a compararlos en el código siguiente a 40DE5A, tal y como podemos ver si continuamos trazando con F8:

:0040DE5A call sub_50CFEC // Aqui se calcularon los 4 primeros dígitos válidos
:0040DE5F add esp, 0Ch // reajusta la pila
:0040DE62 mov edx, [ebp+arg_C] // carga en EDX puntero al buffer con los 4 dígitos cálculados
:0040DE65 loc_40DE65: ; CODE XREF: sub_40DDBC+CB
:0040DE65 mov cl, [eax] // lee en CL dígito del primer grupo de 4 dígitos introducidos
:0040DE67 cmp cl, [edx] // compará con el cálculado
:0040DE69 jnz loc_40DFED // si no son iguales salta al final de la rutina
:0040DE6F test cl, cl // mira si CL (el cáracter del primer grupo de dígitos) es 0
:0040DE71 jz short loc_40DE89 // si es 0 salta
:0040DE73 mov cl, [eax+1] // lee siguiente dígito del primer grupo de 4 dígitos introducidos
:0040DE76 cmp cl, [edx+1] // y compara con el siguiente dígito calculado
:0040DE79 jnz loc_40DFED // si no son iguales salta al final de la rutina
:0040DE7F add eax, 2 // incrementa punteros en 2
:0040DE82 add edx, 2
:0040DE85 test cl,cl // mira si CL (el cáracter del primer grupo de dígitos) es 0
:0040DE87 jnz short loc_40DE65 // si no es 0 salta para procesar los siguientes dos dígitos
:0040DE89 loc_40DE89: ; CODE XREF: sub_40DDBC+B5
:0040DE89 jnz loc_40DFED // si no es 0 salta
:0040DE8F push 0Ah // empuja AH a la pila (para pasarselo a la funcón que cálcula lós dígitos)
:0040DE91 lea eax, [ebp+var_3F0] // lee suma de los caracteres (la que se calculo en primer lugar)
:0040DE97 push eax // y la copia a la pila
:0040DE98 mov eax, ebx // copia de nuevo la suma a EAX (EBX contenia la suma)
:0040DE9A mov ecx, 5 // prepara ECX para dividir entre 5
:0040DE9F xor edx, edx // borrar EDX para dividir
:0040DEA1 div ecx // divide la suma entre 5
:0040DEA3 mov eax, [ebp+edx*4+var_404] // y usa el resto en EDX para leer valor de la tabla guardada en la pila
:0040DEAA push eax // y la pasa a la función que calcula los digitos
:0040DEAB call sub_50CFEC // calcula el último grupo de 4 dígitos
:0040DEB0 add esp, 0Ch // reajusta la pila
:0040DEB3 mov edx, [ebp+arg_10] // copia dirección del buffer donde están los últimos 4 dígitos
:0040DEB6 loc_40DEB6: ; CODE XREF: sub_40DDBC+11C
:0040DEB6 mov cl, [eax] // lee dígíto del grupo de dígitos introducidos
:0040DEB8 cmp cl, [edx] // y lo compara con el cálculado
:0040DEBA jnz loc_40DFED // si no es igual salta al final de la rutina (número no es válido)
:0040DEC0 test cl, cl // mira si el dígito es 0
:0040DEC2 jz short loc_40DEDA // si es 0 salta
:0040DEC4 mov cl, [eax+1] // lee siguiente caracter del grupo de dígitos introducido
:0040DEC7 cmp cl, [edx+1] // y compara con el siguiente dígito calculado
:0040DECA jnz loc_40DFED // si no es igual salta (número no es válido)
:0040DED0 add eax, 2 // incrementa punteros en 2
:0040DED3 add edx, 2
:0040DED6 test cl, cl // mira si es el fín del buffer
:0040DED8 jnz short loc_40DEB6 // si no es el fin salta para comparar resto de dígitos

Como puede comprobarse después de cálcular el primer grupo de dígitos se pasa a comprobar si los introducidos por el usuario eran iguales (lineas 40DE65 a 40DE87). En caso que no sean iguales se salta al final de la rutina y a partir de allí ya te imaginas lo que pasa (mensaje de error etc.) Si los dígitos eran iguales se coge la suma original (nombre+email) y se divide entre 5, y se usa el resto (EDX) como índice (línea 40DEA3) a la tabla que se rellenó en la pila. Con el valor que se obtiene de esta tabla se calcula el siguiente grupo de 4 dígitos de la misma manera que se hizo con el primero, se pasa a la función y esta devuelve un puntero al buffer donde están los dígitos. Luego se comparan con los introducidos (línea 40DEB6), actuando de acuerdo con la situación.

Ya tenemos suficiente información sobre el algoritmo de cálculo, por lo que vamos a pasar a ver el código fuente para el keygen, lo he realizado en C con el compilador lccWin32 que es una maravilla en cuanto a generación de código se refiere (4Kb). Podia haber prescindido de escribir la rutina computedigit() ya que como he dicho antes esta solo cálcula la representación alfanumérica decimal de la suma hexadecimal pasada, pero he preferido imitar el funcionamiento de la rutina original para poner en práctica mi algoritmica ;). Notese que podiamos haber usado el printf del C en vez de hacer nosotros la rutina (p. ej. printf(\n %d,sum+0x9c5) lo cambiariamos por computedigit(sum+0x9c5,digit1) ).

Curiosidades técnicas: si por algún motivo registras el programa a un nombre que no te gusta o simplemente quieres desregistrarlo, tan solo deberás borrar el fichero fileid.sys que el Deep Green Reversi crea en el directorio de Windows, este fichero a sido curiosamente ocultado bajo un nombre poco sospechoso, pero quíen se cree que un archivo SYS ocupe 1Kb?. Su contenido es bastante interesante, contiene el nombre del usuario, su e-mail y su código de registro todo ello en texto plano, sin encriptar. Para averiguar esto utilizé el Filemon, ya que quería probar el keygenerator a fondo con diversos nombres. ;).


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

#define DIGITLENGTH 5

void computedigit(unsigned long suma, char *buffer);

int main()
{
unsigned long table[5]={ 0x00000D87,0X00002234,0x00002231,0x0000128B,0x000015AD};
unsigned long sum,length;
char digit1[DIGITLENGTH];
char name[80];
char email[80];


printf("\nDeep Green Reversi v4.3.7 / KeyGen by Black Fenix");
printf("\n-------------------------------------------------\n");
printf("\nEnter registration name:");
gets(name);
printf("\nEnter registration mail:");
gets(email);

// Borra el contenido de la cadena donde se almacenarán los digitos
ZeroMemory(digit1,DIGITLENGTH);

// convierte las cadena OEM a char, esto es necesario para los carácteres especiales (acentos etc.), el programa
// original no lo hace porque no es una aplicación de consola, la nuestra si lo es y por eso hace falta

OemToChar(name,name);
OemToChar(email,email);

length=strlen(name);

// calculamos la suma de caracteres del nombre
sum=0;

for (int i=0;i<length;i++)
{
sum=name[i]+sum;
}

length=strlen(email);

// calculamos la suma de caracteres del nombre + la del email
for (int i=0;i<length;i++)
{
sum=email[i]+sum;
}

// calculamos el primer grupo de 4 digitos, notese que el programa Deep Green Reversi realiza este calculo primeramente
// sin sumarle 0x9C5, dando un grupo de 4 dígitos que no utiliza para nada

computedigit(sum+0x9C5,digit1);

// lo imprimimos

printf("\nYour serial number is %s - ",digit1);

// obtiene valor de la tabla segun el resto de la división de la suma total entre 5, y con ello calcula
// el siguiente grupo de dígitos

computedigit((unsigned long)table[(sum % (unsigned char)5)],digit1);

// lo imprimimos
printf("%s\n",digit1);

// esperamos pulsación de tecla, es para poder ver/copiar el número ;)
getchar();
return 0;
}

void computedigit(unsigned long suma, char *buffer)
{
unsigned char val;
unsigned long cont=DIGITLENGTH-2;

do
{
// hace el módulo 10 de la suma
val =(unsigned char)(suma % 0xA);
// comprueba que el resto (módulo) sea válido < 0xA (10)
if (val>=10)
{
// no es válido, hacemos que el dígito sea igual a 'W'
val=0x57;
}
else
{
// es válido, le sumamos 30h para convertirlo en su representación alfanumérica
val=val+0x30;
}
// lo guardamos en el buffer
buffer[cont]=val;
// decrementamos contador
cont--;
suma=(suma / 0xA);
} while ((cont>=0) && (suma!=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 :)