Red de conocimiento informático - Conocimiento sistemático - Clasificación de desbordamiento

Clasificación de desbordamiento

El buffer es una memoria continua solicitada por el usuario en la computadora cuando el programa se está ejecutando. Guarda datos de un tipo determinado. El desbordamiento del búfer es un método de ataque al sistema común y dañino. Al escribir algo más allá de su longitud en el búfer del programa, el búfer se desborda, destruyendo así la pila del programa y provocando que el programa ejecute otras instrucciones para lograr el propósito del ataque. Lo que es más grave es que los ataques de desbordamiento de búfer representan la gran mayoría de los ataques a redes remotas, lo que puede brindar a los usuarios anónimos de Internet la oportunidad de obtener un control parcial o total del host. Debido a que este ataque hace posible que cualquiera obtenga el control del host, representa una amenaza de seguridad extremadamente grave.

El propósito de un ataque de desbordamiento de búfer es interferir con la funcionalidad de un programa con ciertos permisos, permitiendo así al atacante obtener el control del programa. Si el programa tiene permisos suficientes, se controla todo el host. En términos generales, los atacantes atacan el programa raíz y luego ejecutan un código de ejecución similar a "exec(sh)" para obtener el shell raíz. Para lograr este objetivo, el atacante debe lograr los dos objetivos siguientes: organizar el código apropiado en el espacio de direcciones del programa; al inicializar correctamente los registros y la memoria, el programa puede saltar al espacio de direcciones preestablecido para su ejecución. Según estos dos objetivos, los ataques de desbordamiento de búfer se pueden dividir en las tres categorías siguientes.

Clasificación de desbordamiento del búfer

El programa de control se transfiere al código de ataque.

Este método se refiere a cambiar el flujo de ejecución del programa para que salte al código de ataque. El método más básico es desbordar un búfer sin verificación de límites u otras debilidades, interrumpiendo el orden de ejecución normal del programa. Mediante el desbordamiento del búfer, un atacante puede reescribir el espacio del programa adyacente casi con fuerza bruta y saltarse directamente las comprobaciones del sistema.

1.2.1 Registro de activación

Cada vez que se produce una llamada a una función, la persona que llama deja un registro de activación en la pila, que contiene la dirección de retorno cuando finaliza la función. Al desbordar estas variables automáticas, el atacante apunta esta dirección de retorno al código de ataque. Al cambiar la dirección de retorno del programa, cuando finaliza la llamada a la función, el programa salta a la dirección establecida por el atacante en lugar de la dirección original. Este tipo de desbordamiento del búfer se denomina "ataque de destrucción de pila" y es un método común de ataque de desbordamiento del búfer previo al objetivo.

1.2.2 Puntero de función

En lenguaje C, "void (* foo)()" declara una variable foo y su valor de retorno es un puntero a la función void. Los punteros de función se pueden usar para ubicar cualquier espacio de direcciones, por lo que un atacante solo necesita encontrar un búfer de desbordamiento cerca de un puntero de función en cualquier espacio y luego desbordar este búfer para cambiar el puntero de función. En algún momento, cuando el programa llama a una función a través de un puntero de función, ¡el flujo del programa se realiza como lo pretendía el atacante! Un ejemplo de su ataque es el programa Super Probe en sistemas Linux.

1.2.3 Búfer Longjmp (búfer Longjmp)

El lenguaje C contiene un sistema simple de verificación/recuperación llamado setjmp/longjmp. Significa configurar "setjmp(buffer)" en el punto de control y usar "longjmp(buffer)" para restaurar el punto de control. Sin embargo, si un atacante tiene acceso al espacio del búfer, entonces "longjmp(buffer)" en realidad salta al código del atacante. Al igual que los punteros de función, los buffers longjmp pueden apuntar a cualquier lugar, por lo que todo lo que un atacante tiene que hacer es encontrar un buffer desbordado. Un ejemplo típico es Perl 5.003. El atacante primero ingresa al búfer longjmp utilizado para recuperar el desbordamiento del búfer y luego es inducido a ingresar al modo de recuperación, lo que hace que el intérprete de Perl salte al código de ataque.

