Karpoff Spanish Tutor 1999-2002

Anatomía de un keygen

 

PROTECCION:

Name / Serial.

Objetivo:

Hacerle la autopsia a un Keygen ;-)

Dificultad:

Aficionado.

Herramientas:

MASM32

CRACKER:

CaoS ReptantE

  FECHA:

23/11/2001

 

 INTRODUCCION

Esta vez vamos a hacer algo un poco distinto. Vamos a dedicar este tuto a analizar paso a paso la creación de un keygen y, para podernos concentrar en esta tarea, prescindiremos en la medida de lo posible del programa "víctima". Así pues, la explicación del sistema para generar el serial a partir del nombre, estará basada en el código del keygen, y no en el de dicho programa. Es por esta razón por lo que en el apartado de herramientas, sólo figura el MASM32, ya que la imprescindible utilización del SoftIce no está directamente relacionada con el objetivo de este tuto.

 

 

Programa:

Active Clouds Screen Saver

Descripción:

Salvapantallas.

DOWNLOAD:

http://controlzed.com/areality/download/aclouds.exe

 

 

 AL ATAKE

Sobre este programa nos limitaremos a indicar que el punto de partida para la generación del serial está en 407A68, aunque también existe un segundo serial, calculado de manera análoga a partir de 407B19. Digamos también que el primer serial - sobre el que se basa el keygen - nos dará acceso a un registro que nos permitirá el uso del salvapantallas para todas las versiones actuales y futuras, y en cambio, el segundo serial sólo permite su uso hasta la aparición de la versión 2.0.

