Explotación del Heap a través de la macro UNLINK (TUT DESAROLLO EXPLOITS)

Iniciado por l3x4, 1 Enero 2018, 03:37 AM

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

l3x4

https://l3x4overflow.wordpress.com/ - Post completo también disponible en mi blog sobre explotación de software y seguridad informática

Es mi primer post , espero que os guste y sea interesante ya que trata de un tema complicado y que no se encuentra mucha información , aún menos en nuestro idioma.

INTRODUCCIÓN

LLevo tiempo interesandome por la explotacion de software , pero la mayoría del material de estudio está en inglés , entonces decidí hacer una pequeña explicación
de como funciona un método de explotación para el heap que se basa en como atacar el mismo algoritmo para ganar codigo de ejecucion arbitrario.

Para realizar la explicación y posterior explotación me he basado en el binario heap3 del CTF Protostar : https://exploit-exercises.com/protostar/ , que introduce de una
manera básica este tipo de vulnerabilidades , pero aunque sea básica sigue siendo dificil de entender en un principio y más si esta publicado a través de una lengua
extranjera, por eso decidí en crear este post para intentar aclarar este método de explotación para las personas que quieran iniciarse en este mundo.

Como bien dice la descripción del binario a explotar , el objetivo del challenge es explotar el heap a través de un fallo del algoritmo de dlmalloc que nos permita ejecución
de codigo arbitraria y ser capaces de ejecutar la función winner.

EXPLICACIÓN Y EXPLOTACIÓN DEL BINARIO

DATO: (Es recomendable que os decargueis este CTF Protostar desde https://exploit-exercises.com/protostar/ y sigais conmigo la explotación del binario , utilizo putty para
conectarme a través de ssh la maquina virtual de protostar)

Primero vamos ver y explicar el codigo del programa:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  char *a, *b, *c;

  a = malloc(32);
  b = malloc(32);
  c = malloc(32);

  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);

  free(c);
  free(b);
  free(a);

  printf("dynamite failed?\n");
}


Es un código muy sencillo:
1- Primero crea 3 (a, b, c) punteros a los que les asignará una memoria igual para los tres en el heap , a través de malloc.

2- A través de la función strcpy se almacenan los tres primeros argumentos dentro de estos tres punteros , que si recordamos apuntan a un espacio de memoria del heap, por
ende los argumentos que pongamos se guardaran dentro de ese espacio apuntado.

3- Libera con "free" estos tres bloques (o chunks) de memoria en orden inverso.

4- Finalmente escribe un texto con la función printf en la que nos confirman que no hemos conseguido pasar la prueba.

Ahora que ya sabemos como funciona el programa , ejecutemoslo a través de GDB para tener una vision mas clara de como se almacena la memoria en el heap.





Vamos a ver el código de la función main en ensamblador:





Vamos a hacer un break en las diferentes direcciones de memoria que he señalado con una flecha , estas direcciones coinciden con las tres funciones "strcpy" , donde podremos
ver como se almacena la memoria en el heap , despues las tres direcciones de "free" donde veremos como actuan estos bloques de memoria al ser liberados y finalmente la
direccion de puts.

PRIMER STRCPY (a)

(gdb) break *0x080488d5

SEGUNDO STRCPY (b)

(gdb) break *0x080488ed

TERCER STRCPY (c)

(gdb) break *0x08048905

------------------------
PRIMER FREE (c)

(gdb) break *0x08048911


SEGUNDO FREE (b)

(gdb) break *0x0804891d

TERCER FREE (a)

(gdb) break *0x08048929
-----------------------

BREAK EN PUTS

(gdb) break *0x08048935

Ahora ejecutemos el programa con tres argumentos que utilizaremos "A" "B" y "C" para diferenciar cada bloque de memoria :

(gdb) r AAAAAAAAAAAAA BBBBBBBBBBBBBB CCCCCCCCCCCCCC

Utilizamos el siguiente comando para averiguar la dirección de inicio del heap:

(gdb) info proc map





La dirección señalada es la dirección de memoria donde comienza el heap , entonces para poder ver los datos almacenados en ella utilizamos:

