Sto cercando di capire come funziona l'implementazione di malloc in glibc. Secondo il codice sorgente di malloc (malloc.c in glibc 2.23) i blocchi di memoria liberi hanno la seguente struttura.
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 dovremmo essere in grado di vedere questa struttura anche in gnu debugger (gdb). Così ho scritto il seguente programma. Il programma alloca 6 blocchi di memoria con le dimensioni di 64 byte. Ogni chunk è pieno di memset, quindi possiamo facilmente vedere il blocco successivo in gdb in seguito. Poiché i blocchi 1,3 e 6 vengono liberati, dovrebbero avere la struttura sopra menzionata. Dato che ci sono dei blocchi allocati in mezzo, i blocchi liberati non possono essere consolidati e di conseguenza sono organizzati in un doppio elenco collegato tramite i puntatori di ciascun blocco.
#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");
}
Quando avvio il programma in gdb e imposto un breakpoint in linea strcpy(b4, argv[1]);
dovremmo essere in grado di vedere che i blocchi liberati sono organizzati in un doppio elenco collegato. Tuttavia l'output gdb è il seguente:
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
In questo output possiamo vedere i blocchi liberati e il puntatore posteriore sul blocco liberato precedente (vedi i commenti sul lato destro dell'output). Ma dove sono i puntatori in avanti e le dimensioni del blocco precedente?