Crack me if you dare!

¿Alguna vez te has preguntado cómo puede haber alguien capaz de hacer un generador de números de serie válidos para un determinado programa protegido? ¿Cómo se las han ingeniado para conseguir que un juego ya no compruebe si hay un disco llave? Muchos internautas usan a diario herramientas programadas por crackers, programadores que disfrutan rompiendo medidas de protección software. ¿Cómo se convierte un programador normal en un cracker?

¿Alguna vez te has preguntado cómo puede haber alguien capaz de hacer un generador de números de serie válidos para un determinado programa protegido? ¿Cómo se las han ingeniado para conseguir que un juego ya no compruebe si hay un disco llave? Muchos internautas usan a diario herramientas programadas por crackers, programadores que disfrutan rompiendo medidas de protección software. ¿Cómo se convierte un programador normal en un cracker?

En mi humilde opinión, un cracker suele responder a un patrón bastante típico: excelente programador, que necesita cada vez retos más y más difíciles de conseguir, experto en el uso de herramientas de auditoria de binarios, depuradores, desensambladores, editores hexadecimales y aplicaciones similares. Cada protección diferente que consiguen romper es un logro. Dudo que les importe siquiera el programa que se está protegiendo. Como prueba de esto, es muy común que crackers veteranos preparen pequeños programas protegidos que denominan “crackme”. Un “crackme” es un programa que no hace nada, salvo sacar un mensaje por pantalla o algún efecto similar, pero con la particularidad de estar protegido con técnicas similares a las empleadas en protecciones comerciales.

Algunos “crackme” son realmente fáciles de romper, solamente basta con cambiar una instrucción para invertir el sentido de la programación. Si por ejemplo tenemos una protección programada de forma tan sencilla como el código de Listado 1, negando la condición del if podríamos cambiar el comportamiento del programa para que dejara pasar todo número de serie inválido (y rechazara los válidos, como efecto colateral).

if(esNumeroSerieCorrecto(serial))
  lanzar_juego();
else
  salir();

Listado 1.

Alguien puede estar pensando: claro, negar una condición en un if es muy sencillo cuando tienes el código fuente, pero una vez que el programa está compilado no es tan sencillo. Es cierto. Si solamente tenemos el código ejecutable, necesitamos un desensamblador para ver el código ensamblador correspondiente a ese código máquina ejecutable y tratar de buscar la comprobación, que estará en un salto condicional como JE (Jump if Equal), JB (Jump if Below) o alguno similar. Una vez encontrado el salto, bastaría con cambiarlo con un editor hexadecimal por su contrario: JNE (Jump if Not Equal) para JE, JAE (Jump if Above or Equal) para JB, etc. No suena imposible, aunque habría que familiarizarse con las herramientas necesarias.

¿Y cómo se programan los generadores de números de serie válidos o keygens? El proceso es algo más laborioso, pero bastante similar. En toda protección por número de serie siempre hay una rutina de código que decide si el número es válido o no. Estudiando todas las comprobaciones que realiza esa función se puede crear un programa que genere números que cumplan todas ellas. Para esta tarea lo más cómodo es ejecutar el programa protegido a través de un depurador que nos permita establecer un punto de parada (breakpoint) en el momento en el que se llame a la función de comprobación para poder seguirla de cerca. Adivinar cuándo se llama no es trivial, aunque la mayoría de protecciones sencillas lo hacen nada más introducir el número de serie. Existen otras protecciones, más avanzadas, que demoran la comprobación, para que sea más difícil saber cuál es el código que valida el número de serie.

Por supuesto estas dos explicaciones son una simplificación del casi siempre insondable mundo del cracking, pero nos sirven para poder continuar. En el resto del artículo trataremos de romper la protección de un “crackme” muy sencillo programado en C. Si alguien tiene muchas ganas de saber antes de comenzar con el análisis qué es lo que hace, su código fuente está en el Listado 13, pero recomiendo no mirarlo hasta el final para entender mejor nuestro proceso de averiguación.