(gdb)x/64xw 0x804c000

Saldrá algo como esto:





Como podeis ver hay algunos numeros escritos ya dentro de la memoria por ejemplo el 0x00000029 que ya hablaremos mas tarde de el , pero explicando por encima ese numero
indica los bytes que mide cada chunk (bloque de memoria) que como ya visteis en el codigo la funcion malloc guarda un espacio de 32 bytes para cada bloque de memoria , ahi
estan presentes los tres bloques , el porque de que el tamaño del bloque sea 0x29 y no 0x32 (como fue asignado en malloc) es porque la dirección 0x00000000 que se encuentra
anteriormente a 0x00000029 también forma parte del bloque de memoria , sumando un total de 28 + 4 = 32 bits como fue asignado en malloc (28 no 29 , porque el ulitimo bit es
especial pero ya hablaremos mas tarde de eso). De todas formas aclararemos dudas más adelante , esto solo era para introducir como se asigna el tamaño de memoria a los
diferentes chunks asignados.

Utilicemos el comando "c" en gdb y volvamos a ver la memoria del heap con otra vez x/64xw 0x804c000:




Como veis los "A" , que son 41 en hexadecimal, se han almacenado en nuestro primer chunk , que como ya dijimos anteriormente , tiene un tamaño de 29 bytes. Lo que voy a
decir ahora solo es para que os hagais una idea que como funciona el heap, entonces si a la dirección de memoria donde esta almacenado el 0x00000029 (0x804c004) le sumas
0x29 , llegarás al espacio de memoria donde se encuentra el siguiente bloque , donde almacenaremos las "B" :

Utilicemos el comando "c" dos veces

(gdb) c
(gdb) c


Y volvamos a mirar el heap:





Como podeis ver ahora se han almacenados nuestros tres argumentos "A", "B" y "C" dentro de los 3 diferentes chunks con un tamaño de 0x29.

Ahora veamos lo que pasa cuando liberamos los tres bloques de memoria , para eso pulsemos utilicemos el comando "c" tres veces.

(gdb) c
(gdb) c
(gdb) c


Miremos el heap.




¿Que ha pasado? ¿A donde apuntan o que son esas direcciones de memoria que han sido reemplazadas?

Pues justamente la respuesta a esa pregunta es lo que va a resolver en buena parte la explotación de este binario:

Todos los espacios liberados a través de free son guardados a través de bins (conjunto de bloques de memoria no asginados , es decir libres), estos bins se pueden organizar
de diferentes formas pero la que nos interesa a nostros son las listas doblemente enlazadas , cuando estos bins tienen un tamaño superior a 80 bytes comienzan a organizarse
a través de listas doblemente enlazadas , es decir que poseen un puntero que apunta al siguiente bloque de memoria libre (fd) y poseen otro puntero que apunta al anterior
bloque de memoria libre (bk) y todos estos bloques de memoria libres enlazados forman un bin.

¿Entonces , que son esas direcciones de memoria extrañas reemplazdas?. Para responder , primero hay que diferenciar la estrcutura de un chunk que ha sido asignado mediante
malloc y otro chunk que ha sido liberado mediante free.

Como ya hemos dicho antes , un chunk asginado posee la dirección de memoria 0x29 que indica su tamaño , pero un bloque de memoria asignado posee mucho más que eso , por
ejemplo , la dirección anterior vacia (0x00000000) que es?

Estructura de un chunk asignado:




Como se puede apreciar en la imagen esos 0x00000000 es el tamaño del bloque asignado anterior , después tenemos el tamaño del mismo bloque asignado (0x29) y despues tenemos
el espacio de datos donde se almacenan nuestras "A", "B" y "C", así es la estructura de todos los chunks asignados.

En cambio, cuando un chunk ha sido liberado su estructura es diferentes , y ahora es cuando descubrireis cuales son esas direcciones de memoria extrañas que han aparecido en
cada bloque de memoria después de su liberacion:

Estructura de un chunk liberado:




Un chunk liberado al igual que uno asginado también dispone del tamaño del anterior chunk y el tamaño del chunk actual, pero por otro lado aparecen dos nuevas variables
justo despues de size , fd y bk, ¿que son estas variables?

