Desbordamiento de búfer

Desbordamiento de búfer

Desbordamiento de búfer

En seguridad informática y programación, un desbordamiento de buffer (del inglés buffer overflow o buffer overrun) es un error de software que se produce cuando se copia una cantidad de datos sobre un área que no es lo suficientemente grande para contenerlos, sobrescribiendo de esta manera otras zonas de memoria. Esto se debe en general a un fallo de programación. La consecuencia de escribir en una zona de memoria imprevista puede resultar impredecible. Existen zonas de memoria protegidas por el sistema operativo. Si se produce la escritura fuera de una zona de memoria protegida se producirá una excepción del sistema de acceso a memoria seguido de la terminación del programa. Bajo ciertas condiciones, un usuario obrando con malas intenciones puede aprovecharse de este mal funcionamiento o una vulnerabilidad para tener control sobre el sistema.

En algunas ocasiones eso puede suponer la posibilidad de alterar el flujo del programa pudiendo hacer que éste realice operaciones no previstas. Esto es posible dado que en las arquitecturas comunes de computadoras, la memoria no tiene separación entre la dedicada a datos y a programa.

Si el programa que tiene el error en cuestión tiene privilegios especiales se convierte además en un fallo de seguridad. El código copiado especialmente preparado para obtener los privilegios del programa atacado se llama shellcode.

Contenido

Descripción técnica

Un desbordamiento de búffer ocurre cuando los datos que se escriben en un búffer corrompen aquellos datos en direcciones de memoria adyacentes a los destinados para el búffer, debido a una falta de validación de los datos de entrada. Esto se da comúnmente al copiar cadenas de caracteres de un búffer a otro.

Ejemplo básico

En este ejemplo, un programa tiene definidos dos elementos de datos continuos en memoria: un buffer de 8 bytes tipo string, A, y otro de dos bytes tipo entero, B. Al comienzo, A contiene bytes nulos y B contiene el número 3 (cada carácter se representa mediante un byte).

0 0 0 0 0 0 0 0 0 3
Buffer A Buffer B

A continuación, el programa intenta almacenar la cadena de caracteres "demasiado" en el buffer A, seguido de bytes nulos para marcar el fin de string. Al no validarse la longitud de la cadena, se sobrescribe el valor de B:

'd' 'e' 'm' 'a' 's' 'i' 'a' 'd' 'o' 0
Buffer A Buffer B

A pesar de que el programador no quería cambiar el contenido del búffer B, el valor de éste ha sido reemplazado por un número equivalente a parte de la cadena de caracteres. Para este ejemplo, en un sistema big-endian que use ASCII, el carácter 'o' seguido del byte nulo equivale al número 28416.

Si B fuese la única variable aparte de A definida en el programa, la escritura de datos que sobrepasen los límites de B generarían un error como segmentation fault, concluyendo así el programa.

Código fuente de ejemplo

En el siguiente ejemplo se presenta un código fuente en C con un error de programación. Una vez compilado, el programa generará un desbordamiento de buffer si se lo invoca desde la línea de comandos con un argumento lo suficientemente grande, pues este argumento se usa para llenar un buffer, sin validar previamente su longitud.[1]

/* overflow.c - demuestra un desbordamiento de buffer */

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  char buffer[10];
  if (argc < 2)
  {
    fprintf(stderr, "MODO DE USO: %s string\n", argv[0]);
    return 1;
  }
  strcpy(buffer, argv[1]);
  return 0;
}

Strings de 9 caracteres o menos no provocarán desbordamiento de buffer. Por el contrario, strings de 10 caracteres o más sí: esto siempre es incorrecto, aunque no siempre resultará en un error del programa o segmentation fault.

Este programa puede reescribirse en forma más segura usando la función strncpy de la siguiente manera:[1]

/* mejor.c - demuestra un método de resolver el problema */

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  char buffer[10];
  if (argc < 2)
  {
    fprintf(stderr, "MODO DE USO: %s string\n", argv[0]);
    return 1;
  }
  strncpy(buffer, argv[1], sizeof(buffer));
  buffer[sizeof(buffer) - 1] = '\0';
  return 0;
}

Historia de abusos

Uno de los primeros aprovechamientos de los que hay registro de desbordamiento de búfers fue en 1988. Fue uno de los muchos que usó el gusano Morris para propagarse en Internet. El programa abusado fue un servicio de Unix llamado fingerd.[2]