Vamos a ello. Como sabemos que no es un programa malicioso, podemos aventurarnos a ejecutarlo sin más para ver qué hace (si tuviéramos la duda de que estuviera infectado o troyanizado, podríamos hacer un análisis sin ejecución, utilizando un desensamblador).

$ ./crackme

Enter REGISTER code: 1234-5678-ABCD

UNREGISTERED!

Listado 2.

De acuerdo. Parece que pide un código de activación nada más comenzar y sale mostrando el mensaje “UNREGISTERED!” si no es de su agrado. Veamos si realiza alguna llamada a las funciones de la API del Sistema Operativo:

$ ltrace ./crackme
__libc_start_main(0x80483c4, 1, 0xafe779b4, 0x8048490, 0x8048500 <unfinished ...>
printf("\nEnter REGISTER code: "
)                                                                = 22
scanf(0x80485df, 0xafe77925, 0xafe77908, 0x804834e, 0xa7f50360Enter REGISTER code: 1234-5678-ABCD
)                                  = 1
printf("\nUNREGISTERED!\n"
UNREGISTERED!
)                                                                      = 15
exit(-1 <unfinished ...>
+ exited (status 255) +

Listado 3.

Bien, aquí ya tenemos un poco más de información. Como vemos, el programa comienza mostrando mediante printf() el mensaje que solicita un código válido y lee ese código con scanf(). Suponemos que después hará unas comprobaciones sin la ayuda de ninguna función de la API y finalmente decide que el código no es válido y sale con error usando exit(-1).

Con esta información ya podemos animarnos a lanzar el GDB (GNU Debugger), el depurador estándar en GNU/Linux. Lo primero que vamos a intentar es poner un breakpoint en la función main(). Si no existiera esta función, habría que mirar en la cabecera del ejecutable cuál será la primera instrucción que se ejecutará, y poner un breakpoint ahí. Cuando ejecutemos el programa, la ejecución se detendrá al llegar al breakpoint.

$ gdb ./crackme
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) break main
Breakpoint 1 at 0x80483ca
(gdb) run
Starting program: /home/txipi/cracking/crackme

Breakpoint 1, 0x080483ca in main ()

Listado 4.

El programa está ejecutándose y nosotros estamos dentro de la función main(). Vamos a desensamblar el código de esta función, para ver si entendemos algo.

(gdb) set disassembly-flavor intel
(gdb) disassemble
Dump of assembler code for function main:
0x080483c4 <main+0>:    push   ebp
0x080483c5 <main+1>:    mov    ebp,esp
0x080483c7 <main+3>:    sub    esp,0x38
0x080483ca <main+6>:    and    esp,0xfffffff0
0x080483cd <main+9>:    mov    eax,0x0
0x080483d2 <main+14>:   add    eax,0xf
0x080483d5 <main+17>:   add    eax,0xf
0x080483d8 <main+20>:   shr    eax,0x4
0x080483db <main+23>:   shl    eax,0x4
0x080483de <main+26>:   sub    esp,eax
0x080483e0 <main+28>:   mov    DWORD PTR esp,0x80485c8
0x080483e7 <main+35>:   call   0x80482e8 <printf@plt>
0x080483ec <main+40>:   lea    eax,ebp-19
0x080483ef <main+43>:   mov    DWORD PTR esp+4,eax
0x080483f3 <main+47>:   mov    DWORD PTR esp,0x80485df
0x080483fa <main+54>:   call   0x80482c8 <scanf@plt>
0x080483ff <main+59>:   movzx  edx,BYTE PTR ebp-6
0x08048403 <main+63>:   movzx  eax,BYTE PTR ebp-19
0x08048407 <main+67>:   cmp    dl,al
0x08048409 <main+69>:   jne    0x8048467 <main+163>
0x0804840b <main+71>:   movzx  edx,BYTE PTR ebp-7
0x0804840f <main+75>:   movzx  eax,BYTE PTR ebp-18
0x08048413 <main+79>:   cmp    dl,al
0x08048415 <main+81>:   jne    0x8048467 <main+163>
0x08048417 <main+83>:   movzx  edx,BYTE PTR ebp-8
0x0804841b <main+87>:   movzx  eax,BYTE PTR ebp-17
0x0804841f <main+91>:   cmp    dl,al
0x08048421 <main+93>:   jne    0x8048467 <main+163>
0x08048423 <main+95>:   movzx  edx,BYTE PTR ebp-9
0x08048427 <main+99>:   movzx  eax,BYTE PTR ebp-16
0x0804842b <main+103>:  cmp    dl,al
0x0804842d <main+105>:  jne    0x8048467 <main+163>
0x0804842f <main+107>:  movzx  eax,BYTE PTR ebp-19
0x08048433 <main+111>:  movsx  edx,al
0x08048436 <main+114>:  movzx  eax,BYTE PTR ebp-18
0x0804843a <main+118>:  movsx  eax,al
0x0804843d <main+121>:  add    edx,eax
0x0804843f <main+123>:  movzx  eax,BYTE PTR ebp-17
0x08048443 <main+127>:  movsx  eax,al
0x08048446 <main+130>:  add    edx,eax
0x08048448 <main+132>:  movzx  eax,BYTE PTR ebp-16
0x0804844c <main+136>:  movsx  eax,al
0x0804844f <main+139>:  lea    eax,edx+eax
0x08048452 <main+142>:  cmp    eax,0xc8
0x08048457 <main+147>:  jle    0x8048467 <main+163>
0x08048459 <main+149>:  mov    DWORD PTR esp,0x80485e2
0x08048460 <main+156>:  call   0x80482e8 <printf@plt>
0x08048465 <main+161>:  jmp    0x804847f <main+187>
0x08048467 <main+163>:  mov    DWORD PTR esp,0x80485f0
0x0804846e <main+170>:  call   0x80482e8 <printf@plt>
0x08048473 <main+175>:  mov    DWORD PTR esp,0xffffffff
-Type <return> to continue, or q <return> to quit-q

Listado 5.

Podemos ver que en la dirección 0x080483fa se hace una llamada a scanf(), por lo que podemos intuir que ahí es donde se lee el código que introducimos por teclado. Vamos a poner un breakpoint en la siguiente instrucción. Una vez que paremos ahí, intentaremos encontrar la rutina que valide el código introducido. Por de pronto vemos un cúmulo de comparaciones (cmp) entre valores cercanos a lo que contiene el registro EBP, y saltos a <main+163> si esos valores no son iguales.

(gdb)  break *0x080483ff
Breakpoint 2 at 0x80483ff
(gdb) cont
Continuing.

Enter REGISTER code: 1234-5678-9ABC

Breakpoint 2, 0x080483ff in main ()

Listado 6.

Intentemos averiguar por qué se hacen tantas comparaciones con valores cercanos a la dirección contenida en EBP. Vamos a consultar ese valor y a mostrar un volcado de la memoria apuntada por EBP – 20 a ver si encontramos algo interesante.

(gdb) info registers ebp
ebp            0xafef10c8       0xafef10c8
(gdb) x/20x 0xafef10b4
0xafef10b4:     0x33323100      0x36352d34      0x392d3837      0x00434241
0xafef10c4:     0xa7ff2cc0      0xafef1118      0xa7ea6eb0      0x00000001
0xafef10d4:     0xafef1144      0xafef114c      0x00000001      0xa7fc5ff4
0xafef10e4:     0x00000000      0xa7ff2cc0      0xafef1118      0xafef10d0
0xafef10f4:     0xa7ea6e75      0x00000000      0x00000000      0x00000000

Listado 7.

Los ojos más despiertos quizá hayan vislumbrado algo en ese volcado. Hay muchos valores cercanos a 30, que corresponden a los números decimales en la tabla ASCII. Volquemos el contenido de esa dirección de memoria en busca de strings.

(gdb) x/1s 0xafef10b4
0xafef10b4:      ""
(gdb) x/2s 0xafef10b4
0xafef10b4:      ""
0xafef10b5:      "1234-5678-9ABC"

Listado 8.

Eureka, en 0xafef10b5 se almacena el código que hemos introducido a traves de scanf(). Por lo tanto, esas comprobaciones tienen muchas posibilidades de ser las que validen el código y podamos deducir qué se necesita para satisfacerlas. En la primera de ellas se compara EBP – 6 (0xafef10c2) con EBP – 19 (0xafef10b5). Veamos qué contienen.

(gdb) x/1s 0xafef10c2
0xafef10c2:      "C"
(gdb) x/1s 0xafef10b5
0xafef10b5:      "1234-5678-9ABC"

Listado 9.

La primera dirección corresponde con el último carácter introducido en el código, la segunda dirección corresponde con el primer carácter introducido. Por lo tanto, el código debe comenzar con el mismo número con el que termina. Si seguimos fijándonos en las otras tres siguientes comprobaciones veremos que los 4 primeros números del código tienen que coincidir en órden inverso con los 4 últimos.

(gdb) x/1s 0xafef10c1
0xafef10c1:      "BC"
(gdb) x/1s 0xafef10b6
0xafef10b6:      "234-5678-9ABC"
(gdb) x/1s 0xafef10c0
0xafef10c0:      "ABC"
(gdb) x/1s 0xafef10b7
0xafef10b7:      "34-5678-9ABC"
(gdb) x/1s 0xafef10bf
0xafef10bf:      "9ABC"
(gdb) x/1s 0xafef10b8
0xafef10b8:      "4-5678-9ABC"

Listado 10.

A partir de la dirección 0x0804842f (<main+107>), la comprobación es diferente: se suma el contenido de EBP – 19 con EBP – 18 y todo ello con EBP – 17 y EBP – 16 y se comprueba si la suma es menor o igual que 0xC8 (200 en decimal). Necesitamos entonces que los 4 primeros caracteres del código sumen en ASCII más que 200.

0x0804842f <main+107>:  movzx  eax,BYTE PTR ebp-19
0x08048433 <main+111>:  movsx  edx,al
0x08048436 <main+114>:  movzx  eax,BYTE PTR ebp-18
0x0804843a <main+118>:  movsx  eax,al
0x0804843d <main+121>:  add    edx,eax
0x0804843f <main+123>:  movzx  eax,BYTE PTR ebp-17
0x08048443 <main+127>:  movsx  eax,al
0x08048446 <main+130>:  add    edx,eax
0x08048448 <main+132>:  movzx  eax,BYTE PTR ebp-16
0x0804844c <main+136>:  movsx  eax,al
0x0804844f <main+139>:  lea    eax,edx+eax
0x08048452 <main+142>:  cmp    eax,0xc8
0x08048457 <main+147>:  jle    0x8048467 <main+163>

Listado 11.

En resumen, necesitamos un serial que tenga los 4 últimos números iguales que los 4 primeros pero en orden inverso y que sus primeros 4 números sumados den más que 200 en decimal. Probemos con “9876-1111-6789” a ver si hay suerte.

$ ./crackme

Enter REGISTER code: 9876-1111-6789

REGISTERED!

Listado 12.

¡Bingo! Hemos conseguido entender cómo se valida el código, así que ya podemos hacer un programa que genere números que cumplan esas dos condiciones y tendremos el keygen para este crackme.

Para finalizar, el código fuente del crackme, por si queréis reproducir esto en vuestras máquinas.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int i;
        char code[15];

        printf("\nEnter REGISTER code: ");
        scanf("%s", &code);

        /* code deberia tener la forma ABCD-EFGH-IJKL */

        if (
             ( code[13] == code[0] && code[12] == code[1] && code[11] == code[2] && code[10] == code[3] )
          && ( code[0] + code[1] + code[2] + code[3] > 200 )
           )
        {
                printf("\nREGISTERED!\n");
        } else
        {
                printf("\nUNREGISTERED!\n");
                exit(-1);
        }
}

