Ubuntu 16.04 - implementación de malloc. ¿Dónde está el puntero al siguiente trozo? [cerrado]

0

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?

    
pregunta Dennis 18.12.2016 - 13:18
fuente

1 respuesta

1

Dependiendo del tamaño del trozo que se va a liberar, los trozos se guardan en diferentes tipos de contenedores (listas enlazadas):

  • Contenedores sin clasificar
  • Pequeños contenedores
  • Contenedores grandes

Le sugerimos que busque el código fuente si está interesado en saber cómo se mantienen estos contenedores. Pero algo común entre todos estos contenedores es que las listas están doblemente vinculadas . Por lo tanto, tenía razón al suponer que debería haber encontrado un puntero adelante y hacia atrás en los fragmentos liberados (y también quizás el tamaño anterior campo)

Sin embargo, hay otro tipo de contenedor especial conocido como fastbin . Los trozos de un tamaño muy pequeño (generalmente entre 16 y 80 bytes, pero puede variar ligeramente en las distintas versiones) se guardan en estos botes rápidos. A diferencia de sus contenedores regulares, estos son enlazados individualmente . Se mantienen en un contenedor rápido apropiado según su tamaño (cada contenedor contiene trozos del mismo tamaño). En lugar de tener que recorrer la lista, se pueden agregar y eliminar fragmentos en un orden LIFO, lo que acelera el rendimiento. Además, a diferencia de los trozos normales, los trozos de fastbin adyacentes no se consolidan (lo que, por supuesto, resulta en fragmentación, pero se libera más rápido). Esto también significa que tampoco necesita el tamaño del fragmento anterior.

Los fragmentos de su programa también son probablemente parte de uno de estos botes rápidos. Por lo tanto, para ver lo que espera, intente asignar y liberar memoria de un tamaño más grande.

    
respondido por el rhodeo 18.12.2016 - 14:57
fuente

Lea otras preguntas en las etiquetas