Buffer Overflow (BoF)

Buffer Overflow (BoF)

En seguridad informática y programación, un desbordamiento de búfer (del inglés buffer overflow o buffer overrun) es un error de software que se produce cuando un programa no controla adecuadamente la cantidad de datos que se copian sobre un área de memoria reservada a tal efecto (buffer): Si dicha cantidad es superior a la capacidad preasignada, los bytes sobrantes se almacenan en zonas de memoria adyacentes, sobrescribiendo su contenido original, que probablemente pertenecían a datos o código almacenados en memoria. Esto constituye un fallo de programación

INTRODUCCIÓN

Diagrama de flujo de la pila:

Cuando se introduzcan datos en la memoria, el STACK bajará hasta la LOW memory y el parametro HEAP subirá hasta STACK memory. Una vez entendido este concepto, el STACK memory se divide en:

– Funciones del codigo

– Parametros de entrada

– Return Address. Que indica al código como salir de la función y volver al main

– EBP. Base Pointer.

– Buffer. Para nuestro ejemplo se ha determinado que el buffer ocupe 64 espacios de memoria. Cada vez que se van introduciendo datos en el buffer, éste va aumentando hacia arriba.

¿Qué pasaría si se introducen tantos datos que se ocupen el lugar de la memoria reservado para EBP? ¿y para Return Address?: Buffer Overflow.

Ejecutar el programa

La mayoría de SO ya vienen por defecto con protección frente a este tipo de vulnerabilidades. Por lo tanto para deshabilitar la protección tenemos que hacer lo siguiente:

echo 0 > /proc/sys/kernel/randomize_va_space

A continuación para compilar el programa:

gcc -g -fno-stack-protector -z execstack -o plaintext-bof-parte-1 plaintext-bof-parte-1.c -m32
sudo chown root plaintext-bof-parte-1
sudo chgrp root plaintext-bof-parte-1
sudo chmod +s plaintext-bof-parte-1

Con la opción -g permites ver el codigo dentro de un “debugger”

  • fno-stack-protector. El stack no va a tener proteccion

  • execstack. El stack va a ser ejecutable.

  • -o. Indicas el nombre que quieras para guardar el programa

  • plaintext-bof-parte1.c. Nombre del programa

  • -m32. Compilar el programa en 32bis.

Con gdb podemos debuggear el codigo:

Modo ensamblador a modo de ejemplo.

Si en lugar de 64 caracteres ponemos por ejemplo 80 caracteres ocurre lo siguiente:

Llegamos a un punto en el que el programa se corrompe.

Con la siguiente opción listamos el código desde la linea 15 a la 28:

  • list 15,28

Con la opción:

  • run AAAA. Le digo al programa que ejecute la aplicación con variable de entrada: AAAA

A continuación pongo un breakpoint en la linea 20 que es donde tengo declarado el buffer e indico lo siguiente:

  • x/64wx. Imprima en hexadecimal todas las palabras.

  • ADDRESS: Necesito indicar que dirección de memoria quieres que imprima.

Por ejemplo:

ESP

ESP (Extended Stack Pointer): Es un puntero al final de la pila. Tras la ejecución de una función la dirección de retorno se vuelve a cargar en ESP para continuar la ejecución en el mismo punto donde había quedado.

Imprimo el ESP:

  • ESP: Stack Pointer. Punto de referencia que tendrá la aplicación para ver donde se hacen los cálculos. Crece en orden ascendente también hacia el HIgh Memory

    • 0xffffd290: Low Memory

    • 0xfffffd380: High Memory

Continuamos con la ejecución de la aplicación, como estoy en un breakpoint con la opción:

  • n: Next. Indico que continúe el flujo del programa

Vemos la dirección: 0x41414141 (AAAAA en hexadecimal)

En ese espacio de memoria es donde se esta ejecutando nuestra variable

EIP

EIP. Intruction Pointer. Es uno de los punteros mas importantes ya que es el indicador de decirle a la memoria cual va a ser el proximo salto/paso. Relevante con respecto a un redteam ya que si controlamos este puntero podría apuntar a un shellcode.

  • x $eip: Opción para indicar la dirección de la memoria del puntero eip

  • c: Continúe el flujo de ejecución del programa

¿Que pasaría si desbordásemos el buffer que tiene por defecto la aplicación?

Con python podemos pasar de manera rapida un nuevo de caracteres elevado, por ejemplo 90

Si observo el espacio de memoria con el puntero esp antes de imprimir veo que esta todo normal. Si ejecuto un “n” para que continue el flujo del programa observo:

LOW Memory está relleno de basura (muchos caracteres “A”)

Si continuamos observamos lo siguiente:

  • Segmentation fault. Cuando nosotros escribimos la cantidad de “A” que exceden el espacio de memoria reserva para el buffer ( 64 caracteres) lo que esta ocurriendo es que se esta sobreescriendo espacios de memoria como: “Return Address”

El espacio de memoria de “Return Address” es 0x565561cd. Lo sabemos porque en el ejemplo anterior cuando el flujo del programa ha sido exitoso lo podiamos ver con el puntero eip.

Ahora estamos sobreescribiendo ese espacio de memoria con muchas “A” (0x4141414141)