El tipo más simple y común de ataque de desbordamiento de búfer es combinar la colonización de código y los registros de activación en una sola cadena. El atacante localiza una variable automática desbordada y luego pasa una cadena grande al programa, lo que provoca que el desbordamiento del búfer cambie el registro de activación e incube el código. Este es el modelo para el ataque que señala Levy. Dado que el lenguaje C tradicionalmente solo abre un pequeño búfer para usuarios y parámetros, existen muchos ejemplos de este tipo de ataque de vulnerabilidad.

La colonización de código y el desbordamiento del búfer no tienen que completarse en una sola acción. Un atacante puede colocar código en un búfer sin que éste se desborde. Luego, el atacante transfiere el puntero del programa desbordando otro búfer.

Este método se utiliza a menudo para resolver el problema de que el búfer disponible para el desbordamiento no es lo suficientemente grande.

Si un atacante intenta utilizar código que ya reside, en lugar de portarlo desde fuera, normalmente tiene que parametrizar el código. Por ejemplo, algunos segmentos de código en libc ejecutarán "exec(algo)", donde algo es un parámetro. Luego, el atacante utilizó un desbordamiento de búfer para cambiar los parámetros del programa y utilizó otro desbordamiento de búfer para apuntar el puntero del programa a un segmento de código específico en libc. Durante casi 40 años, el desbordamiento de la memoria ha sido un problema de largo plazo en la historia del desarrollo de software. Como se muestra en el incidente del virus del "equipo rojo", se ha convertido en el "culpable" de los piratas informáticos que atacan las redes corporativas.

Si se ingresan más datos en un campo de los necesarios, se producirá un desbordamiento de datos y el exceso de datos se puede usar como instrucciones para ejecutar en la computadora. Según los equipos de seguridad pertinentes, más del 50% de las vulnerabilidades de seguridad en los sistemas operativos son causadas por desbordamientos de memoria, la mayoría de los cuales están relacionados con la tecnología de Microsoft.

El software de Microsoft está desarrollado para ordenadores de escritorio y el desbordamiento de memoria no causará problemas graves. Las computadoras de escritorio existentes generalmente están conectadas a Internet y el desbordamiento de la memoria proporciona condiciones convenientes para que los piratas informáticos las invadan. El desbordamiento de datos ocurre cuando un tipo de datos excede los límites de tamaño de palabras de la computadora. Hay muchas razones para los problemas de desbordamiento de memoria, tales como:

(1) Usar lenguajes que no sean seguros, como C/C++.

(2) Acceder o copiar el búfer de almacenamiento de manera no confiable.

(3) El búfer de memoria establecido por el compilador está demasiado cerca de la estructura de datos clave. 1. El desbordamiento de memoria es un defecto inherente del lenguaje C o C++. No verifican los límites de la matriz ni la seguridad de tipos. Como todos sabemos, los programas desarrollados en lenguaje C/C++ pueden acceder directamente a la memoria y a los registros porque el código de destino está muy cerca del núcleo de la máquina, lo que mejora enormemente el rendimiento del código del lenguaje C/C++. Siempre que la codificación sea razonable, las aplicaciones C/C++ deben ser mejores que otros lenguajes de alto nivel en términos de eficiencia de ejecución. Pero es más probable que el lenguaje C/C++ provoque un desbordamiento de la memoria. Otros lenguajes también tienen problemas de desbordamiento de memoria, pero muchas veces no es culpa del programador, sino un error en el entorno de ejecución de la aplicación.

