¿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 😉
Un artículo muy interesante, esperaré más del estilo. 😉
P.D.:A mis feeds!
¿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
COmprate una consola, o lee manuales y busca. Las cosas como son.
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.
alguien sabe donde bajar un generador de serie.
Es urgente si alguien me puede ayudar.Gracias
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.
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
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
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.
Pingback: Introducción al cracking en GNU/Linux « txipi:blog
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!
Hola, Gracias por el post!, que en realiad es excelente 🙂