Ubuntu 16.04 - implementazione malloc. Dov'è il puntatore al prossimo pezzo? [chiuso]

0

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?

    
posta Dennis 18.12.2016 - 13:18
fonte

1 risposta

1

A seconda della dimensione del chunk da liberare, i blocchi sono contenuti in diversi tipi di contenitori (liste collegate):

  • Contenitori non ordinati
  • Piccoli contenitori
  • Contenitori grandi

Ti suggeriamo di cercare il codice sorgente se sei interessato a sapere come vengono gestiti questi contenitori. Ma qualcosa di comune tra tutti questi bidoni è che gli elenchi sono con collegamenti doppi . Quindi eri corretto nel presupposto che avresti dovuto trovare sia un puntatore avanti sia un backward nei blocchi liberati (e forse anche la dimensione precedente campo)

Tuttavia, esiste un altro tipo di contenitore speciale noto come fastbin . I pezzi di dimensioni molto piccole (in genere tra 16 e 80 byte, ma possono variare leggermente tra le versioni) vengono conservati in questi fast-bin. A differenza dei normali contenitori, questi sono collegati singolarmente . Sono conservati in un fastbin appropriato in base alla loro dimensione (ciascun contenitore contenente pezzi della stessa dimensione). Invece di dover attraversare l'elenco, è possibile aggiungere e rimuovere blocchi in un ordine LIFO, accelerando le prestazioni. Inoltre, a differenza dei blocchi regolari, i blocchi adiacenti del fastbin non sono consolidati (il che ovviamente si traduce in frammentazione, ma rende più veloce la velocità). Ciò significa anche che non hai bisogno della dimensione del chunk precedente.

Anche i pezzi del tuo programma fanno probabilmente parte di uno di questi fast-hop. Pertanto, per vedere cosa ti aspetti, prova ad allocare e liberare memoria di dimensioni maggiori.

    
risposta data 18.12.2016 - 14:57
fonte

Leggi altre domande sui tag