Existen varias herramientas para programar en assembler. Hasta ahora he utilizado siempre el MASM32 (lo podéis bajar de http://www.movsd.com/download/masm32v6.zip) y por lo tanto, este trabajo va a tratar sobre un keygen programado con dicha herramienta.

Para programar nuestro keygen, necesitaremos crear dos archivos: El de recursos, con extensión .rc y el de código, con extensión .asm. Aunque están contenidos en este escrito, podéis verlos con más claridad aquí: archivo de recursos y archivo de código.

En primer lugar veremos el archivo de recursos, el cual contiene la definición de la ventana del programa, así como la de los controles contenidos en ella. Este archivo (al igual que el de código) se puede escribir con cualquier editor o preferiblemente con el qeditor del MSAM. También se puede utilizar alguno de los editores de recursos que hay en la Red (estoy tratando de encontrar alguno que me convenza), pero en todo caso es útil saber como diseñar nuestra aplicación manualmente.

#include "\masm32\include\resource.h"

Hay una serie de definiciones que emplearemos en este archivo que están contenidas en el archivo resource.h. Mediante esta línea, nos evitamos el trabajo de definirlas aquí de una en una.

#define IDD_DIALOG          1000
#define IDC_NOMBRE          1001
#define IDC_SERIAL          1002
#define IDC_REGIS           1003
#define IDC_EXIT            1004

Estas definiciones corresponden a los controles que figuran en la ventana del programa. Los números con que los identificamos pueden ser los que nos parezca, pero lógicamente distintos entre sí, y serán los mismos con los que nos referiremos a estos elementos en el archivo de código.

#define IDC_STATIC          -1

El número de identificación de estos elementos debe ser -1 que significa que no se toma en consideración, ya que por tratarse de elementos estáticos (es decir que no cambian durante la ejecución del programa), no son tratados en el archivo de código.

app ICON reg.ico

Aquí va el nombre del icono que queremos asignarle a nuestro programa.

IDD_DIALOG DIALOG       100,100,129,121

Aquí definimos la ventana del programa. En este y en los elementos que se definirán a continuación figuran cuatro cifras. Las dos primeras corresponden a las coordenadas en pixels de la pantalla donde aparecerá representada (seguidamente veremos que en este caso, por la inclusión del estilo DS_CENTER, dichas cifras no tienen ningún valor), y las otras dos a la anchura y altura respectivamente.

STYLE DS_CENTER | WS_SYSMENU | WS_MINIMIZEBOX

Hay una serie de estilos que pueden asociarse a nuestra ventana. Los que utilizamos aquí sirven para que la ventana aparezca centrada en la pantalla (DS_CENTER), para que aparezca el icono en la parte izquierda de la barra superior y el aspa que nos permite cerrar la aplicación en la parte derecha (WS_SYSMODE), y para que junto al aspa aparezca el símbolo de minimizar la ventana (WS_MINIMIZEBOX). Podríamos poner más cosas, como por ejemplo el símbolo de maximizar la ventana pero como podéis suponer, el maximizar la ventana no quedaría muy estético. Podríamos plantearnos lo contrario, es decir dejar únicamente DS_CENTER y así quedaría la barra superior sin nada más que el texto que quisiéramos ponerle, pero eso es cuestión de gustos.

CAPTION "Active Clouds"

Hablando del texto... pues aquí está.

FONT 10, "MS Sans Serif"

Y finalmente el estilo y tamaño de letra que vamos a utilizar. Lo que hemos visto hasta ahora se refiere a la ventana en sí, ahora veremos los controles que contiene:

 

Esta figura y las cotas que hay en ella (podéis ver que no están todas), nos permitirán ver mejor el
significado de las cifras que acompañan a la definición de cada elemento contenido en la ventana

BEGIN

GROUPBOX "Programa",IDC_STATIC,8,6,113,20

Estos recuadros los utilizamos en este caso únicamente por razones estéticas.

LTEXT "Active Clouds Screen Saver 1.02",IDC_STATIC,12,14,105,8,ES_CENTER

También por razones estéticas, centramos este texto.

GROUPBOX "Website",IDC_STATIC,8,28,113,20
LTEXT "http://controlzed.com/",IDC_STATIC,12,36,105,8,ES_CENTER
GROUPBOX "Registro",IDC_STATIC,8,50,113,44
LTEXT "Nombre",IDC_STATIC,17,61,25,8
EDITTEXT IDC_NOMBRE,45,60,66,11,ES_AUTOHSCROLL | ES_CENTER

Aquí además de centrar el texto le permitimos un desplazamiento horizontal, es decir, que si el nombre es más largo que la casilla que lo ha de contener, las letras se irán desplazando, desapareciendo por la izquierda. Un estilo que a veces puede ser útil es ES_UPPERCASE, para aquellos programas que requieren que el nombre se escriba en mayúsculas. Como podemos ver, cuando se aplica más de un estilo, se escribe a continuación del anterior, separándolo de éste mediante una barra vertical.

LTEXT "Serial",IDC_STATIC,17,78,25,8
EDITTEXT IDC_SERIAL,45,77,66,11,ES_CENTER | ES_READONLY

En esta casilla, además de centrar el texto, impedimos la escritura, ya que sólo el programa debe escribir en ella.

PUSHBUTTON "Generar",IDC_REGIS,8,101,48,14
PUSHBUTTON "Salir",IDC_EXIT,73,101,48,14

Lo único resaltable de la definición de los botones son las coordenadas y dimensiones (ver la figura) para que queden alineados, ya que los diferentes estilos aplicables en ellos no aportarían nada positivo.

END

Bien, hemos terminado con el fichero de recursos, ahora hay que grabarlo. En principio se puede salvar con cualquier nombre, pero me he encontrado con que a la hora de compilar el ejecutable, si este archivo estaba grabado con un nombre cualquiera, me daba error por no encontrar el archivo rsrc.rc, por lo que siempre lo grabo con este nombre. Podemos compilarlo ahora desde el qeditor (Projet / Compile Resource File) o hacerlo más tarde, al crear el ejecutable (Projet / Build All). Al compilar este archivo se crean los archivos rsrc.RES y rsrc.obj.
Los estilos que hemos utilizado aquí y por supuesto los que no hemos utilizado, podéis encontrarlos comentados en Win32 Programmer's Reference, en el apartado
CreateWindow.

Nos ocuparemos ahora del fichero de código.

.386

Indicamos al ensamblador el juego de instrucciones que vamos a emplear. Lo más cómodo y seguro por razones de compatibilidad es utilizar el del 80386.

.model flat,stdcall

Model se refiere al tipo de memoria. En Win32 siempre es flat. Stdcall se refiere al orden en que se pasan los parámetros y al restablecimiento de la pila después de un call.

option casemap:none

Esto significa que el ensamblador debe distinguir entre mayúsculas y minúsculas.

include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\advapi32.inc
include c:\masm32\include\kernel32.inc
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\advapi32.lib
includelib c:\masm32\lib\kernel32.lib

Incluimos varios archivos en los que se contienen una serie de constantes y prototipos de funciones que empleamos en el programa. Si por ejemplo, en este programa empleáramos funciones como RegOpenKeyExA o RegQueryValueExA, MSAM nos daría error al intentar ensamblar el archivo .asm por lo que deberíamos añadir las líneas: include c:\masm32\include\advapi32.inc
includelib c:\masm32\lib\advapi32.lib
.

.const

IDD_DIALOG        EQU 1000
IDC_NOMBRE        EQU 1001
IDC_SERIAL        EQU 1002
IDC_REGIS         EQU 1003
IDC_EXIT          EQU 1004

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD
Operacion PROTO

Aquí tenemos por un lado, las definiciones que habíamos hecho en el fichero de recursos exceptuando las de los elementos estáticos, y por otro, los prototipos de las funciones que emplearemos en el programa y que no están incluidas en los archivos detallados anteriormente. Si empleamos alguna subrutina en el programa, debemos incluirla aquí, junto con sus parámetros si los tuviera. 

.data
Icono     db "app",0
Iconimage db "app",0
nombre    db 64 dup(0),0
string    db "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz",0
minombre  db "CaoS ReptantE",0
serial    db 9 dup(0),0
noserial  db " ",0
error1    db "¡Error!",0
error2    db "Me temo que tu nombre contiene demasiadas letras",13,10,\
       "de las que a los yanquis les parecen extrañas :o(",0
hInstance dd 0
hIcon     dd 0
hWnd      dd 0
handle    dd ?

En esta sección incluimos las variables que vamos a utilizar en el programa. Observad que las variables de texto, siempre deben terminar en cero, lo que le permite saber al programa que se ha llegado al final de dicha variable. Si queremos introducir comillas en un texto, debemos ponerlas dobles para que el programa sepa que van dentro del texto y no crea que termina un texto y empieza otro. La expresión n dup(0) representa una cadena de n ceros. La antibarra no tiene ningún valor funcional, sólo significa que el texto de la instrucción no nos cabe en esta línea y se continúa en la siguiente. Un salto de línea se indica mediante 13,10. El valor ? corresponde en este caso a un valor que no definimos, sino que nos va a dar el programa durante su ejecución.

.code

Aquí empieza el código.

start:
invoke GetModuleHandle,NULL
mov hInstance,eax

Obtenemos el handle de nuestro programa y lo ponemos en la variable hInstance.

invoke DialogBoxParam,hInstance,IDD_DIALOG,NULL,ADDR DlgFunc,NULL
invoke ExitProcess,NULL

He aquí la función de nuestra ventana y la de salir del programa. Una de las características de MASM es la utilización de invoke. En assembler, la llamada a esta función se haría mediante:
    push arg5
    push arg4
    push arg3
    push arg2
    push arg1
    call Funcion
Fijáos en el orden en que se guardan en la pila los parámetros de la función. La pila sigue la norma LIFO (last in, first out) y funciona como una pila de platos. Si colocamos uno a uno cinco platos sobre una pila, y luego los vamos cogiendo, tomaremos en primer lugar el último que hayamos puesto, luego el penúltimo etc., hasta llegar al que hayamos puesto en primer lugar, que será el último que cogeremos.
En cambio, MASM hace la llamada a la función utilizando invoke:
    invoke Funcion,arg1,arg2,arg3,arg4,arg5
Sigamos con el programa:

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

Con el inicio de la ejecución de nuestro programa entramos en un bucle sin fin en el que el programa permanece a la espera de que se produzca algún evento para actuar según corresponda.

.if uMsg==WM_INITDIALOG
    mov eax,hDlg
    mov hWnd,eax

Se ha producido la apertura de la ventana de nuestro programa, y hemos obtenido su handle.

    invoke LoadIcon,hInstance,ADDR Icono
    mov hIcon,eax
    invoke SendMessage, hDlg, WM_SETICON,ICON_BIG,hIcon

Hemos introducido en el programa el icono y hemos obtenido su handle, el cual utilizamos para asignar mediante la función SendMessage, el icono que aparecerá en la barra superior del programa y en la barra de tareas cuando esté minimizado. Si no hiciéramos esto último, nos aparecería en estos lugares un icono genérico de Windows, pero en cambio si creáramos un acceso directo al programa, aparecería con el icono correspondiente. 

    invoke SetDlgItemText,hDlg,IDC_NOMBRE,ADDR minombre

En la casilla del nombre, cegados por la vanidad y la soberbia ;-) colocamos el nuestro.

