ESTUDIO COLECTIVO DE DESPROTECCIONES
Última Actualizacion: 25/10/2001

Programa Swish v1.51 W95 / W98 / NT
Descripción Programa para crear banners
Tipo Utilidad grafica
Tipo de Tutorial [X]Original, []Adaptación, []Aplicación, []Traducción
Url http://swishzone.com
Protección Time Limit
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas SoftIce v4.05
Objetivo Crear un Keygen
Cracker Mr.YelloW
Grupo Whiskey Kon Tekila
Fecha 16 de Septiembre de 2000

INTRODUCCION
Este programa es una cononida aplicación para crear banners de publicidad. Pero a nosotros lo que nos interesa es la protección que utiliza. Se trataría de otro programa que utiliza una protección de número de serie de no ser por que el algoritmo esta basado en dos conocidos algoritmos de encriptación, Blowfish y SHA-1.
AL ATAQUE
Lo primero que voy a hacer es repasar todo el algoritmo de verificación del serial desde el principio, de modo que se pueda tener una mejor perspectiva a la hora de afrontar la protección en su conjunto.

Bien, comencemos. En el menú de introducción de los datos tenemos un cuadro de edición para introducir el nombre, y justo debajo aparece un código deducido a partir de las caracteristicas de nuestra máquina (que es el número de volumen del disco duro). Más abajo tenemos el cuadro de edición para introducir el número de serie.

Si colocamos un breakpoint en función GetWindowTextA comprobamos que el programa se interrumpe 3 veces, presumiblemente por los tres datos para verificar el serial. Traceando a partir de la tercera interrupción llegamos a una serie de instrucciones muy interesantes, en concreto en la direccin 43A69D:

(NOTA: no voy a explicar instrucción a instrucción que es lo que hacen las rutinas ya que parto de que hay un mínimo de conocimientos, a parte de que el lector debería ser quien analizara por su cuenta los algoritmos con la ayuda de este texto.)

:0043A69D 8A03                            mov al, byte ptr [ebx]  ; EBX es un puntero al serial
:0043A69F 84C0                            test al, al
:0043A6A1 741A                           je 0043A6BD
:0043A6A3 8BF3                           mov esi, ebx
:0043A6A5 8B8D34020000          mov ecx, dword ptr [ebp+00000234]
:0043A6AB 2C30                          sub al, 30 ; A todos los caracteres se les resta 0x30, de modo que los caracteres
:0043A6AD 50                               push eax     ; deberan tener un valor igual o superior a 0x30.
:0043A6AE E89DFB0000              call 0044A250   ; Se realiza un algoritmo que veremos despues.
:0043A6B3 8806                            mov byte ptr [esi], al   ; Se sustituye el valor antiguo
:0043A6B5 8A4601                       mov al, byte ptr [esi+01]
:0043A6B8 46                                inc esi
:0043A6B9 84C0                           test al, al
:0043A6BB 75E8                           jne 0043A6A5

Ahora vamos a ver en detalle el algoritmo de la dirección 44A250:

:0044A250 8A4C2404                mov cl, byte ptr [esp+04]
:0044A254 32C0                         xor al, al
:0044A256 84C9                         test cl, cl
:0044A258 7C05                         jl 0044A25F
:0044A25A 80F909                    cmp cl, 09
:0044A25D 7E29                        jle 0044A288
:0044A25F 80F910                    cmp cl, 10
:0044A262 7C0D                       jl 0044A271
:0044A264 80F92A                   cmp cl, 2A
:0044A267 7F08                        jg 0044A271
:0044A269 80E906                    sub cl, 06
:0044A26C 8AC1                      mov al, cl
:0044A26E C20400                   ret 0004
:0044A271 80F92F                   cmp cl, 2F
:0044A274 7505                        jne 0044A27B
:0044A276 B025                       mov al, 25
:0044A278 C20400                   ret 0004
:0044A27B 80F931                   cmp cl, 31
:0044A27E 7C0A                      jl 0044A28A
:0044A280 80F94A                  cmp cl, 4A
:0044A283 7F05                       jg 0044A28A
:0044A285 80E90B                  sub cl, 0B
:0044A288 8AC1                      mov al, cl
:0044A28A C20400                  ret 0004

Aqui se realizan toda una serie de comparaciones de lo mas sencillas. De modo que en función del valor introducido devuelve uno u otro. Pero la inversión de este algoritmo la veremos mas tarde, de momento sigamos viendo que más se hace.

Ahora que hemos pasado la primera fase del algoritmo general, sigamos traceando. Un poco mas adelante nos encontramos con otro bucle que trabaja sobre el número de serie anteriormente modificado.

:0043A6EA 85FF                           test edi, edi
:0043A6EC 7C1E                          jl 0043A70C
:0043A6EE 8B8D34020000           mov ecx, dword ptr [ebp+00000234]
:0043A6F4 8D543C6C                  lea edx, dword ptr [esp+edi+6C]
:0043A6F8 52                                push edx    ; Puntero a un buffer vacio
:0043A6F9 53                                push ebx    ; Inicialmente puntero a los últimos 4 datos del serial
:0043A6FA E891FB0000              call 0044A290
:0043A6FF 83EE04                       sub esi, 00000004
:0043A702 83EB04                       sub ebx, 00000004
:0043A705 83EF03                       sub edi, 00000003
:0043A708 85F6                           test esi, esi
:0043A70A 7DDE                         jge 0043A6EA

Lo que se realiza aqui es transformar grupos de 4 bytes en 3 bytes, de modo que si el serial tiene 8 bytes, este quedará en otro de 6 bytes. Hay que tener en cuenta que este empieza por el final. Como he dicho antes, mas adelante nos encargaremos de invertir la función de la dirección 44A290, la cual lo único que hace es pasar un algoritmo que a partir 4 bytes se deduzcan los valores de 3 bytes.

