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