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-HiLoHiYYY


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

Recommended Posts
Showing 5 comments
  • Pepe
    Responder

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

  • uri
    Responder

    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.

  • Picapau
    Responder

    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
    Responder

    Buenas,

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

    Un saludo,

    Josemi

  • miguel
    Responder

    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

Leave a Comment