.elseif uMsg==WM_CLOSE
    invoke EndDialog,hDlg,NULL

Se ha dado orden de cerrar la ventana (y en este caso, el programa) desde el aspa de la parte superior derecha de la ventana.

.elseif uMsg==WM_COMMAND
    mov eax,wParam
    mov edx,eax
    shr edx,16
        .if dx==BN_CLICKED

¿Que está pasando? Que la función nos devuelve en EAX dos datos: el valor de código del mensaje si éste ha sido emitido desde un control, o 0 si procede de un menú, o 1 si procede de una tecla aceleradora (en el word alto) y el código del menú, control o tecla aceleradora (en el word bajo=AX). Copiando EAX en EDX y desplazando el contenido de este registro 16 bits a la derecha, conseguimos tener el word alto de EAX en DX. De esta manera, examinando DX podemos ver si se ha pulsado un botón y en caso afirmativo, podemos saber cual ha sido, comprobando el valor de AX:

            .if ax==IDC_EXIT
                invoke EndDialog,hDlg,NULL

El botón pulsado ha sido el de salir del programa. El resultado es el mismo que pulsando el aspa de la parte superior derecha.

            .elseif ax==IDC_REGIS
                invoke GetDlgItemText,hDlg,IDC_NOMBRE,ADDR nombre,65
                invoke Operacion