Listado 13.

Espero que os haya resultado lo suficientemente interesante como para animaros a probar retos similares a este como los hack-it que tenemos colgados en la página web del e-ghost: http://e-ghost.deusto.es/phpwiki/index.php/GhostHackIt. En Internet hay crackmes verdaderamente divertidos, de dificultad creciente y tutoriales que explican cómo intentar romperlos. Es como hacer Sudokus, pero en ensamblador. Tremendamente adictivo, tened cuidado 😉

12 pensamientos en “Crack me if you dare!

  1. silo

    ¿Como puedo saber la contraseña de un amigo o familiar que esta en http://www.telefonica.net de España, tengo todo la IP las DNS el numero de telefono por donde se conecta y el puerto que usa, pero quisiera como puedo octeñer la contraseña y meterme, no para hacer daño, solo para simple curiosidad, y prebenir caosas raras que veo en esta persona. gracias si me lo pueden aclarar

    Responder
  2. Jhon

    Exelente articulo, yo no se nada del tema y con las limitaciones obvias pude entender a groso modo la tecnica, creo que me va a costar mucho aprender pues no soy computista ni nada que se le parezca el hecho es que necesito el crack para varios programas pues accidentalmente borre mis fotos ( muchas ) y para los programas que son efectivos no he podido encontrar un crack que funcione. Me gustaria recibir informacion sobre el tema.

    Responder
  3. Xen

    Necesito un generador de claves o un serial del programa EBP Plan de Negocio 2007 v 3.1 ya que he estado buscando por todas partes y no he encontrado nada. Agradeceria enormemente la ayuda porque me he pasado mucho tiempo buscandolo. Aparte de serial me pide un numero de licencia.

    Un abrazo y mil gracias.

    Responder
  4. maros

    hola gracias por la informacion ese articulo es muy bueno quisiera saber si tu me puedes enseñar algo mas sobre cracks te lo agradeceria mucho por que ami me gusta la computacion y se algo de informatica quiero saber mas sobre ese tema o talves de hacks si me das un poco mas de informacion te lo agraceceria de por vida

    Responder
  5. marrtin

    quisiera saber como le quite el tiempo de uso a un programa como le hago si tienen un crack me lo podian facilitar no se nada de computacion o como obtener los numeros de series de los programas

    Responder
  6. Roberto

    Me facina lo q hacen ustds los programadores e ingenieros en computacion, la verdad es que no le entiendo a nada de lo que has explicado, ya q mi carrera es muy distinta a la de ustedes, pero estoy seguro que todo eso q has explicado es un dolor de huevo asi como dicen en mi pais, y la verdad es que los considero tosos unos Caballos (positivamente) yo quise estudiar esta onda, pero la verdad me fui por otro rumbo, me gusta eso que hacen, ademas de programar y hackear y toda esa onda, ya que gracias a ustedes la vida es mas sencilla.

    Responder
  7. Pingback: Introducción al cracking en GNU/Linux « txipi:blog

  8. crimsomshadow

    Un post tan cojonudo y unos comentarios como los de arriba desmerecen 😉

    Esto es como todo, cuando nunca te has metido en ello y tal todo parece ciencia ficción, pero una vez visto un poco el asunto, las cosas se van asentando, aunque siempre teniendo claro que hablamos de crackme`s sencillos!

    Yo la verdad es que nunca me ha dado por meterme en estas cosas, pero tras leer esto y lo de los HackIt en DiarioLinux, me ha picado el gusanillo. Aunque me tendría que poner bastante más al dia en tema de seguridad y cifrado, que ahi si que voy chungo!

    Un saludo txipi!

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *