Smashing Rabbit – Sobreescribiendo GOT (III)

En esta entrada veremos el reto passcode de la web Pwnable.kr

Pwnable Challenges – File Descriptor & Hash Collision I

Pwnable Challenges – Buffer Overflow II

A diferencia de las dos entradas anteriores donde vimos como explotar dos buffer overflow, un hash collision y un file descriptor, en estas y las siguientes explicaré como hacerlos pero que sirva como ayuda más que como solución para evitar el dar una respuesta clara y así el poder incitar a resolverlo por uno mismo mejor.

Código fuente vulnerable:

#include <stdio.h>
#include <stdlib.h>

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;	
}

Iniciamos radare2. Analizamos y en la función entry0() veremos las dos llamadas a las funciones welcome() y login() .

Vemos las funciones en el binario:

Vemos las strings:

En un vistazo rapido cuando ejecutamos el programa nos pide un nombre y nos muestra un mensaje de bienvenida con ese mismo nombre que introdujimos. Seguidamente nos pide un passcode1 pero nos encontramos con el problema “Segmentation fault”. 

Comencemos analizando las dos funciones. Veamos la función login().

Deducimos que si los dos passcode son igual a un valor determinado tendremos Login OK. Vemos en el densamblado al introducir el primer passcode:

Antes de llamar a la función scanf(), mueve con MOV el valor del Dword [ebp-local_10h] y no ninguna dirección de memoria con la instrucción LEA por tanto la función antes de ser llamada recibe el valor de una variable y no la dirección de memoria que será donde apunte al contenido cuando introduzcamos el valor por teclado. De ahí que nos de el fallo de segmentation fault y el argumento de la función deba ser una dirección de memoria.

Por tanto debería de ser así: scanf(«%d», &passcode1);

Dependiendo si el resultado de la comparación con CMP es igual o no realizara el salto o no con JNE. A simple vista para tener un Login correcto no tiene que saltar, realizando una llamada a bin/cat visualizando la flag.

¿Entonces que necesitamos hacer para poder explotarlo? Si conseguimos sobreescribir el valor de la variable passcode1 con su valor correspondiente en hexadecimal deberíamos de poder hacer lo mismo con passcode2.

Demos una vuelta hacia atrás y veamos la función welcome().

Ahora la función scanf() si va a recibir la dirección de memoria debido a la instrucción LEA y además solo leerá los 100 primeros bytes (%100s). La dirección de memoria del buffer [ebp-local_70h] se mueve al registro EDX, y no el contenido.

Por tanto la variable donde almacenará el valor que introduzcamos (enter name:) se encuentra en la dirección de memoria [ebp-local_70h]. La idea es usar la vulnerabilidad de poder escribir a partir del primer scanf() de la función welcome() y sobreescribir passcode1, pero para ello necesitamos saber la cantidad de offset que existen entre el comienzo de la variable donde almacenamos el nombre y el inicio de passcode1. El tema es que las variables passcode1 y passcode2 no están inicializadas usando el Stack con la posición adecuada para «sobreescribirlas».

Como vimos anteriormente la variable passcode1 esta almacenada en local_10h, por tanto para calcularlo solo necesitamos hacer una resta: 0x70-0x10=0x60, en decimal 96.

Los últimos 4 bytes sobreescriben a la variable passcode1 pero no conseguimos sobreescribir passcode2 con su valor correspondiente. No podemos parchear el binario ya que no tenemos permiso es de solo lectura, por tanto tenemos que encontrar una manera de poder modificar el flujo de ejecución del programa y conseguir llamar a la función system() que será la que nos de la flag.

Todas las funciones como printf, fflush y system tienen una entrada en la tabla GOT(Global Offset Table) con la dirección donde se encuentra la función. Usaremos el ataque GOT Overwrite (+info: https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf)

La vulnerabilidad que encontramos con scanf() al no recibir la dirección de memoria antes de llamarse sino contenido o valor por causa de la instrucción MOV (deberia de ser asi: scanf(«%d», &passcode1);), podemos aprovecharla y escribir como contenido o valor la dirección de memoria de system() en la tabla así cuando el programa ejecute su flujo normal y ejecute fflush() ejecutará lo que le hayamos escrito dentro de ella, ósea la dirección de memoria donde se encuentra la función en este caso system(). Para ver la tabla GOT:

Gracias a la explotación que realizamos con los 100 Bytes que podemos controlar lo que escribir, a partir del Byte 96 controlamos el argumento que va a recibir la función scanf() que nos permitirá escribir donde queramos por stdin.

Es decir, 96 Bytes + 4 Bytes (dirección GOT fflush: 0x0804a004)

Cuando se ejecute scanf() escribimos por teclado el valor decimal correspondiente a la dirección de memoria de la llamada a system(), se sobreescribirá con esta función que ejecuta el /bin/cat de la sección text obteniendo la flag. Un ejemplo que me puso Manuel (@DiaLluvioso) para poder entenderlo mejor es sobreescribiendo en printf() del mismo modo:

  • Enviamos del mismo modo 96 Bytes + 4 Bytes la dirección de la GOT del printf()
  • Cuando se ejecute la función scanf() añadimos 4 Bytes más en decimal: hex(0x41414141), decimal(1094795585)
  • Y printf() sobreescrito con AAAAA
  • pwndbg> x/xw 0x0804a000
    0x804a000 <printf@got.plt>: 0x41414141

Por tanto el payload quedaría de la siguiente manera:

$python -c «print ‘A’ * 96 + ‘\x04\xa0\x04\x08’ + ‘134514135’» | ./passcode

Entradas sobre Reversing:

Diseccionando binarios – 0x00 Introduccion

Por último dar las gracias a Manuel (@DiaLluvioso) que me ayudo a comprender mejor este reto. No os perdáis esta semana su taller de Exploiting en la RootedCON18 dentro del track Fwhibbit:

GNU/Linux Binary Exploitation I&II, Manuel Blanco.

Talleres introductorios a la explotación de software GNU/Linux. Se explicarán diversos tipos de vulnerabilidades (corrupciones de memoria, fugas de información, condiciones de carrera, etc…) siguiendo metodologías actuales del desarrollo de exploits donde se sortearán las protecciones del sistema operativo.