En
seguridad informatica
y
programacion
, un
desbordamiento de
bufer
(del
ingles
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 area 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 pertenecian a datos o codigo almacenados en memoria. Esto constituye un fallo de programacion.
[
1
]
En las arquitecturas comunes de computadoras no existe separacion entre las zonas de memoria dedicadas a datos y las dedicadas a programa, por lo que los bytes que desbordan el buffer podrian grabarse donde antes habia instrucciones, lo que implicaria la posibilidad de alterar el flujo del programa, llevandole a realizar operaciones imprevistas por el programador original. Esto es lo que se conoce como una vulnerabilidad.
Una vulnerabilidad puede ser aprovechada por un
usuario malintencionado
para influir en el funcionamiento del sistema. En algunos casos el resultado es la capacidad de conseguir cierto nivel de control saltandose las limitaciones de seguridad habituales. Si el programa con el error en cuestion tiene privilegios especiales constituye en un fallo grave de seguridad.
Se denomina
shellcode
al codigo ejecutable especialmente preparado que se copia al host objeto del ataque para obtener los privilegios del programa vulnerable.
La capacidad de los procesadores modernos para marcar zonas de memoria como protegidas puede usarse para aminorar el problema. Si se produce un intento de escritura en una zona de memoria protegida se genera una
excepcion
del sistema de acceso a
memoria
(
segfault
), seguido de la terminacion del programa. Por desgracia para que esta tecnica sea efectiva los programadores han de indicar al sistema operativo las zonas que se necesita proteger, programa a programa y rutina a rutina, lo que supone un problema para todo el codigo heredado.
Existen diferentes tipos de proteccion contra ataques de desbordamiento de bufer. Estos incluyen sistemas como StackGuard que identifica el comportamiento de los ataques y los verifica de manera eficiente.
[
2
]
Descripcion tecnica
[
editar
]
Un desbordamiento de bufer ocurre cuando los
datos
que se escriben en un bufer corrompen aquellos datos en
direcciones de memoria
adyacentes a los destinados para el bufer, debido a una falta de
validacion
de los datos de entrada. Esto se da comunmente al copiar
cadenas de caracteres
de un bufer a otro.
Ejemplo basico
[
editar
]
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 numero 3 (cada caracter se representa mediante un byte).
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
3
|
Buffer A
|
Buffer B
|
A continuacion, 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 queria cambiar el contenido del bufer B, el valor de este ha sido reemplazado por un numero equivalente a parte de la cadena de caracteres. Para este ejemplo, en un sistema
big-endian
que use
ASCII
, el caracter 'o' seguido del byte nulo equivale al numero 28416.
Si B fuese la unica variable aparte de A definida en el programa, la escritura de datos que sobrepasen los limites de B generarian un error como
segmentation fault
, concluyendo asi el programa.
Codigo fuente de ejemplo
[
editar
]
En el siguiente ejemplo se presenta un
codigo fuente
en
C
con un error de programacion. Una vez compilado, el programa generara un desbordamiento de buffer si se lo invoca desde la linea de comandos con un argumento lo suficientemente grande, pues este argumento se usa para llenar un buffer, sin validar previamente su longitud.
[
3
]
/* 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 provocaran desbordamiento de buffer. Por el contrario, strings de 10 caracteres o mas, si: Esto siempre es incorrecto, aunque no siempre resultara en un error del programa o
segmentation fault
.
Este programa puede reescribirse en forma mas segura usando la funcion
strncpy
de la siguiente manera:
[
3
]
/* mejor.c - demuestra un metodo 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
[
editar
]
Uno de los primeros aprovechamientos de los que hay registro de desbordamiento de buferes fue en 1988. Fue uno de los muchos que uso el
gusano
Morris
para propagarse en Internet. El programa abusado fue un
servicio
de
Unix
llamado
fingerd
.
[
4
]
Mas tarde, en 1995, Thomas Lopatic redescubrio en forma independiente el desbordamiento de bufer y publico sus descubrimientos en la
lista de correo
sobre seguridad
Bugtraq
.
[
5
]
Un ano despues, en 1996,
Elias Levy
(conocido tambien como Aleph One) publico en la revista
Phrack
su articulo "Smashing the Stack for Fun and Profit",
[
6
]
una introduccion paso a paso para aprovecharse de vulnerabilidades de desbordamientos de bufer basados en la
pila
.
Desde entonces, por lo menos dos de los gusanos mas importantes de Internet se han aprovechado de los desbordamientos de bufer para comprometer un gran numero de sistemas. En 2001, el gusano
Code Red
se aprovecho de un desbordamiento de bufer en el
Internet Information Services
(IIS) 5.0 de
Microsoft
[
7
]
y en 2003 el gusano
SQL Slammer
comprometio maquinas corriendo
Microsoft SQL Server 2000
.
[
8
]
Stack smashing
[
editar
]
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 aplicacion, o provocar su terminacion. Esto sucede cuando, por algun error imprevisto, se ingresa a la
pila
de la aplicacion mas datos que los que esta puede contener, lo que provoca que esta se "desborde" y algunos datos se sobreescriban. Para evitar que suceda esto, los compiladores se mejoran cada dia dejandole tiempo al programador para pensar en lo que realmente importa.
La siguiente seria una explicacion de que es, como se hace y como se previene el desbordamiento de pila, con el valor agregado de posibilitar una nueva herramienta que ayudara a explicar errores comunes de programacion. El siguiente ejemplo esta inspirado en la funcionalidad incluida en el nuevo compilador por defecto de la distribucion Debian (en su version inestable):
Este es un ejemplo comun de un programa
C
vulnerable:
#include
<stdio.h>
#include
<string.h>
int
main
(
int
argc
,
char
*
argv
[]
)
{
// Buffer estatico 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 estatico. Este es un error de programacion clasico, si este programa fuese compilado a un ejecutable setuid/setgid (ejecutable por cualquiera como si fuese el dueno) permitiria a un atacante ganar permisos facilmente.
Como se van a demostrar las nuevas funciones del nuevo compilador, hay que asegurarse de compilar el ejemplo anterior con gcc-3.3 de la siguiente forma:
user@pc:/tmp$ gcc-3.3 -o buggy buggy.c
Antes de determinar si se puede romper, se deben ejecutar dos pruebas:
user@pc:/tmp$ /tmp/buggy
Uso: /tmp/buggy argumento
user@pc:/tmp$ /tmp/buggy test
Argumento copiado
Ambas funcionan como se espera. Ahora, se ejecuta la prueba pasando un argumento mas largo para determinar si se desborda el buffer estatico:
user@pc:/tmp$ ./buggy `perl -e 'print "X"x2048'`
Argumento copiado
Violacion de segmento
Como se observa, la prueba fue exitosa: el buffer se desbordo con un argumento de mas de 2000 caracteres, resultando en una violacion de segmento. Ahora, un archivo de nucleo puede permitir depurarlo para identificar las direcciones de memoria utilizadas por el proceso:
user@pc:/tmp$ ulimit -c 09999999
user@pc:/tmp$ ./buggy `perl -e 'print "X"x3333'`
Argumento copiado
Violacion de segmento (con archivo de nucleo)
Al correr el depurador
gdb
se puede ver el programa:
user@pc:/tmp$ gdb ./buggy core
GNU gdb 6.4.90-debian
...
El programa finalizo con senal 11, Violacion de segmento
#0 0x58585858 in ?? ()
(gdb) info registers eip
eip 0x58585858 0x58585858
Se puede observar que
EIP
muestra la direccion de memoria de la siguiente instruccion que sera ejecutada (0×58585858). Esto significa que efectivamente se tomo el control del ejecutable con el script malicioso.
El explotar el ejecutable para correr una linea 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
Se utiliza aqui un programa simple para crear un argumento de 2048 bytes de longitud que contiene el codigo requerido para correr una linea de comandos, y luego se ejecuta el programa defectuoso con este argumento construido a medida.
Se produjo el desbordamiento del buffer al correr este codigo, lo cual resulto en la ejecucion de una linea de comandos. Si el programa se hubiese sido ejecutado en Linux con el flag
setuid
por un super-usuario se hubieran ganado los privilegios del super-usuario.
Referencias
[
editar
]
Vease tambien
[
editar
]