El botón pulsado ha sido el de generar el serial para registrarnos. En primer lugar, obtenemos el nombre introducido en la casilla correspondiente y pasamos a la función Operacion que veremos más adelante. El número 65 representa la longitud máxima de caracteres que se van a tomar en cuenta, si el nombre es más largo, se perderán los caracteres que excedan de esta cantidad. Fijáos que al definir la variable nombre le he dado una longitud de 64 caracteres, pero aquí he puesto 65 ya que según he podido ver, se cuenta también el cero final.

                    .if eax==666
                    invoke MessageBox,hDlg,ADDR error2,ADDR error1,MB_ICONSTOP
                    invoke SetDlgItemText,hDlg,IDC_SERIAL,ADDR noserial
                    .else
                    invoke SetDlgItemText,hDlg,IDC_SERIAL,ADDR serial
                    .endif
                    ret

Ya hemos vuelto y hay que ver que ha pasado.
Si ha habido un error, EAX contendrá el número fatídico y nos aparecerá un mensaje de error con los textos que hemos definido anteriormente para la barra superior de la ventana (
error1) y para el interior de la misma (error2). También definimos el icono que debe aparecer en el interior de la ventana (podríamos poner en lugar de MB_ICONSTOP el valor que le corresponde, es decir 16, pero indudablemente así se ve más claro). También colocamos en la casilla del serial una cadena de espacios porque si antes de introducir el nombre causante del error, hubiéramos probado con otro nombre, nos quedaría en la casilla del serial el resultado obtenido anteriormente, lo cual podría dar lugar a confusión.
Si por el contrario, todo ha ido bien, el programa escribirá el serial en la casilla correspondiente.

              .endif
        .endif
.endif

Procuramos cerrar todos los bucles que tenemos abiertos y casi hemos terminado.

ret
DlgFunc endp

Ahora sí que hemos terminado.

Lo que hemos visto hasta ahora es aplicable a la mayoría de keygens. Pueden variar los botones, las variables, pero el esquema será muy parecido. Ahora veremos la función Operacion que es aplicable únicamente a este keygen en particular, aunque por las explicaciones veréis que también hay cosas que son de aplicación general.

Operacion proc
pushad