Se trata de dos punteros , que ya hemos hablado anteriormente , cuando si liberan los chunks entran a formar parte de un bin a través de listas doblemente enlazadas,
entonces el puntero fd es el que apunta al siguiente chunk libre de la lista y bk es el que apunta al anterior y se ubican justo despues de size.

Es decir primero tendriamos prev_size (tamaño del anterior chunk), size (tamaño del chunk actual) y después los punteros fd y bk.

Podemos afirmar entonces que esas dos direcciones de memoria que señale en rojo apuntan al anterior chunk libre , echad un vistazo y os daréis cuenta que la primera
dirección de memoria señalada en rojo apunta al proximo chunk , y la segunda direccion de memoria señalada apunta al tercer chunk.

Entonces , pensemo un poco de lo que nos podríamos aprovechar:
En el codigo del programa se puede apreciar una tremenda vulnerabilidad en strcpy que no asigna un valor minimo para copiar en el heap , algo que puede provocar un buffer
overflow en el heap siendo capaces de sobreescribir en los diferentes chunks , y después tenemos esos 2 punteros , fd y bk , que al ser capaces de sobreescribir la memoria
del heap , entonces también seremos capaces de sobreescribir estos dos punteros.

Pero , ¿que conseguimos al tener el control de estos dos punteros , fd y bk?
Para ello hay que entender como funciona un macro dentro del algoritmo del heap , que se llama unlink (lo siento mucho pero ahi viene mas teoria) este macro afirma lo
siguiente:

Si un bloque pendiente a ser liberado se encuentra de manera adyacente a otro que ya está liberado , el segundo ya liberado será extráido del bin en el que estaba (formando
parte de esa lista doblemente enlazada de chunks liberados) y se juntará con el chunk adyacente pendiente a liberar , formando un chunk libre aun mayor que se enlazará a su
bin correspondiente.

Esta es la macro unlink:

#define unlink(p, bk, fd)
{
BK = p->bk;
FD = p->fd;
FD->bk = BK;
BK->fd = FD;
}

Ahora definamos esta macro matematicamente:
Si tenemos en cuenta de que dentro de la estructura de un chunk liberado prev_size corresponde a +0:

ptr->prev_size = *(ptr).prev_size = *ptr + 0
ptr->size = *(ptr).size = *ptr + 4
ptr->fd = *(ptr).fd = *ptr + 8
ptr->bk = *(ptr).bk = *ptr + 12


Entonces podemos resumir el macro de unlink a lo siguiente:

BK = p + 12;
FD = p + 8;
FD + 12 = BK;
BK + 8 = FD;


Entonces imaginemos que somos capaces de controlar los dos punteros , y dentro hacemos que "fd" apunte a una dirección de memoria de GoT (global offset table) - 12 , ponemos
-12 porque como habreis visto en la macro le suma bk , que es 12, entonces seguimos , almacenamos la direccion de GoT - 12 dentro de fd y almacenamos la direccion de nuestro
shellcode dentro de bk, quedaría así:

BK = &shellcode;
FD = &gotadress - 12
&gotadress + 12 = &shellcode;


Y ya seriamos capaces de ejecutar winner a través de nuestra shellcode.

Ahora centremonos en como vamos a organizar el heap para explotar esta vulnerabilidad en unlink y hacernos con el control de fd y bk , a y se me olvidaba , ¿que funcion
utilizaremos para reemplazar en la GoT? Pues muy sencillo , la última funcion que se ejecuta en el binario que es printf , sustituiremos su direccion de memoria en la GoT
por la dirección de memoria de nuestra shellcode , que a su vez ejecutará la función winner.

Entonces organicemos una estructura del heap para nuestro exploit, como ya dije antes se necesitan dos chunks libres adyacentes , para que se unan formando uno mayor y
**importante** uno de los chunks tiene que tener un tamaño mayor que 80 bytes porque como ya dijimos antes también ,solamente se utilizan listas doblemente enlazadas cuando
el tamaño del chunk es mayor de 80 y el binario es vulnerable a un buffer overflow en el heap lo utilizaremos para cambiar el size del segundo chunk a un mayor que el que
tiene , como por ejemplo 0x65 , que en decimal es 100 , evidentemente , mayor que 80.Pero , ¿cual será el siguiente chunk libre? Tendremos que crear un segundo chunk falso
libre donde se almacenarán los punteros falsos fd y bk.