2. Cuando la aplicación lee los datos del usuario (posiblemente un atacante malicioso) e intenta copiarlos al búfer de memoria abierto por la aplicación, pero no hay garantía de que el espacio del búfer sea lo suficientemente grande (En En otras palabras, supongamos que el código asigna un búfer de memoria de tamaño n bytes y luego copia más de n bytes de datos en él). El búfer de memoria puede desbordarse. Piénselo, si vierte 16 onzas de agua en un vaso de 12 onzas, ¿qué pasa con las 4 onzas adicionales de agua? ¡Por supuesto que el exterior del vaso estará lleno!

3. Lo más importante es que el búfer de memoria abierto por el compilador C/C++ suele estar adyacente a estructuras de datos importantes. Suponiendo que la pila de una función está inmediatamente después del búfer de memoria, entonces la dirección de retorno de la función almacenada en ella será adyacente al búfer de memoria. En este punto, un atacante malintencionado puede copiar una gran cantidad de datos al búfer de memoria, lo que hace que el búfer de memoria se desborde y sobrescriba la dirección de retorno de la función guardada originalmente en la pila. De esta manera, la dirección de retorno de la función se reemplaza por el valor especificado por el atacante; una vez que se completa la llamada a la función, el código en la dirección de retorno de la función continuará ejecutándose. Y algunas otras estructuras de datos de C++, como tablas v, controladores de eventos de excepción, punteros de función, etc. , puede estar sujeto a ataques similares.

Ejemplos específicos

Piense en: ¿Qué hay de malo en el siguiente código?

void CopyData(char *szData) {

char cDest[32]

strcpy(cDest, datos SZ

); //Procesar cDest

...

}

¡Qué extraño, parece que no hay ningún problema con este código! De hecho, simplemente llamar a CopyData() arriba causará problemas.

Por ejemplo, es seguro utilizar CopyData() de esta manera:

char *szNames[] = {Michael, Cheryl, Blake};

CopyData(SZ name[1]) ;

¿Por qué? Debido a que los nombres en la matriz (Michael, Cheryl, Blake) son todos constantes de cadena, de no más de 32 caracteres, siempre es seguro usarlos como argumentos para strcpy(). Supongamos que el único parámetro szData de CopyData proviene de fuentes de datos no confiables, como sockets o archivos. Dado que a strcpy no le importa la fuente de datos, copiará el contenido de szData carácter por carácter siempre que no encuentre un carácter nulo. En este momento, la cadena copiada en cDest puede exceder los 32 caracteres, lo que hará que el búfer de memoria cDest se desborde; los caracteres desbordados reemplazarán los datos detrás del búfer de memoria; Desafortunadamente, ¡la dirección de retorno de la función CopyData también se encuentra entre ellas! Por lo tanto, al llamar a la función CopyData, el programa recurrirá a la "dirección de remitente" proporcionada por el atacante, ¡cayendo así en la trampa del atacante! ¡Dale un control a la gente, miserable!

Las otras estructuras de datos mencionadas anteriormente también pueden estar sujetas a ataques similares. Supongamos que alguien aprovecha una vulnerabilidad de desbordamiento de memoria para sobrescribir la tabla v en la siguiente clase de C++:

void CopyData(char *szData) {

CFoo foo

char cDest[ 32];

strcpy(cDest, datos SZ);

Fu. init();

}

Al igual que otras clases de C++, la clase CFoo aquí también corresponde a la llamada tabla v, que es una lista que almacena las direcciones de todos los métodos. de una clase. Si un atacante aprovecha una vulnerabilidad de desbordamiento de memoria para robar el contenido de la tabla v, todos los métodos de la clase CFoo, incluido el método Init() mencionado anteriormente, apuntarán a la dirección proporcionada por el atacante, no a la dirección del método en el original. v tabla. Por cierto, incluso si no llamas a ningún método en el código fuente de la clase C++, no puedes considerar que esta clase sea segura, porque necesita llamar al menos a un método interno: ¡el destructor! Por supuesto, si hay una clase que no llama a ningún método, su existencia es cuestionable.