Estoy tratando de entender cómo está funcionando la implementación de malloc en glibc. De acuerdo con el código fuente de malloc (malloc.c en glibc 2.23), los fragmentos de memoria libre tienen la siguiente estructura.
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'head:' | Size of chunk, in bytes |P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Normalmente, también deberíamos poder ver esta estructura en el depurador gnu (gdb). Así que escribí el siguiente programa. El programa asigna 6 porciones de memoria con el tamaño de 64 bytes. Cada fragmento está lleno de memset, por lo que podemos ver fácilmente el fragmento correspondiente en gdb más adelante. Debido a que los trozos 1,3 y 6 se liberan, deben tener la estructura mencionada anteriormente. Como hay partes asignadas intermedias, las partes liberadas no se pueden consolidar y, como resultado, se organizan en una lista enlazada duplicada a través de los punteros en cada parte.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void to_jump();
int main(int argc, char **argv){
char *b1, *b2, *b3, *b4, *b5, *b6;
//allocate 6 chunks of memory
b1 = malloc(64);
b2 = malloc(64);
b3 = malloc(64);
b4 = malloc(64);
b5 = malloc(64);
b6 = malloc(64);
memset(b1, 'B', 64);
memset(b2, 'C', 64);
memset(b3, 'D', 64);
memset(b5, 'E', 64);
memset(b6, 'F', 64);
//free chunks 1,3 and 6
free(b1);
free(b3);
free(b6);
strcpy(b4, argv[1]); // <-- set breakpoint here
//exploit this line
free(b4);
free(b5);
free(b2);
}
void to_jump(){
printf("Exploited");
}
Cuando inicio el programa en gdb y establezco un punto de interrupción en la línea strcpy(b4, argv[1]);
, deberíamos poder ver que los fragmentos liberados están organizados en una lista de doble enlace. Sin embargo, la salida de gdb es la siguiente:
gdb-peda$ p b1
$11 = 0x602010 ""
gdb-peda$ x/62xg 0x602000
0x602000: 0x0000000000000000 0x0000000000000051
0x602010: 0x0000000000000000 0x4242424242424242 |
0x602020: 0x4242424242424242 0x4242424242424242 | b1 (freed)
0x602030: 0x4242424242424242 0x4242424242424242 |
0x602040: 0x4242424242424242 0x4242424242424242 |
0x602050: 0x0000000000000000 0x0000000000000051
0x602060: 0x4343434343434343 0x4343434343434343 |
0x602070: 0x4343434343434343 0x4343434343434343 | b2 (allocated)
0x602080: 0x4343434343434343 0x4343434343434343 |
0x602090: 0x4343434343434343 0x4343434343434343 |
0x6020a0: 0x0000000000000000 0x0000000000000051
0x6020b0: 0x0000000000602000 0x4444444444444444 | 0x602000 is pointing to b1 (previous freed block)
0x6020c0: 0x4444444444444444 0x4444444444444444 | b3 (freed)
0x6020d0: 0x4444444444444444 0x4444444444444444 |
0x6020e0: 0x4444444444444444 0x4444444444444444 |
0x6020f0: 0x0000000000000000 0x0000000000000051
0x602100: 0x0000000000000000 0x0000000000000000 |
0x602110: 0x0000000000000000 0x0000000000000000 | b4 (will be filled trough strcpy(b4, argv[1]);
0x602120: 0x0000000000000000 0x0000000000000000 |
0x602130: 0x0000000000000000 0x0000000000000000 |
0x602140: 0x0000000000000000 0x0000000000000051
0x602150: 0x4545454545454545 0x4545454545454545 |
0x602160: 0x4545454545454545 0x4545454545454545 | b5 (allocated)
0x602170: 0x4545454545454545 0x4545454545454545 |
0x602180: 0x4545454545454545 0x4545454545454545 |
0x602190: 0x0000000000000000 0x0000000000000051
0x6021a0: 0x00000000006020a0 0x4646464646464646 | 0x6020a0 is pointing to b3 (previous freed block)
0x6021b0: 0x4646464646464646 0x4646464646464646 | b6 (freed)
0x6021c0: 0x4646464646464646 0x4646464646464646 |
0x6021d0: 0x4646464646464646 0x4646464646464646 |
0x6021e0: 0x0000000000000000 0x0000000000020e21
En esta salida podemos ver los fragmentos liberados y el puntero de retroceso al fragmento liberado anterior (vea los comentarios en el lado derecho de la salida). Pero, ¿dónde están los punteros hacia delante y el tamaño del fragmento anterior?