Buffer overflow explicado

Sin duda muchos de vosotros habréis oído hablar de una vulnerabilidad basada en un buffer overflow pero no os ha llegado a quedar claro que significa exactamente esto.

En general, cuando aparecen dichas noticias suelen ir asociadas al término "ejecución de código arbitrario" que es, en realidad, el verdadero problema. Vamos a ver un poco más a fondo lo que significa una vulnerabilidad de buffer overflow y lo que implica.

En primer lugar, la palabra overflow procede del inglés y quiere decir desbordamiento (de hecho existe el término desbordamiento de buffer en español, pero como tantas otras cosas, es mucho más habitual escuchar el término inglés). ¿Que significa esto? Pues ni más ni menos que lo que indica su nombre, que tenemos un buffer cuya capacidad se ha visto desbordada.

En la mayoría de los lenguajes de programación un buffer hace referencia a un puntero a memoria reservada que tiene un determinado tamaño, si intentamos meter datos de más tamaño del del buffer, este se desborda.

Podríamos pensar que cuando esto pasa se produce una excepción y listo (y en algunos lenguajes esto es exactamente lo que pasa) sin embargo en la mayoría de los lenguajes no ocurre, con resultados generalmente desastrosos.

Supongamos el siguiente fragmento de código en C

void MiConsultaAlUsuario()
{
  // Reservamos 20 caracteres para el nombre del fichero
  char fileName[20];
  printf("Introduce el nombre del fichero\n");
  scanf("%s",fileName);
  printf("Has escrito %s", fileName);
}

Evidentemente en este punto para aquellos que han programado algo en C o en C++ deben estar saltando todas las alarmas, pero puesto que esto es un ejemplo bien sirve para ilustrar el caso.

El problema que nos encontramos aquí, como es lógico, es ¿que ocurre si al usuario le da por introducir más de 20 caracteres? Parece una pregunta fácil pero no lo es en absoluto y la mayor parte de las vulnerabilidades que se encuentran hoy en día pertenecen a este "tipo" de error (sin ir más lejos la relativamente reciente vulnerabilidad del GDI+ de Microsoft era una variante de este tipo de error). Obviamente nuestro buffer se va a desborda pero ¿que consecuencias tiene eso?

Para entender el problema hay que entender que está pasando a nivel interno (a nivel de código máquina) cuando declaramos el segmento de código anterior. A grandes rasgos (no quiero entrar en detalles porque darían para varios artículos) existen dos "áreas" principales de memoria en un programa, la pila o stack y el heap (monticulo en español pero el término nunca se usa). La pila es, como su nombre indica, una pila de memoria en la que se van almacenando y recuperando datos de forma incremental, es decir, lo último que se inserta es lo primero que se recupera.

Entre las cosas que se almacenan en la pila está, por un lado, la declaración de variables locales, es decir, aquellas que se adscriben al ámbito de la función en curso. De esta forma la declaración char fileName[20] reserva espacio para 20 caracteres en la pila. Otra de las cosas que se almacenan en la pila, y he aquí el punto importante, es la dirección de retorno de la función, así como el valor de retorno. De esta forma, el esquema de la pila cuando llegamos al scanf es el siguiente:

pila.png

Ahora vamos a ver lo que pasaría si introducimos más de 20 caracteres en nuestro buffer. En primer lugar se llenarían esos 20 caracteres, en siguiente lugar se sobrescribiría el valor de retorno de la función y por último (o dios mío) el punto de retorno de la función. ¿Veis el problema?

Lo explico, si sobreescribimos el punto de retorno de la función, cuando el puntero de ejecución alcance el return el compilador desapilará (y desechará) los 20 caracteres reservados para el buffer, a continuación obtendrá el puntero de retorno (puesto que nuestra función no tiene valor de retorno) pero hete aquí que dicho valor ya no es el original sino que ha sido sobrescrito. Esto significa que, con un poco de ingenio y paciencia podemos hacer saltar la ejecución del programa ha cualquier punto de la memoria que deseemos, incluyendo código que nosotros mismos hayamos preparado. Para ello bastará con calcular donde está el valor de retorno (o no) y además, en nuestra entrada introducir el código (en hexadecimal con los códigos máquina correspondientes) que queremos que se ejecute.

Esto es un vistazo por encima de como explotar la vulnerabilidad cuando el desbordamiento se produce en la pila, aunque también es posible explotarla cuando los datos estan ubicados en el heap (aunque en este caso es más difícil).

Ampliar información:

Buffer overflow (inglés) en la Wikipedia

Buffer overflow en Bulma Con ejemplos en ensamblador y muy bien explicado.

5
Average: 5 (1 vote)
Your rating: None