Archive

Posts Tagged ‘Desafío’

Solucionando un CrackME de Hispasec!

January 20th, 2009 noukeys 1 comment

  En el presente escrito se abordará la resolución del CrackME de hispasec, llamado a partir de ahora crackme o reto, detallando las siguientes partes.

  •  ¿Qué se pretende?
  •  Encontrando la zona de interés.
  •  Análisis del algoritmo.
  •  Generación de un fichero válido.
  •  Fallos de diseño.
  •  Despedida y cierre.

  El crackme y el fichero de licencia lo podeis encontrar en el siguiente enlace.

1.- ¿Qué se pretende?

  El objetivo de este reto, es generar un fichero de licencia válido y explicar los fallos de diseño encontrados en los hilos. Quiero comentar que en este documento encontraremos lo que a mi juicio son fallos de diseño, no tienen porque coincidir con los criterios de los autores del reto. Para la resolución de este reto se ha utilizado un depurador y un editor hexadecimal, en mi caso he optado por Ollydbg y por Hex Workshop.    

2.- Encontrando la zona de interés.

  Para llegar a la zona que nos interesa analizar, y sabiendo de antemano que necesitamos un fichero de licencia, tenemos varia maneras de abordar nuestro cometido; explicaré un par de ellas.

  •  Localizando la función de chequeo mediante referencias a cadenas.

Nada más cargar nuestro reto en el depurador, parados en el oep, buscamos referencias a cadenas con search for > All referenced text strings. Obtenemos esto.

img1nk

Podemos ver las cadenas “crackem.lic”, “Good job!” y “Keep trying”, es evidente que nuestro objetivo es “Good job”. Seguimos la cadena y caemos en este punto. img2nk  

En función de un par de comparaciones carga una de las cadenas. Seguimos el código hacia arriba, para ver donde se inicia esta función.

img3nk

Aquí encontramos el inicio de la función, en la dirección 004010B0, vamos a fijarnos un momento que llama a la API CreateFileA, pero el atributo Mode es OPEN_EXISTING. Esto es importante para el siguiente punto.

  •  Localizando la función de chequeo utilizando la API de Windows.

 

Ahora vamos a utilizar la API de Windows para llegar al mismo punto. Nada más cargar nuestro reto en el depurador, parados en el oep, buscamos referencias a APIs cargadas con search for > Name (label) in current module. Obtenemos esto.

img4nk

 

Vamos a poner un BP en cada API específica de ficheros.

 

 

KERNEL32.CreateFileA

KERNEL32.GetFileSize

KERNEL32.GetFileType

KERNEL32.ReadFile

KERNEL32.WriteFile

 

Ejecutamos varias veces y vemos unas cuantas paradas antes de llegar a este BP en la dirección 004010CB, tal como vemos en esta imagen.

img5nk

Seguimos el código hacia arriba, para ver donde se inicia esta función. Encontramos el inicio en la dirección 004010B0.

 

3.- Análisis del algoritmo.

 

Como ya hemos localizado la función de chequeo, ahora vamos a centrarnos en analizarla.

La función comprueba que existe el fichero “crackme.lic” , mira el tamaño del fichero, crea un buffer para guardar los datos, vuelca el contenido del fichero en el buffer creado, lanza los hilos que comprueban si los datos que hemos volcado son válidos y analiza el resultado de los hilos para decidir que cadena muestra. Esto corresponde al siguiente esquema.

img6nk

Bueno, lo realmente interesante esta en los hilos de ejecución, así que vamos a ver que es lo que hacen los mismos. Vamos a fijarnos en el argumento Thread Function, vemos que tiene el parámetro 00401000, este es el inicio de la función que ejecuta el hilo.

 

img7nk

Los bytes del fichero llave deben ser menores de 0Ah, es decir menores de 10d, si el hilo encuentra un byte igual a FFh, termina el análisis. Si nos fijamos, la función tiene dos salidas, una en la que realiza un “XOR EAX,EAX” o lo que es lo mismo, poner EAX a 0; y otra que realiza un “MOV EAX,1″ o lo que es lo mismo, poner EAX a 1.

¿Cómo conseguimos poner EAX a 1?, si solucionamos esta pregunta, tenemos solucionado el problema; para conseguir la ejecución de ese salto, debemos lograr que el resultado de AL tras la ejecución del código sea igual a 09h. Veamos más claramente la ejecución.

img8nk

El proceso le el primer byte del fichero, carga otro byte que utiliza para los cálculos, vamos a llamarlo byte auxiliar o aux; este byte, al principio va a estar a 0. Tras esto realiza una operación con este byte, aux = aux + 4*aux, esto lo guarda en edx. Carga en bl el dato leído del fichero y este dato junto con aux, lo utiliza para acceder a una tabla de bytes; el calculo es el siguiente desplazamiento = dato + aux * 2.

