Español | English
rss facebook linkedin Twitter

Una solución para el crackme de la Rooted - Level #1

Como ya comentamos en un anterior post, simultáneamente a las conferencias de la RootedCon tuvo lugar un wargame en el que se debían resolver diversas pruebas. A continuación se muestra una posible solución para una de ellas, que se presentaba a modo de crackme.

Para pasarla, se debía generar un password correcto para el usuario admin:



Un buen punto de entrada habitual en este tipo de crackmes puede ser lanzar el programa con el depurador y poner un punto de ruptura en la API MessageBox, de modo que la ejecución se detenga al mostrar el mensaje de error producido al introducir un valor incorrecto, y suponiendo que nos encontraremos cerca de la rutina de comprobación del password.

En este caso no es tan simple, puesto que el creador del crackme ha añadido algún truco anti-debug. Al lanzar el crackme en el depurador, éste se detiene debido a instrucciones int3, como se puede ver a continuación:




Por supuesto, no se trata de un error de programación, sino que las funciones más importantes del programa han sido cifradas con un código similiar al siguiente:



Como bien sabemos, habitualmente las subrutinas comienzan con el siguiente prólogo:



Podemos comprobar que si ciframos está función con el algoritmo mencionado, dado que la clave de cifrado inicial es 0x99 y la función comienza con 0x55, con un XOR obtenemos 0xCC, que se corresponde con el int3. De esta manera, garantizamos que las funciones cifradas comenzaran con int3, y la función de descifrado será almacenada en el manejador de excepciones para que pueda ser descifrada en ejecución y ejecutada sin problema alguno.

Sabiendo cómo se cifran las funciones, podemos decodificarlas todas, y podemos tener un fichero más cómodo de estudiar. El crackme utiliza la API GetDlgItemTextA para obtener los valores introducidos en los TextBox, llegando al siguiente código:



Aquí podemos encontrar la subrutina que valida nuestro código, protegido por tres detecciones de depurador antes y después de su ejecución y, para complicar más el asunto, el código de validación, a diferencia de las demás funciones, vuelve a ser cifrado después de ejecutarse, siendo únicamente visible mientras la contraseña está siendo comprobada. El string "iddqd-idkfa!" no se trata de la contraseña, habrá que trabajárselo un poco más ;)

Dentro de la rutina de comprobación podemos diferenciar varias partes, como la comprobación del inicio del password, que debe coincidir con "r00ted-" para que la rutina siga comprobando las siguientes condiciones:



Siguiendo el análisis de la rutina, se puede comprobar que el password correcto debe contener un guión ("-") y algunos "Hi" y/o "Lo", llegando a la conclusión de que el password debe seguir el siguiente esquema:

r00ted-XXXXXX-YYY

XXXXXX consiste en una combinación de Hi y Lo, mientras que YYY se trata del valor introducido por el usuario que, como veremos más adelante, deberá cumplir ciertas condiciones.

Veamos ahora como son generados los mensajes de error. Podemos encontrar un array de punteros a diferentes mensajes de error, aunque están cifrados. Encontramos dicho array en la dirección 0x426000.



Posteriormente, son inicializados en el siguiente orden, contando desde 0 hasta 7:



El más importante es el 5º elemento, porque contiene la subrutina que muestra el mensaje que nos dice que hemos acertado el password. Lo único que nos falta es como hacer que el flujo del programa termine llegando a esta rutina.

La solución consiste en que la segunda parte de la contraseña debe apuntar a dicho valor, esto es, el quinto, para lo que, valiendo Hi 1 y Lo 0, necesitamos el valor 101


Ahora tan solo falta la última parte del password. El crackme calcula el checksum (un simple XOR) de las dos primeras partes (azul) y lo compara con el checksum de la última parte (verde):

r00ted-HiLoHi-YYY

Por lo tanto, necesitamos el valor YYY que haga coincidir los dos checksums, y una combinación de caracteres para que esto sea real es r00ted-HiLoHi-Ap8



Solo como curiosidad, otros mensajes ocultos en el programa son:

- You are tracing me!
- ALLMOST!! Bad bad bad ....
- ALLMOST!! This one is not good : /((
- INVALID, No luck .....
- KEEP WORKING, Bad bad bad ....
- INFO, Keep the good work ...
- IN-VALID The password doesn't look OK
- Not Good, Still not there ...

Un crackme muy interesante!

Jozsef Gegeny
S21sec e-crime

5 comentarios:

uri dijo...

Muy buena solucion Jozsef!

Como comentario, el crackme tenia un timer que tiraba un cutre-antidebug de forma aleatoria :

void myTimer() {
    static int oldC = 0;
    int c;

    return 0; // ouch

    if( randomAntiDebug( "A", "B" ) ) {
        ExitProcess(-1);
    }
    c = GetTickCount();
    if( oldC ) {
        int k = ( c - oldC ) / 700;
        for( int j=0; j<8; j++ ) {
            // Corrupt functionPointers if delay is too big.
            functionPointers[j] = (void*)((long)functionPointers[j]^k);
        }
    }
    oldC = c;
    SetTimer(NULL, 1, 500, (TIMERPROC)myTimer);
}

Lo que pasa es que puse un return 0 para arreglar una otra cosa, y allí se quedo :), cosas del directo.

Para evitar los breakpoints en las funciones importantes ( MessageBox, GetDlgItemTextA, .. ) tenia una funcion IndirectCall() que hacia algo asi como :

    // Desensamblar hasta jump relativo / call
    for( offset=0; offset < BF_SIZE; ) {
        x86 disasm( (unsigned char*)pFunc, BF_SIZE, (DWORD)pFunc, offset, &insn );
        if( insn.type & INS_EXEC ) {
            break;
        }
        offset += insn.size;
    }
    // Generando buffer : Stub inicial + PUSH + RET
    memcpy( polyBuffer, (char*)pFunc, offset );
    polyBuffer[offset] = 0x68; // Push
    *(int*)(polyBuffer + 1 + offset ) = (DWORD)pFunc + offset;
    polyBuffer[offset+5] = 0xc3; // Ret
    
    // Copiando argumentos a la funcion y llamando a polybuffer
      asm {
        mov ecx, dword ptr nArgs
        lea esi, [nArgs+4]
pushArgs:
        push [esi+ecx*4-4]
        loop pushArgs
        call polyBuffer
    }

Merci por el post i felicidades por la solucion, que yo sepa es la unica que hay : )).

Salut!,
Uri.

Pepe dijo...

Interesante. Me puede ofrecer un link de descarga del crackme para practicarlo?

Picapau dijo...

Any chance you post a link to the crackme or mail it to me so we can play around with it, please?

S21sec e-crime dijo...

Buenas,

aquí os dejo este link con el crackme de uri, la contraseña es "uricrackme".

Un saludo,

Josemi

miguel dijo...

Buen crackme Uri.
Lo mejor:
La ocultación de código y algunas técnicas antidebug poco vistas.
Después de superar todas las trampas, el conseguir un serial válido es sencillo.
Os dejo un keygen escrito en python.
Según el nombre de usuario: o no es necesario calcular la segunda parte del serial o no tiene por qué ser de 3 caracteres.
Saludos.
Miguel

Keygen .............
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# keygen-uri-crackme.py
#
# miguel
#
import string, sys

def baraja_solucion(user):
solucion = [0,0,0,1,0,0,0,0]
lu = len(user)
for c in range(lu):
n = ord(user[c]) & 3
if n == 0:
solucion[5],solucion[0] = solucion[0],solucion[5]
solucion[3],solucion[6] = solucion[6],solucion[3]
elif n == 1:
solucion[4],solucion[7] = solucion[7],solucion[4]
solucion[3],solucion[2] = solucion[2],solucion[3]
elif n == 2:
solucion[1],solucion[5] = solucion[5],solucion[1]
solucion[6],solucion[3] = solucion[3],solucion[6]
else:
solucion[0],solucion[2] = solucion[2],solucion[0]
solucion[7],solucion[4] = solucion[4],solucion[7]
return 7 - solucion.index(1)

def convierte(n):
result = ""
t = n
for i in range(3):
if t & 1 == 0:
result = "Lo" + result
else:
result = "Hi" + result
t = t >> 1
return result

def calc_primer_xor(s):
r = 0
ls = len(s)
for i in range(ls):
r = r ^ ord(s[i])
return r

def calc_serial(user):
# parte fija
serial = "r00ted-"
n_solucion = baraja_solucion(user)
serial += convierte(n_solucion)
primer_xor = calc_primer_xor(serial)
add_char = ""
if primer_xor > 0:
if primer_xor < 0x21 or primer_xor > 0x7e:
salir = False
for a in range(0x21,0x7f):
for b in range(0x21,0x7f):
if a ^ b == primer_xor:
salir = True
break
if salir:
break
if salir:
add_char = chr(a) + chr(b)
else:
add_char = "ERROR"
else:
add_char = chr(primer_xor)
serial += "-" + add_char
return serial

print "--- Keygen for uri-crackme ---"
print
# Input username
username = raw_input('User name: ')
serial = calc_serial(username)
print "Serial: %s" % serial


(+34 902 222 521)


24 horas / 7 días a la semana



© Copyright S21sec 2013 - Todos los derechos reservados


login