Entonces organicemonos, en el primero chunk pondremos nuestra shellcode y lo utilizaremos para hacer un overflow al segundo chunk y cambiar su size a 0x65 , un valor mayor
que 80 , cogeremos la dirección de memoria donde esta la size del segundo chunk y le sumaremos 0x65 para averiguar donde se localiza el tercer chunk , el cual le cambiaremos
el size , como al anterior a través de un overflow, a fffffffc, pero ¿porqué fffffffc? Porque el ordenador trata estos numeros que en decimal son una cantidad impresionante
como numeros negativos , entonces fffffffc que es una cifra barbara el ordenador lo tratará como un numero negativo , en este caso, -4.También hay que especificar que el
ultimo bit de 0x65 esta asigdnado , es decir es 1 porque comprueba si el bloque anterior esta libre o no entonces en 0x65 lo ponemos a 1 indicando que el primer chunk esta
asignado, en cambio en fffffffc el ultimo bit es 0 porque queremos que el tercer chunk actué como libre para que se llame a la macro unlink.

Entonces pasemos a la acción, utilizare python como lenguaje scripting para explotar el programa.

Primero averigüemos la dirección de winner , la direccion de GoT y la dirección donde pondremos la shellcode.

La direccion de winner la podemos encontrar facilmente con el siguiente comando:

(gdb)x winner





Apuntemos la dirección - winner= 0x8048864

Ahora busquemos la direccion GoT de puts (printf)

Utilizamos el comando disas main en gdb para localizar la direccion de salto de la función puts:




Ahora desensamblamos esa direccion:




Y como podemos ver al examinar esa direccion de memoria nos encontramos que es la direccion de puts:




Pero como dije en el apartado anterior debemos restarle a esa direccion de GoT 12 , porque más tarde a través de la macro unlink enseñada anteriormente al ser cuando esta
direccion es asignada a FD->bk lo que hace matematicamente es sumarle 12, entonces le sumará doce a la direccion que hemos restado 12 dando como resultado la direccion de
memoria que queremos.

Ahora , finalmente necesitamos nuestra shellcode que ejecutara la función winner y su posición dentro del heap.
Lo pondremos en la parte señalada en a siguiente imagen:




No pondremos la shellcode despues de 0x29 porque en las dos direcciones de memoria posteriores se almacenaran los punteros de fd y bk después del free entonces borraria
parte de nuestra shellcode, en cambio en la posición enseñada no hay riesgo de que sea sobreescrita por un elemento sorpresa.

Entonces la explotacion de este binario consistirá en las siguientes partes:

1º- En el primer chunk (primer strcpy) pondremos nuestra shellcode que será:

mov eax, 0x8048864 (dirección de memoria de la funcion winner)
call eax

2º- En el segundo chunk (segundo strcpy) llenaremos todo el chunk de B hasta llegar al size del tercer chunk que lo reemplazaremos por 0x65 , que como dijimos anteriormente
debe ser de un tamaño superior a 80 bytes para que pueda ser incorporada a un bin con listas doblemente enlazadas, donde se encontrarían los punteros fd y bk y llamaría a la
ejecución de unlink.En conclusión , utilizaremos este chunk para reemplazar el size del tercer chunk a 0x65 que son 101 bytes , mayor que 80 obviamente.

3º-Lo que haremos será sumar a la direccion de memoria que contiene el size del tercer chunk (0x65) el mismo valor que su size es decir 0x65 para localizar el size del
proximo segmento de memoria donde crearemos un falso chunk modificando ese size por fffffffc , que recordamos que era -4, size de este chunk falso coincidirá con el size del
anterior chunk.Recordemos también que el ultimo bit de fffffffc es 0,quiere decir que el chunk anterior es libre , no esta asginado , permitiendo asi la llamada a unlink.