Más tarde, en 1995, Thomas Lopatic redescubrió en forma independiente el desbordamiento de búfer y publicó sus descubrimientos en la lista de correo sobre seguridad Bugtraq.[3] Un año después, en 1996, Elias Levy (conocido también como Aleph One) publicó en la revista Phrack su artículo "Smashing the Stack for Fun and Profit",[4] una introducción paso a paso para aprovecharse de vulnerabilidades de desbordamientos de búfer basados en la pila.

Desde entonces, por lo menos dos de los gusanos más importantes de Internet se han aprovechado de los desbordamientos de búfer para comprometer un gran número de sistemas. En 2001, el gusano Code Red se aprovechó de un desbordamiento de búfer en el Internet Information Services (IIS) 5.0 de Microsoft[5] y en 2003 el gusano SQL Slammer comprometió máquinas corriendo Microsoft SQL Server 2000.[6]

Stack smashing

El pisado de pila o stack smashing es un tipo de desbordamiento de buffer que es aprovechado por algunos virus y otros programas maliciosos para tomar control sobre una aplicación, o provocar su terminación. Esto sucede cuando, por algún error imprevisto, se ingresa a la pila de la aplicación más datos que los que ésta puede contener, lo que provoca que esta se "desborde" y algunos datos se sobreescriban. Para evitar que suceda esto, los compiladores se mejoran cada día dejándole tiempo al programador para pensar en lo que realmente importa.

La siguiente sería una explicación de qué es, cómo se hace y cómo se previene el desbordamiento de pila, con el valor agregado de posibilitar una nueva herramienta que nos ayudará a explicar errores de programación. El siguiente ejemplo está inspirado en la funcionalidad incluida en el nuevo compilador por defecto de la distribución Debian (en su versión inestable):

Miremos, primero que nada, un ejemplo. Este es un ejemplo común de un programa C vulnerable:

#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
// Buffer estático en la pila.
char buffer[1024];
if ( argc != 2 )
{
printf("Uso: %s argumento\n", argv[0] );
return( -1 );
}
// Copiado de cadenas sin control.
strcpy( buffer, argv[1]);
printf( "Argumento copiado\n" );
return(0);
}

Este programa simple acepta un argumento y lo copia a un buffer estático. Este es un error de programación clásico, si este programa fuese compilado a un ejecutable setuid/setgid (ejecutable por cualquiera como si fuese el dueño) permitiría a un atacante ganar permisos fácilmente.

Como vamos a demostrar las nuevas funciones del nuevo compilador, asegúrense de compilar el ejemplo anterior con gcc-3.3 de la siguiente forma:

user@pc:/tmp$ gcc-3.3 -o buggy buggy.c

Veamos ahora si lo podemos romper. Primero dos pruebas de ejecución.

user@pc:/tmp$ /tmp/buggy
Uso: /tmp/buggy argumento
user@pc:/tmp$ /tmp/buggy test
Argumento copiado 

Ambas ejecuciones funcionaron como se esperaba. Ahora probemos pasarle un argumento más largo para ver si podemos desbordar el buffer estático:

user@pc:/tmp$ ./buggy `perl -e 'print "X"x2048'`
Argumento copiado
Violación de segmento

Tuvimos éxito: desbordamos el buffer con nuestro argumento de 2k, lo que resultó en una violación de segmento. Ahora, si podemos producir un archivo de núcleo podríamos debuguearlo:

user@pc:/tmp$ ulimit -c 09999999
user@pc:/tmp$ ./buggy `perl -e 'print "X"x3333'`
Argumento copiado
Violación de segmento (con arhivo de núcleo)

Al correr gdb podremos ver el programa:

user@pc:/tmp$ gdb ./buggy core
GNU gdb 6.4.90-debian
...
El programa finalizó con señal 11, Violación de segmento
#0 0x58585858 in ?? ()
(gdb) info registers eip
eip 0x58585858 0x58585858

Aquí podemos ver que el puntero de instrucciones - registro del procesador que indica qué será lo que se ejecutará a continuación es 0×58585858 (0×58 es ‘X’ en hexadecimal). Esto significa que efectivamente tomamos el control del ejecutable con nuestro script malicioso.

El explotar el ejecutable para correr una línea de comandos habiendo hecho esto es trivial y, por lo general, puede ser automatizado:

user@pc:~/cmd-overflow$ make
gcc-3.3 -o cmd-overflow -Wall -ggdb cmd-overflow.c
gcc-3.3 -o cmd-vuln -Wall -ggdb cmd-vuln.c
user@pc:~/cmd-overflow$ ./cmd-overflow --target=/tmp/buggy --args='%' --size=2048
Argumento copiado
shell-3.1$ id
uid=1000(user) gid=1000(user) groups=29(audio), 44(video), 46(plugdev), 100(users), 1000(user)
shell-3.1$ exit
exit

Utilizamos aquí un simple programa para crear un argumento de 2048 bytes de longitud que contiene el código requerido para correr una línea de comandos, y luego corrimos nuestro programa defectuoso con este argumento construido a medida.

Se desbordó el buffer al correr nuestro código, lo que resultó en la ejecución de una línea de comandos (¿qué es un shellcode? - en español). Si nuestro programa hubiese sido ejecutado en Linux en setuid por un super-usuario ¡¡hubiésemos ganado los privilegios del súper-usuario!!

Referencias

  1. a b Safer C: Developing Software for High-integrity and Safety-critical Systems (ISBN 0-07-707640-0)
  2. "Tour del gusano" por Donn Seeley, Universidad de Utah [1]
  3. Bugtraq security mailing list [2]
  4. "Smashing the Stack for Fun and Profit" by Aleph One [3]
  5. eEye Digital Security [4]
  6. Microsft Technet Security Bulletin MS02-039 [5]

Véase también

Obtenido de "Desbordamiento de b%C3%BAfer"

Wikimedia foundation. 2010.

Игры ⚽ Нужно сделать НИР?

Mira otros diccionarios:

  • Desbordamiento de búfer — Un desbordamiento de búfer (del inglés buffer overflow) es un error de software que se produce cuando se copia una cantidad más grande de datos sobre un área más pequeña sin interrumpir la operación, sobreescribiendo otras zonas de memoria. En… …   Enciclopedia Universal

  • Desbordamiento — Este artículo o sección necesita referencias que aparezcan en una publicación acreditada, como revistas especializadas, monografías, prensa diaria o páginas de Internet fidedignas. Puedes añadirlas así o avisar al auto …   Wikipedia Español

  • Desbordamiento de montículo — En informática, un desbordamiento de montículo (heap overflow/overrun) es un problema aritmético que hace referencia al exceso de flujo de datos sobre un montículo, esto permite un acceso no autorizado a la memoria por parte de un comando o de un …   Wikipedia Español

  • Desbordamiento de pila — En informática, un desbordamiento de pila (stack overflow/overrun) es un problema aritmético que hace referencia al exceso de flujo de datos almacenados en la pila de una función, esto permite que la dirección de retorno de la pila pueda ser… …   Wikipedia Español

  • Subdesbordamiento de búfer — En informática un subdesbordamiento de búfer (Buffer underflow/underrun) es un problema aritmético, esto sucede cuando un búfer carga su información (flujo de datos) a una velocidad más baja que el procesamiento de la misma, esto hace que el… …   Wikipedia Español

  • Exploit — (del inglés to exploit, explotar o aprovechar) es una pieza de software, un fragmento de datos, o una secuencia de comandos con el fin de automatizar el aprovechamiento de un error, fallo o vulnerabilidad, a fin de causar un comportamiento no… …   Wikipedia Español

  • Shellcode — Una shellcode es un conjunto de órdenes programadas generalmente en lenguaje ensamblador y trasladadas a opcodes que suelen ser inyectadas en la pila (o stack) de ejecución de un programa para conseguir que la máquina en la que reside se ejecute… …   Wikipedia Español

  • Bit NX — Este artículo o sección necesita referencias que aparezcan en una publicación acreditada, como revistas especializadas, monografías, prensa diaria o páginas de Internet fidedignas. Puedes añadirlas así o avisar …   Wikipedia Español

  • Pila de llamadas — Estructura de la pila de llamadas. En la figura se ve una pila, creciendo de abajo hacia arriba. La subrutina DrawSquare es llamada y se crea un stack frame para ella (en azul). Luego, DrawSquare llama a la subrutina DrawLine, la cual tiene su… …   Wikipedia Español

  • Programación segura — Saltar a navegación, búsqueda La programación segura es una rama de la programación que estudia la seguridad del código fuente de un software cuyo objetivo es encontrar y solucionar los errores de software, esto incluye: Utilización de funciones… …   Wikipedia Español

Compartir el artículo y extractos

Link directo
Do a right-click on the link above
and select “Copy Link”