Mas tarde, en la dirección 43A741 se hace una copia del nombre de usuario a un buffer vacio que esta justo antes del buffer donde se ha modificado el número de serie. Inmediatamente despues se coge el número de la máquina y se copia sobre el nombre de usuario. Veamoslo:

:0043A741 56                             push esi    ; Pumtero del nombre de usuario
:0043A742 50                             push eax    ; Puntero del buffer vacio
:0043A743 E868A20700            call 004B49B0    ; Aqui se realiza la copia
:0043A748 8B8D34020000        mov ecx, dword ptr [ebp+00000234] 
:0043A74E 8BFE                        mov edi, esi
:0043A750 33C0                        xor eax, eax
:0043A752 83C40C                   add esp, 0000000C
:0043A755 8B9184010000        mov edx, dword ptr [ecx+00000184]    ;Coge el número de la maquina
:0043A75B 83C9FF                  or ecx, FFFFFFFF
:0043A75E 8954244C               mov dword ptr [esp+4C], edx    ; Lo copia sobre el nombre de usuario

Ahora se realiza una serie operaciones XOR para sacar un valor a partir del nombre de usuario original:

:0043A762 B2AA                       mov dl, AA    ;Valor inicial del registro DL
:0043A764 F2                             repnz
:0043A765 AE                            scasb
:0043A766 F7D1                        not ecx
:0043A768 49                            dec ecx           ; Se saca la longitud del nombre de usuario
:0043A769 85C9                        test ecx, ecx
:0043A76B 7E0A                       jle 0043A777
:0043A76D 8A1C06                   mov bl, byte ptr [esi+eax]    ; Puntero al nombre de usuario
:0043A770 32D3                        xor dl, bl           ; Operación  XOR
:0043A772 40                             inc eax
:0043A773 3BC1                       cmp eax, ecx
:0043A775 7CF6                       jl 0043A76D
:0043A777 3A94248C000000  cmp dl, byte ptr [esp+0000008C]    ; Comparación de validación
:0043A77E 0F85A6000000      jne 0043A82A

La comprarción que se realiza en la direccion 43A777 sirve para de alguna manera validar del número de serie, sería algo asi como la primera prueba de autenticidad. Lo que hace es comparar el valor obtenido de las operaciones XOR del nombre de usuario con el 4º valor empezando por el final del serial modificado en el ultimo paso. Para que el serial sea válido tiene que darse la condición de que sean iguales. Ya tenemos una condición del serial.

A continuación se llama a una función que recibe como parametros un buffer vacio de una longitud de  20 bytes, los 3 últimos caracteres deducidos del número de serie modificados, y el presumiblemente un 3 indicando la longitud de estos últimos 3 caracteres.

:0043A786 8D4C2424                    lea ecx, dword ptr [esp+24]
:0043A78A 89442424                    mov dword ptr [esp+24], eax
:0043A78E 51                                push ecx    ; Buffer de 20 bytes
:0043A78F 8944242C                   mov dword ptr [esp+2C], eax
:0043A793 8D942491000000       lea edx, dword ptr [esp+00000091]
:0043A79A 89442430                   mov dword ptr [esp+30], eax
:0043A79E 6A03                          push 00000003
:0043A7A0 89442438                  mov dword ptr [esp+38], eax
:0043A7A4 52                              push edx    ; Tres últimos bytes del serial modificado
:0043A7A5 89442440                  mov dword ptr [esp+40], eax
:0043A7A9 E82268FFFF             call 00430FD0

Si ejecutamos el call, en el buffer de los 20 bytes obtenemos un resultado. Vamos a ver en que consiste este algoritmo:

:00430FD0 83EC5C                  sub esp, 0000005C
:00430FD3 8D442400               lea eax, dword ptr [esp]
:00430FD7 50                            push eax    ; Puntero a zona de memoria de variables
:00430FD8 E8D3DBFFFF        call 0042EBB0    ; Inicialización de las variables
:00430FDD 8B4C2468              mov ecx, dword ptr [esp+68]
:00430FE1 8B542464                mov edx, dword ptr [esp+64]
:00430FE5 51                            push ecx    ; Longitud del mensaje
:00430FE6 8D442408               lea eax, dword ptr [esp+08]
:00430FEA 52                           push edx    ; Puntero al mensaje
:00430FEB 50                           push eax    ; Zona de memoria de variables y padding
:00430FEC E8EFDBFFFF        call 0042EBE0
:00430FF1 8B542478               mov edx, dword ptr [esp+78]
:00430FF5 8D4C2410              lea ecx, dword ptr [esp+10]
:00430FF9 51                           push ecx    ; Puntero a zona de variables y padding
:00430FFA 52                           push edx   ; Puntero a digest
:00430FFB E8E0FEFFFF         call 00430EE0    ; Algoritmo y padding
:00431000 83C474                   add esp, 00000074
:00431003 C3                           ret

En la primera llamada se inicializan unas variables a unos valores muy concretos, veamos cuales són:

:0042EBB6 C70001234567                mov dword ptr [eax], 67452301
:0042EBBC C7400489ABCDEF        mov [eax+04], EFCDAB89
:0042EBC3 C74008FEDCBA98         mov [eax+08], 98BADCFE
:0042EBCA C7400C76543210           mov [eax+0C], 10325476
:0042EBD1 C74010F0E1D2C3          mov [eax+10], C3D2E1F0

