miércoles, 8 de abril de 2009

Solución al reto 1 de Panda (III parte, las dos comprobaciones)

-------------------------------------------------------------------- Solución al reto 1 de Panda (I parte, fracaso absoluto) Solución al reto 1 de Panda (II parte, FPqué!?) Solución al reto 1 de Panda (III parte, las dos comprobaciones) -------------------------------------------------------------------- En anteriores episodios, localizamos dos zonas vitales para que nuestro serial fuera tomado como valido. Primero una comprobación, un salto que nos lleva a la zona de password incorrecto, “bad!” y una variable que tiene que tener un determinado valor, 4, para mostrar el mensaje correcto, “yes!”.

Empecemos por la primera comparación.

image

Empezamos desde el salto condicional JNZ resaltado en amarillo hacia atrás. JNZ, realiza el salto cuando el flag Zero no está activado, vale 0. En nuestro caso esto es así.

image Por esta razón el salto es tomado y llegamos a la zona de printf “bad!” :(. Podríamos cambiar el flag manualmente a 1 o invertir el salto y seguir la ejecución, pero nuestro objetivo es conseguir un password correcto no forzar a que muestre el mensaje correcto.

Así que manos a la obra, hay que analizar las instrucciones anteriores al JNZ.

La instrucción inmediatamente anterior al JNZ es XOR AH, 40 Queremos que el flag zero se active, así que esa instrucción tiene que dar como resultado 0, por ello AH debe valer 40 en ese punto.

Vamos a ir analizando mas rápidamente el resto las instrucciones, tened en cuenta que se hace en orden inverso, primero las que se ejecutarán últimas:

  • AND AH, 45: Quedamos en que AH tenía que valer 40 tras esta operación, si se le va a aplicar un “AND 45”, AH tiene que tener el bit 6 activado (correspondiente al segundo 4) y los bits 2 y 0 desactivados (correspondientes al primer 5). Puede resultar confuso, si, mirad esta representación para aclaraos:image
    En los bits marcados con x da igual el valor que tengan ya que el AND cuando se le aplica un 0 siempre devuelve un 0 independientemente del otro bit.
  • FSTSW AX: Esta instrucción guarda en AX el valor de los bits de condición de la FPU, mas info. ¿Qué diablos son esos bits? Pues los que indican el resultado de una comparación de dos floats. Puede verse en los registros.
    image
    El valor de FST se guarda en AX. Recordad que antes las operaciones se han hecho sobre AH (la parte alta del registro AX), que descartamos la parte baja AL, 8 bits. Así que queremos que el bit 6+8, 14, del FST valga 1 y que los bits 2+8,10 y 0+8,8 valgan 0. Ollydbg puede confundir con su descomposición del registro FST. El contenido desglosado de FST es el siguiente.
    imageComo vemos el bit 14 corresponde a el bit C3(Z) de los Condition Code. El bit 10 al C2(C) y el bit 8 al C0(S). imageCuando estos bits se activan justo como requiere el programa significa que la FPU ha comparado dos número que eran iguales. Seguimos con las instrucciones previas, omitiendo aquellas que no hagan nada interesante:
  • FUCOMPP: Compara los números de la FPU ST(0) y ST(1)
    imageAsí que esos dos números tendrían que ser iguales, ¿no?
  • FXCH ST(1): Intercambia los registros ST(0) y ST(1)
  • FILD WORD PTR SS:[ESP]: Carga en ST(0), el tope de la pila de la FPU, el valor apuntado por la cima de la pila, y si hubiese algo en ST(0) lo desplaza a ST(1).
  • PUSH EAX: El valor que acababa de cargar en la FPU es EAX, que lo mete en la pila previamente.
  • CBW: Convierte un byte, AL, en palabra, AX, rellenando con ceros…pues fale.
  • MOV AL,BYTE PTR SS:[EBP-21]: Mueve a AL el carácter introducido, ¿que cómo sabemos que en EBP-21 estaba el carácter? Acudiendo a nuestro scanf inicial. image
  • FMULP ST(1),ST: Se multiplican ST(0) y ST(1)
  • FLD [LOCAL.8]: Carga en la pila de la FPU el %f2
  • FILD DWORD PTR SS:[ESP]: Carga en la FPU el valor de la cima de la pila
  • PUSH EAX: Pone en la pila EAX
  • MOV EAX,[LOCAL.12]: Pone en EAX la variable LOCAL.12, que aun no sabemos que es…
  • FISTP [LOCAL.12]: Guarda en LOCAL.12 el valor ST(0)
  • FLD [LOCAL.7]: Pone en ST(0) %f1

Recapitulemos:

  • Se carga en la FPU %f1
  • Lo saca y lo vuelve a meter, así me gusta nena, pero sigue estando %f1 en la FPU, ST(0)
  • Se carga en la FPU %f2 en ST(0), ahora %f1 está en ST(1) Para %f1 = 1 y %f2 = 97 image
  • Se multiplican estos dos valores
  • Mete en la FPU el valor ASCII del carácter %c
  • Compara los dos números

De modo que un password como esté: “1-1-97-a” debería superar la primera condición. 1*97 == ASCII(‘a’)

Probémoslo, ponemos un breakpoint antes del maldito JNZ, introducimos el posible password vemos que camino toma el salto.

image

JNZ no salta (la flecha esta de color gris), por lo que llega al JMP que le aleja de el printf que nos dice que el password era incorrecto. Perfecto !!

Uff, esto de analizarlo al revés es una locura. Pero nos ahorra analizar algunas instrucciones aunque ahora no nos ha servidor de mucho después nos ahorraremos un buen cacho de análisis.

Pasamos al segundo requisito para que un password sea correcto. que el desplazamiento visto en el capítulo anterior, almacenado en LOCAL.4, valga 4. Recordamos…

image

Buscamos donde se asigna una valor a LOCAL.4, se encuentra casi al principio, cerca del scanf.

image

Se le asigna el valor de EAX, justo después de de esa función. Esto es típico, cuando una función devuelve un valor, realmente se almacena en EAX. Hay que fijarse que la función solo se le pasan %f1 (argumento 3) y %d (argumento 1) así que en principio no usará el resto de números introducidos.

Entramos en la función, la seleccionamos y pulsamos intro. Una vez dentro de la función vamos al final de está. Tracearemos desde el final al principio buscando que hace falta para que EAX valga 4.

image

EAX vale lo que contenga LOCAL.18. A esta instrucción de puede llegar desde tres lugares. Desde la instrucción justamente anterior, MOV [LOCAL.17], EDX. Y desde dos saltos. Para localizarlos seguimos la línea roja de la muerte.

image

Desde cualquiera de los dos saltos LOCAL.18 vale 0 y por lo tanto EAX también. Esto no puede ocurrir ! Ponemos un breakpoint en los dos saltos, con el fin de determinar si alguno de los dos es tomado. Ejecutamos el programa e introducimos “1-1-97-a”. Vemos que en el primer salto ni se para, una comparación anterior hace que no se llegue hasta ahí. En el segundo alto si se para y…salta, por lo que LOCAL.18 valdrá 0, EAX 0 y … “bad!”.

image

Bueno está claro que la culpa es del JE(Jump if Equal) anterior si hubiese sido “Equal” hubiese ignorado este JMP maldito.

Analizamos las instrucciones anteriores al JE.

image

A destacar que EAX y LOCAL.16 tienen que valer lo mismo. EAX resulta de la suma de EAX y EBX. Ponemos un breakpoint en ADD EAX, EBX para observar que valores toma con diferentes passwords.

image

Sea cual sea el password introducido, EAX siempre vale 1 y EBX 3. Así que EAX terminará conteniendo el valor 4 (EAX += EBX).

Recordamos que antes hemos quedado en que EAX y LOCAL.16 tenían que valer lo mismo, y EAX sabemos que siempre vale 4. Así que buscamos cual es la última asignación realizada a LOCAL.16.

image

La encontramos ahí, tras analizar las instrucciones vemos como LOCAL.16 es cargada a partir de LOCAL.7 la cual es cargada a partir de ARG.3, que como hemos visto al entrar a esta función es %f1.

Ahora con todas las piezas disponibles, montamos el puzzle. EAX y LOCAL.16 tenían que valer lo mismo, EAX siempre valía 4, así que LOCAL.16 tiene que valer 4 y LOCAL.16 coge el valor de %f1. Así que %f1 tiene que valer 4.

Pero no olvidemos que %f1*%f2 = %c 4*%f2 = %c Si %f2 vale 25 por ejemplo 100 = %c = ‘d’

Pues ahí lo tenemos. El primer número, %d, no se ha usado para nada, así que le damos un valor aleatorio. %f1 tiene que valer 4. %f2 le damos el valor de 25. %c tiene que ser igual al valor ASCII de %f1*%f2, 100, ‘d’.

Y colorín colorado, un password correcto es: 1-4-25-d

image

Eureka ! Tarde unas 6 horas en sacarlo, soy un cracker de pacotilla. Envíe la solución 14 horas después del inicio del reto así que me quedaré sin camiseta, solo los 5 primeros la reciben.

Shaddy, un amigo que lo resolvió antes que yo, ha publicado también una solución que seguramente esté mejor explicada que esto. Solución en Megaupload Solución en GoogleGroups

Si queréis aprender sobre ingeniería inversa, este curso de Ricardo Narvaja es un buen comienzo, y la lista de google de crackslatinos un buen camino.

8 comentarios:

  1. Thor inpecable los planteamientos, Shaddy pensaba que era bueno pero no tanto.

    Bueno me despido un gusto leerme las tres partes.

    Saludos, interesante lo de la camiseta xD!!

    ResponderEliminar
  2. Me alegro que te haya gustado.

    Un saludo!

    ResponderEliminar
  3. EXCELENTE TUTO AMIGO

    ResponderEliminar
  4. Buen artículo amigo :), desde luego que la explicación del FPU está impecable, me ha gustado :P, quiero ver más como éste eh?

    ResponderEliminar
  5. Muy bueno Thor, estubo genial tu solución :). Felicitaciones por ello.

    salu2

    ResponderEliminar
  6. Gracias, me alegro que os haya gustado

    ResponderEliminar
  7. Muy bueno el tute Thor!!

    Ademas leer tu tute es como revivir el momento de cuando lo estuve resolviendo, ganas de tirar la toalla con las operaciones inutiles y ver la luz al intentar identificar el chico bueno ;D

    Aunque para eso me venia todo el rato a la cabeza las palabras de ShaDDy, "Haz ingenieria inversa, del mensaje al serial y no al reves" Gracias!!

    La verdad uno se desespera analizando el algoritmo que usa los comandos de coma flotante, pero es cierto que de esa manera aprendi mas sobre los ST y demas operaciones.

    Enhorabuena Thor!!
    Saludos.

    ResponderEliminar
  8. Gracias por publicar. Está impecable. He disfrutado leyéndolo.

    Gracias otra vez.

    ResponderEliminar