domingo, 24 de julio de 2011

Writeup sdb wargame keyconsole

Keyconsole era un reto de reversing (al menos yo lo enfoqué así) muy sencillito, de hecho era el primero de todo el panel de retos (si empezamos arriba a la izquierda claro).

Al acceder al reto encontrabamos un enlace a un binario llamado tmp_key que descargabamos y al ejecutar obteníamos unos mensajes de "comprobando el environment" y de que nuestro software no es original así que nos quedamos sin token :).






Visto lo visto empezamos el reversing, en mi caso utilizo gdb. Lo arrancamos con el binario y desamblamos main (por probar a lo loco si se escribió en C).

DISCLAIMER!: Yo soy un newbie en esto del reversing y del hacking en general, yo hago lo que buenamente puedo y me manejo con las herramientas como buenamente puedo, si crees que hubiera sido mejor tirar primero por otro camino, muy probablemente tengas razón, por ejemplo creo que en general es mejor antes de ponerse con el gdb, pasarle un strings al binario y ver si algo por dentro nos llama la atención... esto es algo que he aprendido a posteriori y que a partir de ahora sí haré :), pero aquí pongo un poco el proceso que seguí, que como ya comento no es el más profesional.

Lo que vemos en el desamble de main es lo siguiente:

Una costumbre que tengo yo cuando me pongo a estas cosas es fijarme en las funciones que se llaman (calls) para saber más o menos qué es lo que quiere hacer la función, los he marcado en rojo.

También me fijo en aquellas direcciones que puedan ser ristras de caracteres en memoria. Las identifico buscando los parámetros a las funciones llamadas que deban tener ristras (como a los fopen, cuyo primer parámetro es el path al fichero a abrir) y buscando instrucciones movl que pongan en el registro destino un número que tenga pinta de estar haciendo referencia a la zona de datos, los he marcado en verde.


Finalmente profundizo un poco más en cómo hace lo que quiera que haga el programa en base a mirar los saltos y ver hacia dónde llevan, marcados en azul.


Vemos que hay 3 funciones más definidas en el binario (o sea, creadas por el programador):
  • get_random_number_by_sony()
  • check_env()
  • array()
Antes de ponerme a mirar por dentro cada una de esas funciones le sigo un poco la pista al programa y busco ristras en memoria con las direcciones antes apuntadas:












En rojo tenemos la string que en principio parece que querríamos que nos apareciera. Si le seguimos la pista hacia atrás vemos lo siguiente:
























Para entender bien esta imagen hay que seguirla hacia atrás. Vemos que la string interesante la imprime el printf() señalado porque como primer parámetro (%esp) tiene lo que hay contenido en eax, que a su vez viene del movl anterior el cual pone la dirección de nuestra cadena en el registro, a esta instrucción se llega desde el salto jne de main+254, dicho salto se realiza si el valor de lo contenido en 0x6c(%esp) (una variable local de main) es distinto de 1, que a su vez es lo que contenía el registro eax después de la llamada a check_env(). En definitiva algo como:

if (check_env() != 1) {
    printf("[-]System compatible:\n     ");
    array(0x6c(%esp));
}
else {
    ...
}


Pues que bien, ahora se me ocurren dos cosas, ir a la función array() e intentar sacarle lo que nos va a devolver, o ir a check_env para comprobar que es lo que hace y como logramos que no devuelva un 1. Yo he optado por la segunda opción. Desamblamos check_env():






















































Empezando por el final vemos la instrucción que NO queremos que se ejecute, la que pone un 1 en el registro eax y termina. Siguiendo la pista hacia atrás vemos que queremos que el salto anterior se tome (jne). Para quién no sea un ninja de ensamblador de x86 (como yo, que no lo soy y me pegue aquí un tiempo) la combinación de "test eax, eax" con el jne puede ser algo confusa. Test realiza un AND entre los dos registros, cuando ésto se realiza sobre un mismo registro viene a significar más o menos "es este registro 0?". Los saltos comprueban su condición en base al registro de flags, en el caso del jne se comprobará el zf (zero flag) que estará activo si el test devolvió 0, es decir si eax era 0. En román paladino ese grupo de instrucciones significan:

if (fopen("fichero interesante", "algun modo de apertura"))
    return 1;
else
    return 0;


Así que lo que queremos hacer es que ese "fichero interesante exista". ¿El nombre? Pues siguiéndole la pista hacia atrás vemos que es el primer parámetro para fopen() (%esp), que viene de eax, que a su vez viene de "lea -0x3a(%ebp)". Eso es la dirección de memoria de una variable local, el nombre del fichero que necesitamos que exista para que check_env() devuelva distinto de 1 se construye en tiempo de ejecución. Llegados a este punto podemos ejecutar hasta el fopen(), ver el nombre del fichero necesario, crearlo y luego seguir ejecutando el programa... y no necesitaríamos ponernos a comprender todo lo anterior, srand(), rand(), etc...












Bueno, pues ya sabemos que fichero debe existir... >$ touch /tmp/0_privatekey_2 y continuamos.









Y ya tenemos el token.

No hay comentarios:

Publicar un comentario