Si no tenemos mucha idea de los algoritmos de encriptación estos valores no nos dirián nada, pero para aquel al que le suene el algoritmo Hash  SHA-1, se dará cuenta de que estos son precisamente los valores de inicialización para realizar el algoritmo. Ahora que intuimos (por que todavía no es seguro) a que nos enfrentamos, voy a hacer una pequeñisima mención de lo que es el algoritmo SHA-1. Se trata de una función Hash de una única vía, lo cual quiere decir que una vez aplicado el algoritmo no es posible deducir cual es el plaintext a partir del ciphertext, a menos que se utilice la fuerza bruta. Esto es debido a que utiliza funciones no lineales. Estos producen una salida llamada digest o hash de un número constante de bits, dependiendo del algoritmo. Mientras que la longitud de la entrada es variable, ya que esta se convierte a una cadena de un numero constante de bits.  A este proceso se le llama padding. Estos algoritmos inicialemente se pensaron como funciones de compresión, pero se pueden utilizar para firmas digitales asi como para proteger programas. 

No voy a explicar las operaciones que se realizan dentro del algoritmo. Para mas información leer un documento sobre criptografia.

La salida del algoritmo SHA-1 es de 160 bits, mientras que la longitud del mensaje despues del proceso padding es de 512 bits. Una vez explicado muy por encima el SHA-1, vamos a intentar identificar las diferentes fases.

La función que se ejecuta en la direccion 430FEC realiza una copia del mensaje sobre la zona de memoria donde se va a realizar el proceso de padding. Es en la tercera funcion (430FFB) donde se concluye con el proceso de padding y se aplica el algoritmo propiamente dicho:

:00430F32 6A01                        push 00000001
:00430F34 6814B45100            push 0051B414
:00430F39 56                            push esi
:00430F3A E8A1DCFFFF        call 0042EBE0
:00430F3F 8B4E14                   mov ecx, dword ptr [esi+14]
:00430F42 83C40C                  add esp, 0000000C
:00430F45 81E1F8010000       and ecx, 000001F8
:00430F4B 81F9C0010000      cmp ecx, 000001C0    ; Rellena de ceros hasta los primeros 448 bits
:00430F51 75DF                       jne 00430F32

El resto de los 64 bits es la longitud en bits del mensaje, la cual se copia en las siguientes  lineas:

:0042EC25 8B742418                mov esi, dword ptr [esp+18]
:0042EC29 2BE8                       sub ebp, eax
:0042EC2B 8D7C181C             lea edi, dword ptr [eax+ebx+1C]
:0042EC2F 8BCD                      mov ecx, ebp
:0042EC31 8BC1                       mov eax, ecx
:0042EC33 C1E902                   shr ecx, 02
:0042EC36 F3                            repz
:0042EC37 A5                           movsd

Ahora por último el algoritmo propiamente dicho se encuentra en la dirección 430EC44

:0042EC42 51                          push ecx
:0042EC43 53                          push ebx
:0042EC44 E857000000          call 0042ECA0

Donde recibe como parametros el mensaje de 512bits y las variables inicializadas. Todo el tratamiento consiste en realizar 80 funciones no lineales obteniendo un resultado de 160 bits que se suma a los valores iniciales que hemos visto antes. 

Ahora ya tenemos claramente identificado el algoritmo SHA-1 dentro de la proteccion. Es bueno realizar pruebas con algoritmos implementados y comparar resultados con mensajes comunes, ya que nunca se sabe si el programador a modificado alguna variable u operación dentro del algoritmo, lo cual no deberia extrañarnos.

Para implementar el keygen deberemos de conseguir una implementacion del algoritmo, o por el contrario se la puede hacer uno mismo, pero sinceramente es una matada.....

:0043A7AE 8B442430                mov eax, dword ptr [esp+30]    ; Primeros 4 bytes
:0043A7B2 8B4C2434                mov ecx, dword ptr [esp+34]    ; Siguientes 4 bytes
:0043A7B6 33D2                        xor edx, edx
:0043A7B8 89442424                 mov dword ptr [esp+24], eax    ; Copia de los primeros 4 bytes
:0043A7BC 89542444                mov dword ptr [esp+44], edx
:0043A7C0 894C2428                mov dword ptr [esp+28], ecx    ; Copia de los siguientes 4 bytes

Lo que se ha obtenido son 160 bits, que seran utilizados mas adelante. Aunque de principio los 8 primeros bytes se guardan en diferentes zonas de memoria.

:0043A7D6 50                            push eax
:0043A7D7 81C1A4110000      add ecx, 000011A4
:0043A7DD 89542454               mov dword ptr [esp+54], edx
:0043A7E1 6A10                       push 00000010
:0043A7E3 51                            push ecx
:0043A7E4 89542460                mov dword ptr [esp+60], edx
:0043A7E8 E8E367FFFF          call 00430FD0

Aqui se vuelve a utilizar el algoritmo SHA-1 donde se encriptan 16 bytes constantes y se obtiene un resultado de 160 bits tambien constante (lògicamente). Esto último no se por que el autor lo haría, supongo que como una medida para despistar.

Mensaje:     E4 05 1E 00 E5 F0 BC A5 4A 71 01 DA 37 21 A4 7D
Digest:        BC DA 16 7E 85 33 39 54 B0 E1 C8 5A AA 07 F0 53 18 F7 CF 31

A continuación (43A7FC) se pasa como parametros el resultado obtenido de SHA-1, su longitud, y una dirección. Aparantemente no ocurre nada, pero sigamos ejecutando instrucciones. Inmediatamente despues nos encontramos con una función que recibe hasta 6 parámetros. Pero sigamos ejecutando instrucciones.

:0043A7F8 52                                  push edx    ; Puntero resultado digest
:0043A7F9 6A14                             push 00000014    ; Longitud digest
:0043A7FB 50                                 push eax    ; Puntero desconocido
:0043A7FC E8AFDAFCFF            call 004082B0
:0043A801 8D4C243C                   lea ecx, dword ptr [esp+3C]
:0043A805 6A00                            push 00000000
:0043A807 8D9424DC000000       lea edx, dword ptr [esp+000000DC]
:0043A80E 51                                 push ecx    ; Puntero a los 8 primeros bytes del digest del serial
:0043A80F 52                                 push edx    ; Puntero desconocido
:0043A810 8D8424C0000000        lea eax, dword ptr [esp+000000C0]
:0043A817 6A20                            push 00000020
:0043A819 8D8C24A0000000       lea ecx, dword ptr [esp+000000A0]
:0043A820 50                                 push eax    ; Puntero buffer vacio
:0043A821 51                                 push ecx    ; Puntero a la ultima transformacion del serial
:0043A822 E8B9CCFCFF             call 004074E0

