¿Qué es el Heap?
A diferencia del stack, el heap es una zona de memoria que puede ser asignada de forma dinámica. Podemos asignar y liberar espacio en casi cualquier momento. Es importante saber que, al contrario que el stack, el heap crece hacia direcciones de memoria crecientes.
Para que nos situemos mejor, he creado un pequeño diagrama que intenta reflejar el espacio de direcciones virtuales en Linux. Este diagrama nos va a ser de gran utilidad para que, en cada momento, sepamos dónde nos encontramos y hacia qué zona queremos desplazarnos. No es lo mismo alocar un chunk para sobreescribir el valor de una dirección que se encuentra en «.data» que sobreescribir una función que se encuentra en la GLIBC.
Nota: Las direcciones de memoria que aparecen son ejemplos. No se deben tomar como rangos fijos.
Malloc Internals
Los chunks son la unidad básica de memoria con la que trabaja Malloc. Están formados por un campo size (8 bytes) y por los datos de usuarios.
Los chunks tienen la siguiente estructura en una arquitectura x86_64:
Allocated Chunks
Código de ejemplo:
Vamos a utilizar ese ejemplo para entender mejor el funcionamiento de los chunks.
Comenzamos desensamblando la función main:
Ahora vamos a establecer un breakpoint en main + 17. En ese punto es cuando se efectúa la primera llamada a malloc:
b *main+17
Ejecutamos el programa y visualizamos los chunks con el comando vis:
Como se puede ver, no hay chunks actualmente porque no se ha llegado a efectuar la llamada a malloc.
Continuamos con la ejecución y visualizamos nuevamente los chunks:
Ahora sí que tenemos datos sobre el chunk reservado. Vamos a analizarlos:
En el primer malloc, hemos reservado 1 solo byte; sin embargo, GDB nos indica que el chunk tiene un tamaño de 0x20 bytes, es decir, 32 bytes en base decimal. De estos 32 bytes, tan solo 8 se utilizan para indicar el tamaño y los 24 restantes son los que almacenan la información. En consecuencia, el tamaño mínimo (usable) de chunk es de 0x20 bytes, de los cuales 0x18 son de datos.
El tamaño de los chunks se incrementa en múltiplos de 16 bytes (malloc está alineado a 16 bytes). Por tanto, tenemos la siguiente secuencia de tamaños: 0x20, 0x30, 0x40, 0x50, etc.
Por consiguiente, si queremos reservar entre 25 y 40 bytes de datos, el tamaño del chunk será de 0x30.
Veámoslo con el código de ejemplo.
Ahora, vamos a establecer un breakpoint en main + 59:
b *main + 59
Arrancamos el programa y visualizamos los chunks:
Efectivamente, se reservan 0x30 bytes para cada una de las siguientes líneas:
char * buf3 = malloc(25); char * buf4 = malloc(40);
Top Chunk
El top chunk se encuentra en la parte superior del heap. Si se asigna más espacio, se desplaza hacia abajo y da el espacio. Si el chunk se libera, se vuelve a recuperar el espacio.
Vamos a verlo con el mismo ejemplo de antes:
Con el primer malloc, el tamaño del top chunk toma el siguiente valor:
Con el segundo malloc, el tamaño del top chunk toma el siguiente valor:
Como se puede ver el tamaño del top chunk actual 0x0000000000020d31 = 0x0000000000020d51 – 0x20
Con el tercer malloc, el tamaño del top chunk toma el siguiente valor:
0x0000000000020d01 = 0x0000000000020d31 – 0x30
Con el tercer malloc, el tamaño del top chunk toma el siguiente valor:
0x0000000000020cd1 = 0x0000000000020d01 – 0x30
Free Chunks
Cuando «liberamos» un chunk a través de free, algunos quadwords que antes se utilizaban para almacenar la información del usuario, comienzan a formar parte de la información de malloc.
El campo size se mantiene, pero el primer quadword que contenía información de usuario, se utiliza como forward pointer y el segundo quadword se utiliza como backward pointer. Estos punteros se utilizan para clasificar los chunks en bins. Por tanto, la estructura es similar a la siguiente imagen:
Nota: hay más quadwords asociados a los datos del usuario que se reutilizan. Los veremos en más detalle en futuros artículos en los que hablemos sobre bins, pero para los más curiosos, en el malloc.c se define la siguiente estructura:
Chunks Flags
Dado que los tamaños que reserva malloc siempre terminan en 0, los últimos 3 LSBs del campo size se usan como flags para indicar estados del chunk. Por tanto, los chunks adquieren la siguiente forma:
NON_MAIN_ARENA (0x04)
Este bit indica si el chunk NO pertenece a la arena principal (main arena).
IS_MMAPPED (0x02)
Este bit indica si el chunk fue alocado usando mmap().
PREV_INUSE (0x01)
Este bit indica si el chunk anterior está en uso. Es importante saber que algunos chunks como los fastbins tienen este bit activado a pesar de haber sido liberados por la aplicación (free).
Para más información podéis consultar la wiki de GLIBC
Referencias
– https://ir0nstone.gitbook.io/notes/types/heap
– https://guyinatuxedo.github.io/25-heap/index.html
– https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk
– https://www.youtube.com/watch?v=s-GJ-buCGio
– https://heap-exploitation.dhavalkapil.com/
Espero que os haya gustado y, lo más importante, que hayáis aprendido.
DiegoAltF4
Inicio
Deja una respuesta