En la seguridad actual de redes y sistemas distribuidos, más del 50% de los exploits más utilizados son desbordamientos de búfer. El ejemplo más famoso es el gusano que aprovechó la vulnerabilidad fingerd en 1988. Entre los desbordamientos de búfer, el más peligroso es el desbordamiento de pila, porque un intruso puede utilizar el desbordamiento de pila para cambiar la dirección del programa de retorno cuando la función regresa, permitiéndole saltar a cualquier dirección. Uno de los peligros es que el programa falle y. Provoca el rechazo del servicio, el otro es saltar y ejecutar un fragmento de código malicioso, como obtener un shell, y luego hacer lo que quieras.
En primer lugar, introduzcamos algunos conceptos relacionados con la pila: hay dos tipos de memoria dinámica, pila y montón. La pila está en el extremo superior de la memoria y el montón está en el extremo inferior de la memoria. Cuando se ejecuta el programa, la pila crece hacia abajo hacia el montón y el montón crece hacia arriba hacia la pila. Por lo general, las variables locales, las direcciones de retorno y los parámetros de función se colocan en la pila.
Dirección baja
Variables locales
Puntero base antiguo
Dirección de retorno
Parámetros de función (izquierda)
Parámetros de función (...)
Parámetros de función (derecha)
Dirección alta
Podemos escribir un pequeño programa de prueba :
#include "string.h"
void test(char *a);
int main(int argc, char* argv[] )
{
char a[] = “hola”;
prueba(a);
devuelve 0;
}
prueba nula(char *a)
{
char* j;
char buf[6] ;
strcpy(buf,a);
printf("&main=%p\n",&main);
printf("&buf=%p \n ",&buf);
printf("&a=%p\n",&a);
printf("&test=%p\n",&test);
for ( j=buf-8;j<((char *)&a)+8;j++)
printf("%p: 0x%x\n",j, *( unsigned char *)j);
}
Main define una cadena hola y luego llama a la función de prueba. En la función de prueba, hay una cadena local de longitud 6 Variable buf. Y luego copie el parámetro de copia a en buf. Aquí, debido a que la longitud de no a es menor o igual que la longitud de buf, no hay desbordamiento de buf.
Luego muestre las direcciones de cada función, parámetro, variable local y la dirección entre la variable de cadena local buf y el parámetro a. Vemos:
&main=0040100A
&buf=0012FF14 <. /p>
&a=0012FF28
&test=00401005
0012FF0C: 0xcc
0012FF0D: 0xcc
0012FF0E: 0xcc
0012FF0F: 0xcc
0012FF10: 0xcc
0012FF11: 0xcc
0012FF12: 0xcc
0012FF13: 0xcc
0012FF14: 0x68 h ¡Esto es buf!
0012FF15: 0x65 e
0012FF16: 0x6c l
0012FF17: 0x6c l
0012FF18: 0x6f o
0012FF19: 0x0 \0
0012FF1A: 0xcc
0012FF1B: 0xcc
0012FF1C: 0x1c aquí está
0012FF1D: 0xff dos
0012FF1E: 0x12 antiguo
0012FF1F: puntero base 0x0, ignórelo
0012FF20: 0x80
0012FF21: 0xff
0012FF22: 0x12
0012FF23: 0x0
0012FF24: 0x34 Esta es la dirección del remitente
0012FF25: 0xb8
0012FF26: 0x40 es muy similar a la dirección principal
0012FF27: ¡0x0 está cerca!
0012FF28: 0x78 Esto es
0012FF29: 0xff parámetro a, es decir,
0012FF2A: 0x12 una cadena
0012FF2B: 0x0 Dirección
0012FF2C: 0xe
0012
FF2D: 0x0
0012FF2E: 0x0
0012FF2F: 0x0
Dado que el compilador de c no realiza la verificación de límites por sí solo, si el contenido en buf es lo suficientemente largo en lugar de hola, entonces es muy probable que se sobrescriba la dirección de retorno original y el programa salte a otros lugares. Para fines de prueba, definimos una función simple eco para permitir que la prueba salte a eco cuando regrese.
Utilice printf("&echo=%p\n",&echo); ya sé que la dirección de echo es 0x0040100f Del ejemplo anterior, ya sé cuántos datos deben cubrirse desde 0012ff14. a 0012ff27. Cuéntalo. ¿Solo lo sabes? 8?1 Vuelve a escribirlo de la siguiente manera:
#include "string.h"
void test(char *a);
void echo() ;
int main(int argc, char* argv[])
{
char a[16]; p>
int i ;
for(i=0;i<16;i++) a='x'; //Cubra la parte sin importancia para llegar a la dirección del remitente
a[16]= 0xf; //La dirección del remitente se reescribe aquí
a[17]=0x10;
a[18]=0x40;
a[19]= 0x00; //Por un lado, el byte alto es exactamente 00 y, al mismo tiempo, 00 es el final de la cadena
test(a); /p>
devuelve 0;
}
prueba nula (char *a)
{
char* j;
char buf[6];
strcpy(buf,a); //El búfer asignado es solo 5, pero el resultado es 19, ¡desbordado!
printf("&main=%p\n",&main);
printf("&buf=%p\n",&buf);
printf ("&a=%p\n",&a);
printf("&echo=%p\n",&echo);
printf("&test=%p\n ",&test);
for ( j=buf-8;j<((char *)&a)+8;j++)
printf("%p: 0x%x \n",j, *(unsigned char *)j);
}
void echo()
{
printf ("jaja!\n");
printf("jaja!\n");
printf("jaja!\n");
printf("jaja!\n");
}
Como resultado, vemos que después de mostrar la dirección, ¡aparece jaja en la función de eco! \njaja! \njaja! \njaja! \n, lo que indica que el salto de desbordamiento fue exitoso, pero el programa falló al final porque echo no pudo encontrar su dirección de retorno. Pero hoy hemos logrado nuestro objetivo de utilizar el desbordamiento de pila para ejecutar otros códigos. La próxima vez les diré cómo combinar el desbordamiento de pila y el código de shell.
Gracias a todos, por cierto, yo también soy principiante, tengo algunos malentendidos, espero que los expertos puedan darme consejos,