Tras la ejecución de la función de la dirección 43A822 se obtiene un resultado sobre el buffer vacio. Pero si continuamos un poco mas adelante vemos como se realizan unas series de comparaciones que prometen ser muy interesantes.

:0043A82A 8B84240C110000          mov eax, dword ptr [esp+0000110C]
:0043A831 85C0                               test eax, eax
:0043A833 7425                                je 0043A85A

........

:0043A85A B908000000                  mov ecx, 00000008           ; Se comparan 8 dwords
:0043A85F 8D7C244C                     lea edi, dword ptr [esp+4C]   ; Puntero al nombre de usuario con el número de la
                                                                                                        ; maquina superpuesto
:0043A863 8DB42490000000          lea esi, dword ptr [esp+00000090]    ; Resultado de la llamada en 43A882
:0043A86A 33D2                              xor edx, edx
:0043A86C F3                                  repz
:0043A86D A7                                 cmpsd
:0043A86E 7427                              je 0043A897

Se deduce que para que el registro sea valido los 64 bytes deben ser iguales. Ahora ya tenemos mas o menos claro el objetivo, vamos a adentrarnos en las funciones de las direcciones 43A7FC y 43A882.

Empezando con la funcion de la dirección 43A7FC, recibe tres parametros, lo que podría ser un buffer, el resultado de la segunda pasada del algoritmo SHA-1, que como recordaremos se trataba de una serie de bytes constantes. Y por ultimo un numero 0x14, que podria ser la longitud de esta ultima que he comentado. Si nos metemos dentro, lo primero que nos encontramos es una copia de datos desde una dirección de la seccion .data (los cuales podemos suponer que seran constantes) a la direccion que ha recibido como parámetro. El número total de dwords que copia es 0x412.

:004082B3 B912040000              mov ecx, 00000412    ; Número de dwords a copiar
:004082B8 53                              push ebx
:004082B9 8B5C2410                 mov ebx, dword ptr [esp+10]    ; Puntero a buffer
:004082BD 56                             push esi
:004082BE 57                              push edi
:004082BF BE60045100             mov esi, 00510460    ; Origen de los datos
:004082C4 8BFB                        mov edi, ebx
:004082C6 F3                             repz
:004082C7 A5                             movsd

Inmediatamente despues se hace toda una serie de operaciones que modifican toda la tabla utilizando los valores del resultado de la segunda pasada del algoritmo SHA-1 de longitud 20 bytes.

Si miramos detenidamente el contenido de la tabla inicial podemos identificar los valores como los P-arrays iniciales y los S-Boxes iniciales del algoritmo de encriptación Blowfish. Esto último se puede verificar examinando la documentación del algoritmo en cuestión. Ahora ya tenemos una posible idea del algoritmo que usa. Se pueden verificar todas las operaciones, bien comparandolas con alguna implementación valida (podemos suponer que esto sería la inicialización del algoritmo), o sino comparando el resultado de las dos inicializaciones. Si aceptamos el hecho de que se trata de un algoritmo Blowfish, deducimos que el resultado de la segunda pasada del algoritmo SHA-1 es la llave de encriptacion, la cual es constante, como es logico.

Lo que se consigue aqui es una expansión de la llave (sub-llaves) hasta un total de 4168 bytes, con 18x4 bytes para los P-arrays y 4x4x256 bytes para los S-Boxes. Los pasos a seguir a la hora crear las sub-llaves para aquel que desea verificarlo serían los siguientes:

  • Inicializar los P-arrays y los S-Boxes con los valores fijos de inicialización.
  • Realizar la operación XOR con P1 (32 bits) y el primer dword de la llave, luego entre P2 y los siguientes 32 bits de la llave, hasta P18. Cuando los bits de la llave se acaben, volver a repetirlo ciclicamente.
  • Encriptar un plaintext de valor 0 con blowfish utilizando las sub-llaves anteriormente deducidas.
  • Reemplazar P1 y P2 por el ciphertext obtenido.
  • Se vuelve a encriptar el resultado de la anterior con las sub-llaves modificadas.
  • Se vuelven a reemplazar P3 y P4, volviendose a repetir el ciclo hasta modificar los P-arrays y las S-Boxes.
El algoritmo de encriptación Blowfish, tambien denominado como block cipher, encripta en bloques de 64 bits, con una llave de tamaño variable de hasta 448 bits. Se trata de un algoritmo simétrico debido a que la llave de encriptación sirve tanto para encriptar como para desencriptar.

Vamos ahora con la segunda función, que teniendo en cuenta la lista de parametros que recibe debe de tratarse de la encriptación o desencriptación del número de serie modificado, al cual le asigna una longitud de 0x20 bytes.

Podemos ver como le da la vuelta, por decirlo de alguna manera, a los dwords obtenidos de los 8 primeros bytes del resultado del algoritmo SHA-1 del serial, y los guarda en otras variable.

........
:0040778E 89442410                 mov dword ptr [esp+10], eax    ; Primeros 4 bytes
........
:004077BB 894C2438                mov dword ptr [esp+38], ecx    ; Siguientes 4 bytes
........
:0040783A 52                      push edx
:0040783B 50                      push eax
:0040783C 46                      inc esi
:0040783D 896C2424         mov dword ptr [esp+24], ebp    ; Primetos 4 bytes a encriptar/desencriptar
:00407841 895C2428          mov dword ptr [esp+28], ebx    ; Siguientes 4 bytes a encriptar/desencriptar
:00407845 E8E6010000      call 00407A30