En nuestro caso necesitamos un desplazamiento de 48h, si no conseguimos cargar el 09h, el byte apuntado se convierte en el nuevo aux. Y esto se repite hasta encontrar FFh.

4.- Generación de un fichero válido.

 

Bueno, conociendo el algoritmo, es fácil generar un fichero válido, solo necesitamos conocer la tabla de bytes que hemos comentado anteriormente. Es la siguiente (las cabeceras de la tabla están representadas en decimal.).

1 .- Bytes hexadecimales.

 

0

1

2

3

4

5

6

7

8

9

0

05

01

03

02

01

02

04

03

05

04

10

00

00

00

00

00

02

00

00

00

00

20

02

03

03

00

00

01

01

04

04

05

30

02

03

03

01

06

04

04

05

05

00

40

00

00

05

01

02

03

04

08

08

05

50

05

05

05

04

04

05

05

00

00

00

60

06

06

06

06

06

07

08

08

06

06

70

07

07

09

08

07

07

07

08

08

08

80

08

07

07

08

08

08

08

07

07

  07

 

Necesitamos conseguir un desplazamiento de 72d bytes. Para ello vamos a realizar las siguientes operaciones.

Aux = 0 (la primera pasada del algoritmo).

Data = 06 (Primer byte del fichero).

0 + 0*4 = 0.

6 + 0*2 = 6.

Cargamos el byte que hay con desplazamiento 6; el 04.

No es 09, por tanto lo usamos como nuevo aux.

 

Aux = 04.

Data = 07 (Segundo byte del fichero).

4 + 4*4 = 20.

7 + 20*2 = 47.

Cargamos el byte que hay con desplazamiento 46; el 08.

No es 09, por lo tanto lo usamos como nuevo aux.

 

Aux = 08.

Data = 01 (Tercer byte del fichero).

8 + 8*4 = 40.

1 + 40*2 = 81

Cargamos el byte que hay con desplazamiento 81; el 07;

No es 09, por lo tanto lo usamos como nuevo aux.

 

Aux = 07.

Data = 02 (Cuarto byte del fichero).

7 + 7*4 = 35.

02 + 35*2 = 72.

Cargamos el byte que hay con desplazamiento 72; el 09.

Es 09, por lo tanto terminamos en el return que nos interesa.

 

Si nos fijamos, el segundo hilo, utiliza exactamente la segunda función, además, la tabla de bytes no es modificada, por tanto, sólo tenemos que duplicar la cadena de bytes obtenida anteriormente y replicarla. Al final, añadimos el byte de fin que utiliza el programa, el FFh.

La cadena de bytes que necesitamos escribir para generar un fichero válido es la siguiente: “0607010206070102FF”. Esto lo podemos hacer con nuestro editor hexadecimal preferido. Una vez realizado esto, tenemos solucionado este reto.

 

5.- Fallos de diseño.

 

A continuación, comento lo que a mi juicio son fallos de diseño en el algoritmo.

  •  Existe un fragmento de código, que no es necesario para la resolución del reto,

00401070   .  68 CE070000   push 7CE

00401075   .  FFD6                call esi                                 ;  kernel32.Sleep

00401077   .  EB 04               jmp short crackme.0040107D

 

0040107D   > \E8 67010000  call crackme.004011E9

00401082   .  99                     cdq

00401083   .  B9 32000000   mov ecx,32

00401088   .  F7F9                idiv ecx

0040108A   .  52                    push edx

0040108B   .  FFD6               call esi                                 ;  kernel32.Sleep

 

Si se ha utilizado a modo de código basura, debería de repartirse más por la función o diseñarse de alguna otra manera ya que es muy obvio comprobar que no es necesario analizarlo para la resolución del reto.

 

  •  Los dos hilos que se lanzan para comprobar el fichero llave, utilizan la misma función y esta hace exactamente lo miso. Podríamos haber solucionado esto utilizando por ejemplo dos puntos de entrada para la función o una modificación de la misma. Otra opción hubiese sido utilizar otra función que comprobase otras propiedades del los bytes del fichero.

 

  •  Al utilizar el segundo hilo para chequear los bytes del fichero, si no hemos modificado la función ni hemos utilizado otra función distinta, al menos deberíamos haber cambiado la tabla de bytes para forzar un nuevo cálculo de la segunda parte de los bytes del fichero.

 

6.- Despedida y cierre.

 

Un Saludo a los miembros de Disidents.org, al canal #crackers, a thEpOpE, a NoMuRyTo y a toda la gente que me dejo pero sabe que puede darse por saludada. Espero que no seais muy duros en las críticas de mi primera publicación.

convert this post to pdf.