4º-Y finalmente, colocaremos en nuestro chunk falso los punteros de fd y bk.
fd - apunta a la direccion GoT - 12
bk - apunta la dirección de nuestro shellcode

Comencemos con nuestro exploit.

Primero necesitamos nuestra shellcode , para hacerlo mas rápido he utilizado esta página https://defuse.ca/online-x86-assembler.htm saca automáticamente el shellcode del código introducido en ensamblador:




Ahora creamos un pequeño script en python para introducir ese shellcode en la direccion enseñada antes:

$(python -c 'print "A"*12 + "\xB8\x64\x88\x04\x08\xFF\xD0"')





Como podemos ver la direccion donde empieza nuestra shellcode es 0x804c014, recordemosla porque la utilizaremos mas tarde.

Ahora necesitamos otro script en python para utilizar el 2 strcpy y remplazar el size del tercer chunk por 0x65:

$(python -c 'print "B"*36 + "\x65"')





Como se puede apreciar en la imagen hemos logrado sobreescribir el 0x29 del tercer chunk por 0x65

Ahora localicemos donde esta el proximo chunk sumando a la direccion de memoria donde esta el size del segundo chunk 0x65:





Podemos ver que el size del siguiente chunk esta localizado en 0x804c0b9, entonces sobreescribimos el buffer hasta llegar a esa direccion y lo reemplazamos por fffffffc con
el objetivo de crear un chunk en una posición -4 donde pondremos también un size de fffffffc , evidentemente con el ultimo bit en 0 para indicar que el anterior chunk no
esta asginado , es libre , y finalmente pondremos las direcciones de los punteros fd y bk , que són 0x804b11c (GoT_PUTS - 12) y 0x804c014 (direccion de shellcode)
Script en pyhton:

$(python -c 'print "C"*92 + "\xfc\xff\xff\xff" + "\xfc\xff\xff\xff" + "\x1c\xb1\x04\x08" + "\x14\xc0\x04\x08"')

Ahora solo nos queda ejecutar el programa con los tres scripts como argumento , y por fin , ya habremos explotado el binario gracias a haber engañado a la macro unlink.





Asi quedaría organizado el heap despues de estos tres argumentos:





Y al final aqui tenemos nuestra funcion winner ejecutada:





Tambíen se puede ejecutar sin GDB dando el mismo resultado:





Bueno , aqui ha finalizado este post , espero que os haya gustado o que por lo menos hayais aprendido y comprendido como funcionan este tipo de ataques a unlink aunque
actualmente ya no son utilizados ya que la libreria de glibc fue parcheada implementando la comprobación de double free que impedía este tipo de ataques , pero de todas
formas comprendiendo este tipo de explotación te ayudará a entender ataques mas avanzados.

Se que el post me ha quedado bastante largo pero creo que merece la pena para explicar una técnica muy importante dentro de la explotación del heap , una información que no
he encontrado en español, a si que muchas gracias por visitar el post , y cualquier duda será respondida.

Sources:
Recomendaría que os leyerais estos artículos que dejaré a continuación para un mayor entendimiento del ataque:

http://phrack.org/issues/57/9.html ONCE UPON A FREE()

Un artículo impresionante que cubre todo lo relacionado con esta ténica, dentro de una de las magazines mas prestigiosos de explotación de software , vale mucho la pena leer
unos cuantos artículos , muy recomendado aunque se debe tener una buena base de inglés y explotación para entender.

https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/ Heap overflow using unlink

Explica con detalle como funciona la explotación a la macro unlink con diferentes diagramas que permiten un mejor entendimiento de lo sucedido

***ALGUNAS DE LAS IMAGENES PRESENTADAS SOBRE LA ORGANIZACIÓN Y ESTRUCTURA DEL HEAP LAS HE SACADO DE DICHA PÁGINA****

Bueno , pues ya hemos llegado al final , muchísimas gracias a las personas que han visitado al post y sobre todo a aquellas que han tomado su tiempo para leer toooodooo el
post jajaj.

Y por cierto agradecería que si publicais este post en otra parte que guardarais los créditos a mi : https://l3x4overflow.wordpress.com/ y a los resources escritos.