Tutorial del sencillo pero interesante crackme de ESET
Primero debería haber abierto el crackme con el PEiD o cualquier otro programa, pero confiando en la experiencia lo abrí sin más con el OllyDbg. Bien, parecía programado en MASM32, así que seguro que iba a estar todo bastante claro, además es el lenguaje que normalmente utilizo para mis crackmes y keygens.
Veo que no tiene mensaje de "chico bueno" y en seguida aprecio que lo primero que hace tras pulsar el botón OK es poner un controlador de errores. Supongo el error que busca pero por el momento no le hago caso. Luego lee el nombre introducido (mínimo 4 letras, máximo 19) y entra en un procedimiento 0x401000 que parece la inicialización de un MD5, así que paso de las dos siguientes llamadas y compruebo lo que hizo. Pues tenía razón, veo en el dump el hash MD5 de mi nick "PeterPunk" (empieza por 8D y acaba en 9D). A continuación suma los 16 bytes (más exactamente conjuntos de 8 bits) que componen el hash, y multiplica el resultado por 3. Así que para "PeterPunk" obtengo 0x163E.
Unas imágenes para aclarar esto:
Vemos como el PEiD coincide conmigo tanto en el lenguaje de programación como en el uso del hash MD5.
Esta es la parte de código que acabo de comentar.
Y en ésta apreciamos como en 0x404344 se encuentra el MD5 de "PeterPunk".
Lo siguiente que hace el crackme es leer el serial (número entero con signo). Al número introducido como serial se le suma el resultado del cálculo anterior y se le resta 0x42B8. Luego divide 1 entre el resultado anterior y muestra el mensaje de "Chico malo".
Obviamente y aunque el cracker en cuestión no entienda de excepciones va a suponer que lo único a lo que aspira es a que esa división sea entre 0, y a ver que pasa. Así que probamos
17080 (0x42B8) - 5694 (0x163E) = 11386 (qué número más bonito, acaba en 386). Probamos con la dupla "PeterPunk", "11386" y por arte de magia obtengo un mensaje de "chico bueno".
Otra tanda de imágenes:
Aquí vemos la parte de código que acabo de comentar y apreciamos que no hay comparación posible del serial introducido. Y aparentemente obligatoriedad de mostrar el mensaje de "Chico malo". Por lo que sólo parece posible forzar una división por 0.
Esta es mi dupla típica. "PeterPunk" y "81818181" que irremediablemente siempre me devuelve el mensaje de error.
Si meto el valor que fuerza una división por 0 me muestra un mensajito de que soy bueno.
Por tanto vemos que cualquier persona que comience con el maravilloso mundo de la ingeniería inversa va a poder resolver el crackme, pero igual no se dan cuenta de lo verdaderamente interesante del mismo, el procedimiento programado para el control de excepciones.
Como he comentado con anterioridad en el primer vistazo del crackme reparé en que existía un procedimiento para controlar las posibles excepciones. Está definido en:
00402353 |. BE 62224000 MOV ESI,00402262
00402358 |. 56 PUSH ESI
00402359 |. 64:FF35 00000000 PUSH DWORD PTR FS:[0]
00402360 |. 64:8925 00000000 MOV FS:[0],ESP
y vemos que, por tanto, ese manejador está en 0x402262.
Lo voy a ir despiezando para comentar que es lo que hace cada cosa:
00402265 |. 8B5C24 08 MOV EBX,[ESP+8]
Con lo que logramos que EBX señale a la estructura EXCEPTION_RECORD (
http://msdn.microsoft.com/en-us/library/aa363082(v=VS.85).aspx).
A continuación:
00402269 |. 813B 940000C0 CMP DWORD PTR [EBX],C0000094
0040226F |. 75 43 JNZ SHORT 004022B4
00402271 |. E8 D6FFFFFF CALL 0040224C
Si el código de la excepción es 0xC0000094 (EXCEPTION_INT_DIVIDE_BY_ZERO) llamamos al procedimiento 0x40224C que simplemente cambia el mensaje de "Bad boy!" por "Good boy" por medio de unos xor.
00402276 |. 8B7B 0C MOV EDI,[EBX+C]
00402279 |. 83C7 16 ADD EDI,16
Aquí, lo que hacemos es que EDI apunte a la instrucción 0x40241B (por si no os coinciden las derecciones, va a ser siempre la siguiente a la llamada del MessageBox de "chico bueno", y ¿por qué?, porque [EBX+C] (EXCEPTION_RECORD + 0Ch) apunta a la dirección que produjo el error (la de DIV EBX) que en mi caso se trata de 0x402405, así que al sumarle 0x16 bytes vamos a apuntar siempre a la instrucción siguiente al MessageBox.
0040227C |. 8B4424 10 MOV EAX,[ESP+10]
Con esto logramos que EAX apunte a la estructura CONTEXT que contiene muchísima información (
http://msdn.microsoft.com/en-us/library/ms679284(v=VS.85).aspx).
00402280 |. C700 10000100 MOV DWORD PTR [EAX],10010
00402286 |. 8108 07000100 OR DWORD PTR [EAX],10007
Esto nos cambia las banderas de la estructura (ContextFlags) que controlan la información que guarda la estructura. Particularmente creo que no hace falta ya que por defecto nos va a aparecer con un valor de 0x1003F (información de todo) pero el crackme lo cambia a 0x10017 (CONTEXT_CONTROL or CONTEXT_INTEGER or CONTEXT_EGMENTS or DEBUG_REGISTERS).
0040228C |. 8978 04 MOV [EAX+4],EDI
0040228F |. C740 18 01010000 MOV DWORD PTR [EAX+18],101
Aquí empieza lo bueno. Recordamos que EDI apuntaba a la instrucción siguiente al MessageBox. Bien, pues ahora lo guardamos en [EAX+4] (CONTEXT + 4h) que es el Debug Register #0, y en [EAX+18] (CONTEXT + 18h) que es el Debug Register 7, más conocido como DR7 o Debug Control, guardamos 0x101. Pues lo que acabamos de hacer es poner un BreakPoint eXecution en la dirección a la que apunta EDI. Si bajáis los PDFs de los manuales de desarrolladores de software para arquitecturas Intel 64 e IA-32 de la web de Intel, veréis que de el Debug Register #0 al #3 hacen referencia a las direcciones de los 4 breakpoints que se pueden usar y el Debug Control Register (DR7) controla la activación o desactivación de los mismos así como de sus condiciones. En este caso en este DR7 sólo hemos marcado el bit #0 (bp local activado en el primer breakpoint, que es el DR0) y el #8 (activado bp local exacto, que según Intel no está soportado desde el P6 pero recomiendan su uso por retrocompatibilidad o futura reimplementación).
00402296 |. 83EF 12 SUB EDI,12
00402299 |. 89B8 B8000000 MOV [EAX+B8],EDI
Vale, ahora hacemos que EDI apunte en mi caso a 0x402409 que para todos va a ser tercer parámetro del MessageBox (el título) y lo guardamos en [EAX+B8] (CONTEXT + 0B8h) que es el EIP, bueno exactamente el EIP en el momento que se produjo la excepción, así que si os fijáis, el valor que tiene [EAX+B8] antes de volcarle la nueva es el DIV EBX que produjo la división por 0. ¿Y para qué se cambia este valor? para que cuando se salga de este controlador de errores y el sistema operativo devuelva el control del programa a su rutina normal siga ejecutándose desde aquí. ¡Pero nos comemos el primer parámetro del MessageBox (uType) que controla los botones e iconos del mismo!. Tranquilos, el programador sabía lo que hacía:
0040229F |. 83A8 C4000000 04 SUB DWORD PTR [EAX+C4],4
004022A6 |. 8B90 C4000000 MOV EDX,[EAX+C4]
004022AC |. C702 40000000 MOV DWORD PTR [EDX],40
Le resta 4 a [EAX+C4] (CONTEXT + 0C4h) que es el valor del ESP en el momento de la excepción. O sea, que modificamos el valor de la pila, como si hubiésemos hecho un "push dword" más y en esa dirección de la pila guardamos el valor 0x40 (MB_ICONINFORMATION) ya que lo que estamos es creando el parámetro que nos faltaba para el MessageBox y ahora el programa cuando siga su curso, ya no va a ejecutar el "PUSH 10h" como 4º parámetro del mensaje, que le otorgaría el MB_ICONERROR, sino que ha sido sustituido por un ficticio "PUSH 40h" (decrementando el valor de ESP y copiando ahí directamente el parámetro que queríamos).
Todo esto está muy bien, pero ¿para qué hemos creado un punto de ruptura en la instrucción siguiente al mensaje? Pues porque al llegar a esa instrucción, va a saltar la excepción STATUS_SINGLE_STEP (0x80000004) que también está controlada en el código manejador de excepciones:
004022B4 |> \813B 04000080 CMP DWORD PTR [EBX],80000004
004022BA |. 75 37 JNZ SHORT 004022F3
004022BC |. E8 8BFFFFFF CALL 0040224C
004022C1 |. 8B4424 10 MOV EAX,[ESP+10]
004022C5 |. C700 07000100 MOV DWORD PTR [EAX],10007
004022CB |. 8108 10000100 OR DWORD PTR [EAX],10010
004022D1 |. C740 04 00000000 MOV DWORD PTR [EAX+4],0
004022D8 |. C740 18 00000000 MOV DWORD PTR [EAX+18],0
004022DF |. 8348 18 00 OR DWORD PTR [EAX+18],0
004022E3 |. 8148 18 00040000 OR DWORD PTR [EAX+18],400
O sea, como hemos puesto el breakpoint, cuando el código llega a esa instrucción (tras mostrar el mensaje de "Chico bueno"), nos salta una excepción, y con nuestro controlador de excepciones volvemos al procedimiento 0x402262.Ahí primero va a comprobar, como hemos visto antes, que el error sea 0xC0000094 (EXCEPTION_INT_DIVIDE_BY_ZERO) y como no lo es ya nos salta a la parte de código que acabo de pegar.
Al igual que antes vemos que EAX va a apuntar a la estructura CONTEXT.
Después vuelve a llamar al procedimiento que cambia el mensaje (esta vez el de "Chico bueno" por el de "Chico malo") para que no quede el bueno fijo ya que sino, aunque a continuación metiésemos un serial malo nos mostraría el bueno (eso sí, con el icono de error en el mensaje).
También vuelve a cambiar las banderas (ContextFlags) aunque sigo convencido de que esto no hace falta y esta vez borra el contenido de [EAX+4] (Debug Register #0) y cambia el valor de [EAX+18] (Debug Control o DR7) por 0x400. O sea que elimina el BPX que antes había puesto. Realmente para esto bastaría con poner a 0 el bit #0 del DR7, pero de todas formas lo normal para eliminar los breakpoints es poner a 0 ese DR7. Aquí, el crackme activa el bit #10 (0x400 = 100 0000 0000) pero lo cierto es qué no sé el motivo ya que en el manual de Intel, los bits #10, #11, #12, #14 y #15 de este Debug Control Register no están descritos, lo cual me hace suponer que no tienen ninguna utilidad si bien es cierto que en la figura 16.1 de dicho manual el bit #10 aparece con un 1 mientras que los otros cuatro con 0.
Este es el código del manejador de excepciones que realmente es el corazón del crackme.
Y eso es todo amigos, para cualquier duda u observación dejen un comentario a continuación.
Gracias a todos por leer este "ladrillo" y sobre todo gracias al programador del crackme por tomarse su tiempo en la construcción del manejador de excepciones.
Saludos.