El programa al ir a la dirección de la memoria de “Return Address” pregunta donde vuelvo? y la aplicación le indica que vuelva a la dirección de la memoria 0x414141 es decir “A”, como no va a saber que dirección de memoria es esa corrompe.

Nota: Podemos aprovechar esto para indicar que apunte a la dirección de la memoria que nosotros como atacantes queramos, controlar la aplicacion, inyectar una shellcode y realizar un ataque.

Sobrescribir el EIP

El objetivo principal es identificar en que punto se sobreescribe la memoria para controlarlo nosotros como atacante.

Por ejemplo pruebo a pasarle al programa 80 caracteres:

  • delete: Borro todos los breakpoints que tenga

Por ejemplo, pruebo a pasarle 70 “A” + 10 “B”. Como en hexadecimal “B” es 0x42422, si en el segmentation fault recibo un 0x424242 ya sé que se encuentra dentro de esos 10 ultimos caracteres el espacio de memoria que se sobreescribe

Efectivamente, cada vez lo tengo mas acotado nuestro objetivo final.

Sigo acotando:

De los 78 bits solo ocupe 2bits (4242). Por lo tanto me faltan cubrir otros 2 bit para tener el espacio de memoria completo:

Ahora ya sé que justo despues va a haber un “segmentation fault” despues de los 76 caracteres que relleno con “A” y los cuatro ultimos bits “B”

Este es el segmento de la memoria, tengo controlado el eip. Puedo sobreescribir esta parte. Por ejemplo lo susituyo por “C” (0x434343):

¡Controlo el Return Address! esto es lo mas critico para una victima y el objetivo final para nosotros como atacantes. ¡Puedo sobreescribir el EIP!

Shellcode

Como en este punto ya tengo controlado la direccion “Return Address” lo que tenemos que hacer ahora es buscar un espacio de la memoria donde pueda inyectar una shellcode y dirigir el flujo del programa a esa shellcode. Para ello hacemos lo siguiente:

Lo que está marcado, todo con “A” (0x41414) es el espacio de la memoria que tengo para inyectar la shellcode. Tendriamos que poner la shellcode dentro del buffer

Para explicar esto se indica en el siguiente diagrama

Supongamos que el espacio de memoria del buffer ocupa 8 espacios de memoria. Como yo tengo control del EIP que vale 0x434343 lo que puedo hacer es que el siguiente paso le indico que apunte a uno de estos espacios de memoria reservado para el buffer donde yo ahora mismo tengo “A” 0x414141 a modo de basura pero es donde voy a inyectar mi shellcode. Por lo tanto cuando se ejecute el EIP apuntará de manera a mi shellcode como atacante.

El programa ejecutará lo que queramos.

Tenemos un espacio de hasta 64bytes para inyectar nuestra shellcode, buscamos en Google una shellcode de este tamaño como maximo y Linux ya que lo vamos a ejecutar en una Kali:

Por ejemplo:

Link: https://www.exploit-db.com/exploits/40131

Otro ejemplo si Googleamos: “shellcode tiny linux”:

Link: http://shell-storm.org/shellcode/files/shellcode-841.php

Shellcode-Test

De esta manera pruebo si el shellcode es funcional

Funciona correctamente.

Shellcode-Explotación

hay un aspecto a tener en cuenta en que hay espacios de la memoria “NOPS” que la aplicación no hace “Nada” se traduce como x90

A veces la memoria tiende a variar con gdb por lo tanto si indicamos que la direccion de la memoria por ejemplo se ubica en el primer espacio lo ponemos con NOPS “x90” y como la aplicacion no hara nada seguira a la derecha hasta que encuentre algo que ejecutar.

De esta manera nos aseguramos saber en que direccion de la memoria se ejecutara la shellcode.

Cálculo de NOPS

Todos esas direcciones es nuestra pila: stack. Por ejemplo elegimos el espacio de memoria 2 (). Sabemos que podemos inundar de basura nuestro buffer hasta 76 caracteres pero nuestra shellcode ocupa 21 caracteres, por lo tanto:

  • 76 – 21 = 55 caracteres lo inundamos con NOPS

  • 21 caracteres. Nuestra shellcode

  • EIP. le asignamos la dirección de memoria del próximo salto que hemos dicho que es: 0xffffd260. Como estamos trabajando con intel es little endian por lo tanto tenemos que poner la memoria al revés: x60xd2xffxff

Si ejecutamos a veces puede ocurrir lo siguiente:

nos vuelve a dar un segmentatin fault. Esto es debido en que a veces no es bueno tener juntas la shellcode + return address. Puede haber un conflicto en la memoria y el programa puede romper como en este caso.

Para arreglar esto es conveniente dejar un espacio en blanco con NOP entre la shellcode y return address de la siguiente manera:

Dejo un espacio entre mi shellcode y el return address dando como resultado los 80 bytes que teniamos inicialmente dejando fijo el tamaño de la shellcode que ese no puede variar y jugando con los NOP.

Soy root.

Conclusión

En técnicas de postexplotacion si busco algún programa que tenga privilegos de SUID, un usuario sin privilegios podrá ejecutar este programa como root. Si se aprovecha de un buffer overflow podría llegar a escalar privilegios y ser root realizando los pasos comentados.

Referencia: https://www.youtube.com/watch?v=7KZ5LCFr6Sw&t=1282s&ab_channel=

To top