¿Qué es el desbordamiento del búfer?
Desbordamiento de búfer
El búfer es donde se almacenan los datos en la memoria. Se produce un desbordamiento del búfer cuando un programa intenta colocar datos en una ubicación de la memoria de la computadora, pero no hay suficiente espacio.
Un búfer es un bloque contiguo de memoria de la computadora que contiene datos de un tipo determinado mientras se ejecuta un programa. Surgen problemas con la asignación dinámica de variables. Para utilizar menos memoria, un programa con variables asignadas dinámicamente decide cuánta memoria asignarles solo cuando el programa se está ejecutando. ¿Qué pasará si el programa coloca demasiados datos en el búfer asignado dinámicamente? Se desbordó y goteó por otra parte. Una aplicación de desbordamiento de búfer utiliza los datos desbordados para colocar código en lenguaje ensamblador en la memoria de la computadora, generalmente donde surgen los privilegios de root. El desbordamiento del búfer por sí solo no causa problemas de seguridad. Esto sólo funciona si el desbordamiento se envía a un área donde los comandos se pueden ejecutar con privilegios de root. De esta manera, un exploit de búfer coloca instrucciones ejecutables en la memoria con privilegios de root, de modo que una vez que se ejecutan estas instrucciones, la computadora queda controlada con privilegios de root. Para resumir la descripción anterior. El desbordamiento del búfer se refiere a un método de ataque al sistema. Al escribir contenido más allá de la longitud del búfer del programa, el búfer se desborda, lo que destruye la pila del programa y hace que el programa ejecute otras instrucciones para lograr el propósito. el ataque. Según las estadísticas, los ataques mediante desbordamiento de búfer representan más del 80% de todos los ataques al sistema. La causa del desbordamiento del búfer es que los parámetros ingresados por el usuario no se verifican cuidadosamente en el programa. Por ejemplo, el siguiente programa:
ejemplo0.c
-------------------------- ----------------------------------
función vacía (char *cadena) {
char buffer[16];
strcpy(buffer,str);
}
--------- ---- ---------------------------------------------- ----
El strcpy() anterior copiará directamente el contenido de str al búfer. De esta forma, siempre que la longitud de str sea mayor que 16, el búfer se desbordará y el programa se ejecutará incorrectamente. Las funciones estándar que tienen problemas como strcpy incluyen strcat(), sprintf(), vsprintf(), gets(), scanf() y getc(), fgetc(), getchar(), etc. dentro de un bucle. En lenguaje C, las variables estáticas se asignan en el segmento de datos y las variables dinámicas se asignan en el segmento de pila. El desbordamiento del búfer utiliza el desbordamiento del segmento de la pila. Un programa generalmente se divide en tres partes en la memoria: segmento de programa, final de datos y pila. El segmento del programa contiene el código de máquina y los datos de solo lectura del programa. Este segmento suele ser de solo lectura y escribir en él es ilegal. El segmento de datos contiene datos estáticos en el programa. Los datos dinámicos se almacenan a través de la pila. En la memoria, sus ubicaciones son las siguientes:
/――――――――\ Extremo inferior de la memoria
|Sección de programa|
|― ― ――――――――|
|Segmento de datos|
|―――――――――|
|Pila|
\―――――――――――/Extremo superior de la memoria
La pila es un bloque continuo en la memoria. Un registro llamado puntero de pila (SP) apunta a la parte superior de la pila. La parte inferior de la pila es una dirección fija. Una característica de la pila es que es el último en entrar, el primero en salir. En otras palabras, los datos ingresados más tarde se eliminan primero. Admite dos operaciones, PUSH y POP. PUSH es colocar los datos en la parte superior de la pila y POP es tomar los datos de la parte superior de la pila. En lenguajes de alto nivel, las llamadas a funciones de programas y las variables temporales en funciones utilizan la pila. La pila también se utiliza para la transferencia de parámetros y los valores de retorno. Normalmente las referencias a variables locales se hacen dando sus compensaciones al SP.
También hay un puntero base (FP, BP en chips Intel), que muchos compiladores utilizan para referirse a variables y parámetros locales. Normalmente, las compensaciones relativas a la FP son positivas para los parámetros y negativas para las variables locales. Cuando se produce una llamada a una función en el programa, la computadora hace lo siguiente: primero inserta los parámetros en la pila; luego guarda el contenido del registro de instrucciones (IP) como la dirección de retorno (RET); el registro de dirección base (FP); luego copie el puntero de pila actual (SP) a FP como la nueva dirección base; finalmente deje un cierto espacio para las variables locales y reste el valor apropiado de SP.
Aquí tienes un ejemplo:
ejemplo1.c:
-------------------- ------ ----------------------------------------
función vacía (int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
función(1,2,3);
}
------- ------ -------------------------------------------- ------
Para comprender cómo el programa llama a function(), use la opción -S En Linux, compile con gcc para producir una salida de código ensamblador:
$ gcc -S -o ejemplo1.s ejemplo1.c
Mire la parte del archivo de salida que llama a la función:
pushl $3
pushl $2
pushl $1 p>
función de llamada
Esto empuja los tres parámetros a la pila y llama a la función(). La llamada de instrucción empujará la IP del puntero de instrucción a la pila. Al regresar, RET utilizará esta IP guardada. En la función, lo primero que debe hacer es realizar algún procesamiento necesario. Cada función debe tener estos procedimientos:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
Estos Las instrucciones colocan EBP, el puntero de dirección base, en la pila. Luego copie el SP actual a EBP. Luego, se asigna espacio para las variables locales y su tamaño se resta del SP. Dado que la asignación de memoria se realiza en unidades de palabras, el buffer1 aquí usa 8 bytes (2 palabras, 4 bytes por palabra). Buffer2 utiliza 12 bytes (3 palabras). Entonces aquí el ESP se reduce en 20. Ahora, la pila debería verse así.
Memoria de gama baja, memoria de gama alta
buffer2 buffer1 sfp ret a b c
< ------ [ ][ ][ ][ ][ ][ ] [ ]
Parte superior de la pila Parte inferior de la pila
El desbordamiento del búfer consiste en escribir demasiados datos en un búfer.
Entonces, cómo usarlo, eche un vistazo
El siguiente programa:
ejemplo2.c
------------- --- --------------------------------------- p>
función vacía (char *str) {
char buffer[16];
strcpy(buffer,str);
} p>
void main() {
char large_string[256];
int i;
for( i = 0; i < 255; i++)
cadena_grande[i] = 'A';
función(cadena_grande);
}
----- ----- --------------------------------------------- -----
Este programa es un clásico error de codificación de desbordamiento del búfer. La función copia una cadena a otra área de memoria sin verificar los límites. Cuando se llama a la función function(), la pila es la siguiente:
Buffer de fin de memoria bajo sfp ret *str Fin de memoria alto
< ------ [ ][ ][ ][ ]
Parte superior de la pila Parte inferior de la pila
Obviamente, el resultado de la ejecución del programa es "Error de segmentación (núcleo volcado)" o un mensaje de error similar. Porque los 256 bytes que comienzan en el búfer serán sobrescritos por el contenido 'A' de *str, incluidos sfp, ret e incluso *str. El valor hexadecimal de 'A' es 0x41, por lo que la dirección de retorno de la función se convierte en 0x41414141, que excede el espacio de direcciones del programa, por lo que se produce un error de segmentación. Como puede ver, el desbordamiento del búfer nos permite cambiar la dirección de retorno de una función. De esta forma se puede cambiar el orden de ejecución del programa.