Lo mismo se hace con los 8 primeros bytes del array a encriptar/desencriptar y se guardan en los registros EBP y EBX. A continuación se llama a una función. Hay que tratar de averiguar si se trata de una encriptación o una desencriptación.

Para ello primero vamos intentar entender dichos algoritmos. Como he dicho antes, la operación se realiza en bloques de 64 bits, dando un resultado tambien de 64 bits. Ambas operaciones (encriptación/desencriptación) consisten en un bucle de 16 repeticiones, como indica el siguiente diagrama de bloques:

                                 

                                                        Encriptación

Los parametros P se refieren a los P-arrays que antes se han obtenido a partir de la llave, asi como los S-Box son las S-Boxes obtenidas también antes. Se trata de realizar 16 iteraciones con los 64 bits, y en cada iteración se realiza la funcion F descrita en la parte derecha, donde la entrada de 32 bits se divide en 4 bloques de 8 bits, que representan el indice de entrada a las tablas de las S-Boxes, un byte para cada tabla, es decir, cuatro tablas de 256 entradas cada una.
El byte de menor peso entra en la tabla S-Box4 y el de mayor peso en la tabla S-Box1. La unica difenrencia entre el algoritmo de encriptacion y desencriptacion esta en que en la desencriptación los parametros P se empiezan a utilizar desde P18 hasta P1, que es justo al reves que en la encriptacion.

Sin mas, ya podemos empezar el algoritmo para ver de que se trata. La primera llamada al algoritmo estaría aqui.

:00407E7E 8B5044                     mov edx, dword ptr [eax+44]    ; Coge P18 de la tabla de P-arrays
:00407E81 33CA                        xor ecx, edx    ; Operación XOR entre P18 y parte baja del dato a desencriptar
:00407E83 33D2                         xor edx, edx
:00407E85 8BD9                        mov ebx, ecx
:00407E87 894C2418                 mov dword ptr [esp+18], ecx    ; Guarda resultado de la operacion
:00407E8B 8A54241A                mov dl, byte ptr [esp+1A]    ; Recoge los bits 16-23 del resultado.
:00407E8F C1EB18                    shr ebx, 18    ; Se queda con los bits 24-32 del resultado.
:00407E92 8B949048040000     mov edx, dword ptr [eax+4*edx+00000448]    ; Entra en la tabla S-Box2
:00407E99 8B6C9848                mov ebp, dword ptr [eax+4*ebx+48]    ; Entra en la tabla S-Box1
:00407E9D 33DB                       xor ebx, ebx
:00407E9F 8ADD                       mov bl, ch    ; Recoge los bits 8-15 del resultado
:00407EA1 03D5                        add edx, ebp    ; Suma resultados de S-Box1 y S-Box2
:00407EA3 8BAC9848080000   mov ebp, dword ptr [eax+4*ebx+00000848]    ; Entra en la tabla S-Box3
:00407EAA 8BD9                       mov ebx, ecx
:00407EAC 81E3FF000000       and ebx, 000000FF    ; Recoge los bits 0-7 del resultado
:00407EB2 33D5                        xor edx, ebp    ; Operacion XOR entre resuiltados S-Box3 y (S-Box1 + S-Box2)
:00407EB4 8BAC98480C0000  mov ebp, dword ptr [eax+4*ebx+00000C48]    ; Entra en la tabla S-Box4
:00407EBB 03D5                       add edx, ebp    ; Obtiene resultado de función F
:00407EBD 8B6840                   mov ebp, dword ptr [eax+40]    ; Coge P17 de la tabla de P-arrays
:00407EC0 33D5                       xor edx, ebp    ; En estas dos operaciones XOR se empieza con la siguiente iteración
:00407EC2 33F2                       xor esi, edx      ; Aqui se realiza el cambio de nibbles.
:00407EC4 33D2                      xor edx, edx
:00407EC6 89742418               mov dword ptr [esp+18], esi
.........

Esto sería el código correspondiente a una de las iteraciones, en concreto a la primera. Y examinando el código llegamos a la conclusion de que se trata de una desencriptacion, entre otras cosas debido a que se empieza por P18. Fijarse como se realiza el cambio de la parte baja por la alta del dato de 64 bits que estan guardados en ESI y ECX.

Antes de seguir quisiera apuntar un detalle acerca de la funcion que reside en la direccion 407A30, la cual se encargaba de desencriptar los datos. Pues si os fijais recibe un tercer parametro que es un cero. Si examinais el codigo vereis que es lo primero que se verifica, debido a que es el flag que determina si se trata de una encriptacion o una desencriptacion.

:00407830 6A00                    push 00000000

Ahora ya tenemos claro que se trata de una desencriptación, veamos que se hace con los resultados. Normalmente este tipo de encriptaciones no se realizan encriptando los bloques de 64 bits independientemente uno del otro. Existen varios modos de funcionamiento:

  • CBC: El ciphertext es obtenido mediante la operacion XOR entre el plaintext del bloque actual y el resultado de ciphertext del bloque anterior, para luego encriptar el resultado.
  • CFB: El ciphertext se obtiene encriptando el ciphertext del bloque anterior y despues realizando la operacion XOR entre el resultado y el plaintext del bloque actual.
Veamos que método utiliza el caso que tenemos entre manos.