Esta instrucción guarda todos los registros en la pila. Su utilización aquí es necesaria porque Windows utiliza algunos registros para su funcionamiento interno, por lo que si los utilizamos, debemos dejarlos como estaban cuando terminemos con ellos. Al final, veremos la instrucción popad que se encarga de ello.

mov ebx, offset nombre
xor eax, eax
xor edx, edx
bucle1:
mov dl, byte ptr [ebx]
movsx edx, dl

Fijáos en estas dos instrucciones. Lo que queremos hacer es poner en EDX (4 bytes) el contenido de una posición de memoria ocupada por una letra del nombre (1 byte). No podemos utilizar la instrucción mov edx, byte ptr [ebx] que nos daría error por esa disparidad de tamaños, y por lo tanto necesitamos la segunda instrucción que puede ser la que hemos puesto aquí o and edx 000000FFh que produce los mismos resultados. Esta explicación la he puesto aquí porque es un caso frecuente pero concretamente en este caso, podemos trabajar únicamente con DL porque al principio hemos puesto el registro a cero (xor edx, edx) y no hemos puesto nada en él que sobrepase el valor de DL.

inc ebx
add eax, edx

Sumamos el código ASCII de la letra que hemos tomado con el contenido de EAX, que también hemos puesto a cero y el resultado se pone en EAX.

.if dl!=0
      jmp bucle1
.endif

Comprobamos si se trata de la última letra y en caso negativo volvemos a ejecutar el bucle. Cuando terminamos la última ejecución del bucle, nos encontramos con que la suma de los códigos de todas las letras que forman el nombre está almacenada en EAX.

.if eax>7FFFFFFFh
      popad
      mov eax, 666
      ret
.endif

Aquí nos encontramos con un problema debido a la peculiar manera de representar los números negativos en hexadecimal, ya que no cabe la posibilidad de utilizar guiones para indicar cantidades negativas. Lo que se utiliza para efectuar esta representación es el llamado complemento a dos. No vamos a ver en que consiste este sistema, en este caso concreto sólo necesitamos saber que los números positivos estan comprendidos entre 00h (0) y 7Fh (127) y los negativos entre 80h (-128) y FF (-1). ¿En que nos afecta esto? Pues en que si utilizamos en nuestro nombre caracteres cuyo código ASCII sea mayor de 127 (por ejemplo Ñ, ñ, Ç, ç o letras acentuadas), resultará que en vez de sumar, lo que estaremos haciendo es sumar un número negativo, es decir, restar. En el caso poco probable de que la suma de codigos de letra nos dé un número negativo, el keygen no funcionará, porque el programa "víctima" no ha previsto esa posibilidad. Así pues, si la suma de los códigos ASCII de las letras es mayor de 7FFFFFFFh, terminaremos la ejecución de Operacion y daremos un mensaje de error. Pero el salir no es tan sencillo. Cuando se hace un call o llamada a una subrutina, se coloca en la pila la dirección de la instrucción a ejecutar al regreso, por lo que antes de volver debemos dejar la pila como estaba al empezar la ejecución de la subrutina. En este caso hemos modificado la pila mediante la instruccion pushad, por lo que debemos restablecer el valor de la pila con popad. Después de esto (y no antes porque el valor se perdería) colocamos el número de la Bestia en EAX (naturalmente, podría ser cualquier otro) y - ahora sí - podemos regresar

mov ebp, offset serial
mov byte ptr [ebp], 41h
inc ebp

La primera letra del serial siempre es "A" por lo tanto, empezamos poniendo su código ASCII en la dirección señalada por EBP. A continuación incrementamos EBP para que apunte al primer espacio vacío del campo serial.

mov edi, offset string

Colocamos en EDI la dirección de string. Si contamos los caracteres que forman esta cadena veremos que son 62.

push eax

El valor de EAX lo necesitaremos para calcular cada carácter del serial, y lo guardamos en la pila porque el valor del registro EAX se va a modificar a causa de las instrucciones imul e idiv que lo utilizan.

mov ebx, 2
mov esi, 3Eh

Se pone 2 en EBX y 3Eh (62) en ESI.

bucle2:
pop eax
push eax