:0040783A 52                      push edx
:0040783B 50                      push eax
:0040783C 46                      inc esi
:0040783D 896C2424         mov dword ptr [esp+24], ebp    ; Primeros 32 bit ciphertext
:00407841 895C2428          mov dword ptr [esp+28], ebx    ; Siguientes 32 bit ciphertext
:00407845 E8E6010000      call 00407A30    ; Se desencripta 
:0040784A 8B44241C         mov eax, dword ptr [esp+1C]   ; Primeros 32 bit del resultado SHA-1
:0040784E 8B4C2424          mov ecx, dword ptr [esp+24]    ; Primeros 32 bit desencriptados
:00407852 8B542428           mov edx, dword ptr [esp+28]     ; Siguientes 32 bit desencriptados
:00407856 33C1                   xor eax, ecx    ; Resultado final desencriptacion de 32 bit primeros
:00407858 8B4C2444          mov ecx, dword ptr [esp+44]    ; Siguientes 32 bit del resultado SHA-1
:0040785C 83C40C             add esp, 0000000C
:0040785F 33CA                  xor ecx, edx    ; Resultado final desencriptación de 32 bit siguientes

Luego el resultado final se utiliza para desencriptar el siguiente bloque, y asi hasta el final. Con esta información deberiamos ser capaces de deducir que el modo utilizado es el CBC. Al encriptar/desencriptar el primer bloque no se tiene el ciphertext del bloque anterior, de modo que en este caso el programador ha utilizado los primeros 64 bit del resultado del algoritmo SHA-1 de los tres ultimos bytes del numero de serie modificado.

Y finalmente el resultado de la desencriptación se compara con el nombre de usuario, como se ha visto antes.

De todo esto podemos sacar la longitud que debe tener el número de serie. El array que se desencripta y compara es de 32 bytes. A estos hay que sumarle el byte del checkeo mediante XOR, y los tres bytes que se pasan por SHA-1 para obterner los 8 bytes para el modo CBC de blowfish. Suman un total de 36 bytes, que teniendo en cuenta el algoritmo que pasa de 4 a 3 bytes, tenemos un numero de serie de longitud 36*4/3 = 48 bytes

Ya se ha descrito todo el proceso de verificación del serial, ahora voy dar los pasos que se deben seguir para realizar el keygen que cree los números de serie validos:

  • Generar la cadena de 32 bytes con el nombre de usuario y el número de la máquina superpuesto.
  • Generar aleatoriamente los tres últimos bytes del serial modificado por la función 44A290 para pasarlos por el algoritmo SHA-1 y asi conseguir los 8 bytes para encriptar en modo CBC.
  • Aplicar el algoritmo SHA-1 sobre estos tres bytes.
  • Calcular el 4º byte empezando por el final del serial modificado mediante el algoritmo de la dirección 43A762 sobre el nombre de usuario.
  • Encriptar mediante Blowfish con la llave conocida el nombre de usuario con el número de la maquina superpuesto. El tamaño del plaintext debe de ser de 32 bytes, para ello rellenar en resto con ceros. Antes de pasar por el encriptador y despues de pasar por el hay que recordar que tenemos que dar la vuelta a los dwords, es decir, el byte de menor peso pasa a ser el de mayor y viceversa.
  • Concatenar los 32 bytes resultantes con los otros 4 deducidos al principio.
  • Aplicar la inversa del algoritmo de la direccion 44A290.
  • Aplicar la inversa del algoritmo de la direccion 44A250.
Y estos serían los pasos que habría que dar para conseguir realizar un keygen para esta aplicación. Hasta ahora debería estar claro el poder realizar todos los pasos excepto los dos ultimos, aunque estos poco tienen que ver con la encriptación, solo es cuestión de tener un poco de imaginación.

Segun mi experiencia, el algoritmo mas complicado de los dos es el de la dirección 44A290, el encargado de pasar 4 bytes a 3 bytes. 

:0044A290 53                         push ebx
:0044A291 56                         push esi
:0044A292 8B74240C            mov esi, dword ptr [esp+0C]
:0044A296 57                         push edi
:0044A297 0FBE4E02            movsx ecx, byte ptr [esi+02]    ; Coge el tercer byte
:0044A29B 8A5E03                mov bl, byte ptr [esi+03]    ; Coge el cuarto byte
:0044A29E 8A4601                mov al, byte ptr [esi+01]    ; Coge el segundo byte
:0044A2A1 8BF9                    mov edi, ecx    ; EDI = tercer byte
:0044A2A3 83E13F                and ecx, 0000003F 
:0044A2A6 83E33F                and ebx, 0000003F
:0044A2A9 8AD0                   mov dl, al    ; EDX = segundo byte
:0044A2AB C1E106               shl ecx, 06    ; Desplaza tercer byte 6 bit a la izquierda
:0044A2AE 0BD9                   or ebx, ecx    ; Operacion OR entre cuarto byte y tercero modificado
:0044A2B0 8A0E                    mov cl, byte ptr [esi]    ; Coge el primer byte
:0044A2B2 C0F804                sar al, 04    ; Rotacion del segundo byte 4 bits a la derecha
:0044A2B5 83E23F                and edx, 0000003F
:0044A2B8 2403                     and al, 03 
:0044A2BA C1FF02               sar edi, 02    ; Rotacion del tercer byte 2 bits a la derecha
:0044A2BD C0E102               shl cl, 02
:0044A2C0 C1E204                shl edx, 04
:0044A2C3 83E70F                and edi, 0000000F
:0044A2C6 0AC1                   or al, cl
:0044A2C8 8B4C2414            mov ecx, dword ptr [esp+14]
:0044A2CC 0BD7                   or edx, edi
:0044A2CE 5F                        pop edi
:0044A2CF 5E                        pop esi
:0044A2D0 885902                mov byte ptr [ecx+02], bl    ; Tercer nuevo byte
:0044A2D3 8801                    mov byte ptr [ecx], al    ; Primer nuevo byte
:0044A2D5 885101                mov byte ptr [ecx+01], dl    ; Segundo nuevo byte
:0044A2D8 5B                        pop ebx
:0044A2D9 C20800                ret 0008

Nada mas empezar podemos deducir el valor del primer byte de los cuatro:

    PRIMERO_4 = PRIMERO_3 >> 2  (El "subindice" indica si pertenece al grupo de 3 o 4 bytes)