Recuperamos el valor de EAX y lo volvemos a guardar.

imul ebx

Multiplicamos el valor de EAX por EBX o sea, la suma de los códigos de las letras por dos. En las siguientes ejecuciones del bucle se irá multiplicando la misma cifra por tres, cuatro, y así hasta ocho.

cdq
idiv esi

La instrucción idiv esi divide el contenido de EDX:EAX por ESI poniendo el resultado de la división en EAX y el resto en EDX. Para poderse efectuar esta división el contenido de EDX debe ser igual a cero, lo que puede conseguirse mediante cdq o xor edx, edx. En este caso, no sería necesario poner a cero el registro EDX, pues como el resultado de la multiplicación anterior se coloca en EDX:EAX, con los valores que manejamos en este programa se pone EDX a cero.

mov dl, [edi+edx]

En EDI habíamos colocado la dirección del inicio de la cadena string. Ahora le sumamos el resto de la división anterior y tomamos el código ASCII del carácter correspondiente a la dirección indicada por esta suma, colocándolo en DL. Ahora vemos lo que habría sucedido si el valor de que hablábamos antes fuera negativo y no hubiéramos salido de Operacion. También podemos ver que como la cadena string tiene 62 caracteres y hemos dividido la suma de códigos por 62, el resto será un número comprendido entre 0 y 61 que contado a partir del inicio de string, nos dará un carácter comprendido entre "A" y "z".

mov [ebp], dl

Colocamos el carácter escogido en su lugar de la variable serial.

inc ebx
inc ebp

Incrementamos estos registros para la siguiente ejecución del bucle.

.if ebx==5
      mov byte ptr [ebp], 2Dh
      inc ebp
.endif

Comprobamos si estamos en el quinto carácter del serial. En caso afirmativo, colocamos un guión e incrementamos EBP para que apunte al lugar que ha de ocupar el siguiente carácter.

.if ebx<9
      jmp bucle2
.endif

Comprobamos si hemos llegado al último carácter del serial, si no es así, saltamos al inicio del bucle.

pop eax
popad

Recuperamos los registros, cuidando de no olvidar que teníamos un push eax de más :-)

ret
Operacion endp

Se ha acabado Operacion.

end start

Se ha acabado el programa.

Pues ahora se trata de grabar el fichero con el nombre que queramos y la extensión .asm. y ensamblarlo ahora (Projet / Assemble ASM file) o al crear el ejecutable (Projet / Build All). Al ensamblarlo se nos creará el archivo nombre.obj.

 DOCUMENTACIÓN RELACIONADA:

Win32 Programmer's Reference.
http://212.14.34.87/~fkiepski/down/helpy/winapi32.zip

Intel Pentium Instruction Set Reference Produced
http://212.14.34.87/~fkiepski/down/helpy/asmhelp.zip

Tutoriales de Iczelion - Incluidos en el paquete de MSAM.

Como Craquear con Ensamblador para Win32 by Mr_Crimson - Incluido en la Compilación de Tutoriales 2000 4.0 (recopilación del Pr@fesor X).

Tutoriales de JoTaKe - Incluidos en la misma compilación.

Apocalipsis. San Juan 13,18.


Antes de despedirme debo recordaros que debéis pagar por los programas que utilicéis regularmente, ya que de lo contrario, podéis ser bombardeados por los B-52 o peor aún, sufrir daños colaterales ;-)

Para terminar, quiero saludar a DeK_Oin, Act MagO, Karpoff, KuaTo_ThoR, Silver Storm "¿Silver, cuantos programas has crackeado hoy?" ... "¿SOLO 12?" ... "amigo, estás en baja forma ;-)", Mharselo y especialmente al Pr@fEsOr X, ya que una conversación con él me dio la idea de hacer este tuto, y agradecer a ByTESCRK y a vLuGo así como al propio profe sus comentarios y sugerencias.

Si queréis alguna aclaración sobre mis tutos, mi dirección de e-mail es: caos_reptante@hotmail.com

 

Karpoff Spanish Tutor: Pagina dedicada a la divulgacion de informacion en Castellano, sobre Ingenieria Inversa y Programacion. Email "Colabora con tus Proyectos"

www.000webhost.com