Esto es debido a que PRIMERO_3 es el resultado de la operación OR entre AL y CL, pero CL proviene de la rotación a la izquierda de PRIMERO_4. Como la operación es una OR podemos suponer que CL=AL, de modo que PRIMERO_4 es la rotación de 2 bits a la derecha de PRIMERO_3.

    AUX = PRIMERO_3 & 0x3
    __asm sal AUX , 4
    AUX = AUX | ((SEGUNDO_3)>>4)
    SEGUNDO_4=AUX

A pesar de que hemos dicho antes que CL=AL, no podemos sin embargo suponer que AL=CL, debido a que AL viene impuesto con la regla de que todos sus bit excepto los tres de menor deben ser cero. Esta condicion hace que la igualdad no se de en los dos sentidos, de modo que a PRIMERO_3 le aplicamos la operacion AND con el valor 0x03.
Una vez hecho esto, realizamos sobre este valor una rotación de 4 bits a la izquierda. Pero todavía no podemos asegurar que este valor corresponda a SEGUNDO_4, debido a que antes de hacer nada con SEGUNDO_4, este se guarda en el registro DL. El cual se desplaza 4 bits a la izquierda y su resultado se asigna a SEGUNDO_3. De modo que para que se den las dos condiciones inversas, tiene que existir una relacion entre SEGUNDO_3 y PRIMERO_3 con SEGUNDO_4.
Esta relación la conseguimos mediante la operación OR entre el resultado de la inversión a partir de SEGUNDO_3 y el de la inversión a partir de PRIMERO_3. Esto se debe a que cuando se hace una rotación en el algoritmo hacia la derecha y hacia la izquierda, en realidad nos quedamos con la parte alta y baja del byte. Al hacer la inversa tenemos que juntar los dos resultados, y eso lo hacemos con la operación OR.

    CUARTO_4 = (TERCERO_3) & 0x3f

El cuarto número sale directamente debido a que no hay operaciones muy complejas por medio.

    AUX = SEGUNDO_3 & 0x0F 
    __asm sal AUX , 2 
    TERCERO_4 = AUX
    AUX = (TERCERO_4) & 0x0C0
    AUX = AUX>>6
    TERCERO_4 = (TERCERO_4) | AUX

Esto es algo mas complicado. Aqui ocurre algo parecido que con el caso de SEGUNDO_4. Empezamos invirtiendo una rama desde SEGUNDO_3 mediante una operación AND y una rotación a la izquierda de 2 bits, de modo que recuperamos los 6 bits de mayor peso. Ahora tenemos que recuperar los dos bit de menor peso, para ello hacemos una AND para coger los dos bits de mayor peso del resultado anterior y hacemos un desplazamiento de 6 bits hacia la derecha. Para juntar los dos resultados utilizamos la operación OR.

Ya tenemos invertido uno de los dos algoritmos, ahora queda el mas facil, el de la direccion 44A250.

:0044A250 8A4C2404                mov cl, byte ptr [esp+04]    ; Recordar que previamente se le ha restado 0x30
:0044A254 32C0                         xor al, al
:0044A256 84C9                         test cl, cl
:0044A258 7C05                         jl 0044A25F
:0044A25A 80F909                    cmp cl, 09
:0044A25D 7E29                         jle 0044A288
:0044A25F 80F910                     cmp cl, 10
:0044A262 7C0D                        jl 0044A271
:0044A264 80F92A                    cmp cl, 2A
:0044A267 7F08                         jg 0044A271
:0044A269 80E906                     sub cl, 06
:0044A26C 8AC1                       mov al, cl
:0044A26E C20400                    ret 0004
:0044A271 80F92F                    cmp cl, 2F
:0044A274 7505                         jne 0044A27B
:0044A276 B025                        mov al, 25
:0044A278 C20400                    ret 0004
:0044A27B 80F931                    cmp cl, 31
:0044A27E 7C0A                       jl 0044A28A
:0044A280 80F94A                   cmp cl, 4A
:0044A283 7F05                        jg 0044A28A
:0044A285 80E90B                    sub cl, 0B
:0044A288 8AC1                       mov al, cl
:0044A28A C20400                   ret 0004

Analizando el código anterior podemos particionar un rango de números de 0 a 0x4A en 6 condiciones:

[0,9] -> No cambia
]9,0x2A] -> Resta 6
{0x2F} -> Fuerza a 0x25
]0x2A,0x31[ -> Fuerza a cero
]0x4A,inf [ -> Fuerza a cero
[0x31,0x4A] -> Resta 0xB

Si invertimos las relaciones de esta tabla llegamos a los siguiente:

[0,9] -> No cambia
[0xA,0x24] -> Suma 6
{0x25} -> Fuerza a 0x2F
[0x26,0x3F] -> Suma 0xB

Al resultado final hay que sumarle el valor 0x30, y ya tenemos el serial definitivo. Un poco largo, verdad? Pues ahora queda lo mas facil, programar el keygen. Para ello debemos conseguir implementaciones de libre distribución ya realizadas de los algoritmos Blowfish y SHA-1, a menos de que se disponga de ganas y tiempo y se desee hacer la propia implementación de los algoritmos. Yo he optado por implementaciones en C, entre otras cosas por que son los más difundidos. Una buena base de datos la podeis encontrar en ftp.funet.fi

Para los que no quieran buscar, las implementaciones que yo he usado las puede coger desde aquí los ficheros para el algoritmo Blowfish y SHA-1.

A continuación voy a poner el listado de las funciones del algoritmo del keygen, ya se que esta un poco chapucero, pero es lo que hay, y ya no tenia demasiadas ganas de mejorarlo.

#include <stdio.h>
#include "blowfish.h"
#include "sha1.h"

char nombre[255]="Mr. YelloW";
unsigned int cod=0x12345;
unsigned char serial[49];

int stime;
long ltime;

void Algoritmo (void);
int Intercambia (int num);
void Algo (unsigned char *ori,unsigned char *dest);

void Algoritmo (void) {

  BLOWFISH_CTX ctx;
  unsigned char key[]={0x0bc,0x0da,0x016,0x07e,0x085,0x033,0x039,0x054,0x0b0,0x0e1,0x0c8,0x05a,0x0aa,0x07,0x0f0,0x053,0x018,0x0f7,0x0cf,0x031};

  SHA1_CTX context;
  unsigned char digest[20], codigo[3];

  unsigned char plaintext[32];
  unsigned int *pti;
  unsigned int xorl,xorr,tmpl,tmpr;
  unsigned char final[36];

  unsigned char *ori;
  unsigned char *dest;
  char chk;
  unsigned int cod;
  int i=0,len;

// Generación numeros aleatorios

  ltime = time (NULL);
  stime = (unsigned) (ltime/(rand()+1));
  srand(stime);

  codigo[0]=0x30+(rand())%10;
  codigo[1]=0x30+(rand())%10;
  codigo[2]=0x30+(rand())%10;

  for(i=0;i<32;i++)
   plaintext[i]=0;

// Nombre de usuario con código máquina superpuesto

  strcpy(plaintext,nombre);
  pti=(unsigned int *)plaintext;
  *pti=cod;

// Algoritmo SHA1

  SHA1Init(&context);
  SHA1Update(&context, codigo, 3);
  SHA1Final(digest, &context);

  pti=(unsigned int *)digest;
  xorl=*pti;
  xorr=*(pti+1);
  xorl=Intercambia(xorl);
  xorr=Intercambia(xorr);

// Algoritmo Blowfish

  Blowfish_Init (&ctx, key, 0x14);
  pti=(unsigned int *)plaintext;

// Rotacion

  *pti=Intercambia(*pti);
  *(pti+1)=Intercambia(*(pti+1));
  *(pti+2)=Intercambia(*(pti+2));
  *(pti+3)=Intercambia(*(pti+3));
  *(pti+4)=Intercambia(*(pti+4));
  *(pti+5)=Intercambia(*(pti+5));
  *(pti+6)=Intercambia(*(pti+6));
  *(pti+7)=Intercambia(*(pti+7));
 
// Encriptación modo CBC

  for(i=0;i<4;i++) {
     tmpl=*pti;
     tmpr=*(pti+1);
     *pti=*pti^xorl;
     *(pti+1)=*(pti+1)^xorr;
    Blowfish_Encrypt(&ctx, pti, pti+1);
     xorl=*pti;
     xorr=*(pti+1);
    *pti=Intercambia(*pti);    // Se vuelven a rotar
    *(pti+1)=Intercambia(*(pti+1));
     pti=pti+2;
  }

// Generación byte chequeo
 
  chk=0x0AA;
  len=strlen(nombre);
  for(i=0;i<len;i++)
    chk=chk^nombre[i];

  for(i=0;i<32;i++)
   final[i]=plaintext[i];

  final[32]=chk;
  final[33]=codigo[0]; 
  final[34]=codigo[1]; 
  final[35]=codigo[2]; 

// Inversa algoritmo 44A290

  ori=final;
  dest=serial;
  for(i=0;i<12;i++) {
     Algo(ori,dest);
     ori=ori+3;
     dest=dest+4;
  }
serial[48]=0;

// Inversa algoritmo 44A250

for(i=0;i<48;i++) {
     len=serial[i];
     if((len>=0)&&(len<=9)) {
      serial[i]=serial[i]+0x30;
      continue;
      }
     if((len>=10)&&(len<=0x24)) {
      serial[i]=serial[i]+0x06;
      serial[i]=serial[i]+0x30;
      continue;
      }
     if((len>=0x26)&&(len<=0x3f)) {
      serial[i]=serial[i]+0x0B;
      serial[i]=serial[i]+0x30;
      continue;
      }
     if(len==0x25) {
      serial[i]=0x2f;
      serial[i]=serial[i]+0x30;
      continue;
     }
    }
}
 

int Intercambia (int num) {
int aux;
aux=(num&0x0ff)<<24;
aux=aux + ((num&0x0ff00)<<8);
aux=aux + ((num&0x0ff0000)>>8);
aux=aux + ((num&0x0ff000000)>>24);
return(aux);
}

void Algo(unsigned char *ori,unsigned char *dest) {
unsigned int edi_,al_;

*dest=*ori >> 2;

al_=(*ori)&0x3;
__asm sal al_,4;
al_=al_|((*(ori+1))>>4);
*(dest+1)=al_;

edi_=*(ori+1)&0x0f;
__asm sal edi_,2;
*(dest+2)=edi_;
edi_=(*(ori+2))&0x0c0;
edi_=edi_>>6;
*(dest+2)=(*(dest+2))|edi_;

*(dest+3)=(*(ori+2))&0x3f;
}

En caso de que alguien este interesado en el tema y desee leer mas acerca de la criptografia, puede pasarse por las siguientes urls donde encontrara información de los diferentes algoritmos, implementaciones y libros.

Cryptography page by James Pate Williams, Jr.
SSH - Tech Corner - Cryptographic Algorithms
Pagina The Egoiste

Estas son algunas de las tantas páginas y quizas no de las mejores dedicadas a la criptografía, pero es un comienzo.

Bueno, pues por el momento esto ha sido todo. Hasta el próximo tutorial!!!

Agur.
 

[ Entrada | Documentos Genéricos | WkT! Web Site ]
[ Todo el ECD | x Tipo de Protección | x Fecha de Publicación | x Orden Alfabético ]
(c) Whiskey Kon Tekila [WkT!] - The Original Spanish Reversers.
Si necesitas contactar con
nosotros , lee esto antes e infórmate de cómo puedes ayudarnos