The House of Force
House of Force é uma técnica de exploração de heap que permite alocar um chunk em um endereço arbitrário ao corromper o top chunk. O ataque manipula o campo size do top chunk para enganar o malloc e fazer ele acreditar que há espaço suficiente para qualquer alocação.
O top-chunk (chunk mais ao topo na memória alocada na heap) é similar a qualquer outro chunk alocado por malloc: possui um header seguido de uma seção de dados. A diferença mais importante é que ele beira o fim da memória disponível e é o único chunk que pode ser extendido ou reduzido. A implementação do malloc trata o top chunk como um caso especial.
Pré requisitos do ataque
- Heap overflow ou primitiva de escrita arbitrária para corromper o
sizedo top chunk - Controle sobre o tamanho de futuras alocações
malloc() - Leak de heap e libc
Estrutura do top chunk
O top chunk (também chamado de wilderness) é o último chunk no heap. Quando malloc() precisa de memória e não há chunks adequados nas bins, ele usa o top chunk.
Heap:
┌─────────────┐
│ chunk 1 │
├─────────────┤
│ chunk 2 │
├─────────────┤
│ chunk 3 │
├─────────────┤
│ │
│ TOP CHUNK │ ← remainder (tudo que sobra)
│ │
│ size: 0x123 │ ← metadado que indica quanto espaço resta
└─────────────┘
O modo como malloc() lida com o top chunk (código simplificado):
void *malloc(size_t size) {
int top_chunk_size = top_chunk->size
if (size <= top_chunk_size) {
void *ptr = top_chunk; // posição atual do top chunk
top_chunk += size; // avança o top chunk
top_chunk->size = top_chunk_size - size; // diminui o tamanho restante
return ptr;
}
// senão, expande heap com sbrk/mmap
}
O ataque
- Corromper o
top_chunk->sizecom um valor enorme (geralmente-1ou0xffffffffffffffff). Isso faz omalloc()acreditar que o top chunk tem espaço "infinito". - Calcular a distância até o alvo. Se queremos alocar um chunk em
target_address, então a distância serádistance = target_address - top_chunk_address - 0x10(o0x10é para compensar o header do chunk que será alocado). - Alocar um chunk com tamanho igual à distância calculado.
void *p = malloc(distance). Esta alocação move o top chunk até otarget_address. - Fazer uma segunda alocação. Essa alocação retorna exatamente o
target_address, de modo que qualquer dado inserido no campo de dados desse chunk alocado estará sobrescrevendotarget_address. Escrita arbitrária!
Esse ataque é bem maluco, pois fazemos uma alocação gigantesca para fazer o malloc() levar o top chunk até onde queremos, de modo que a próxima alocação retorne o endereço desejado.
Lembre-se de que o top chunk não é em si um chunk alocado. Ele sempre estará após o último chunk alocado.
Detalhes importantes
- Alinhamento:
malloc()sempre retorna endereços alinhados (múltiplos de 16 bytes em x64). Você precisa calcular odistanceconsiderando isso.
# Se target não está alinhado, ajustar
target_aligned = target & ~0xf # zera últimos 4 bits
distance = target_aligned - top_chunk - 0x10
- Size com Flags: O campo size tem flags nos últimos 3 bits. Ao corromper, use:
evil_size = 0xffffffffffffffff # todos os bits setados
# ou
evil_size = 0xfffffffffffffffe # preserva alinhamento
- Distâncias negativas: Devido ao wraparound (quando uma operação aritmética ultrapassa o valor máxima, ela volta ao início de forma circular. Se o limite é
0x0000000000000000 - 1 = 0xffffffffffffffff), distâncias "negativas" funcionam:
# Se target está ANTES do top chunk no espaço de endereços
distance = (target - top_chunk - 0x10) & 0xffffffffffffffff
# Em C, unsigned arithmetic faz wraparound automaticamente (não precisa do & 0xffffffffffffffff, que mantém apenas os 64 bits inferiores)
Mitigações
- glibc < 2.29: Sem proteções
- glibc ≥ 2.29: Top Chunk Integrity Check
- glibc ≥ 2.32: Safe-linking, dificulta obter leaks necessários.
Top Chunk Integrity Check
A verificação é adicionada:
if ((unsigned long)(size) > (unsigned long)(system_mem) ||
(unsigned long)(size) < MINSIZE) {
malloc_printerr("malloc(): corrupted top size");
}
O que verifica:
sizenão pode ser maior que memória total do sistemasizenão pode ser menor que tamanho mínimo de chunk
Bypass:
- Impossível com
-1(muito grande) - Possível com valores "realistas" mas ainda grandes
- Requer conhecimento preciso de system_mem
Exemplo em código C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
long *target = 0x7ffff7dd1b10; // __malloc_hook (exemplo)
// Passo 1: Alocar chunks
long *chunk_A = malloc(0x10);
long *chunk_B = malloc(0x20);
// Passo 2: Overflow para corromper top chunk
// chunk_B pode escrever além de seu tamanho
long *top_chunk_size = chunk_B + 0x20/8; // offset até top chunk size
*top_chunk_size = -1; // 0xffffffffffffffff
// Passo 3: Calcular distância
long top_chunk = (long)(chunk_B + 0x20/8 + 1); // endereço do top chunk
long distance = (long)target - top_chunk - 0x10;
// Passo 4: Mover top chunk
malloc(distance);
// Passo 5: Alocar no target
long *evil = malloc(0x10);
printf("target address: %p\n", target);
printf("evil address: %p\n", evil);
printf("Match: %s\n", (evil == target) ? "YES!" : "NO");
return 0;
}
Resumo
House of Force = Corromper top chunk size → Fazer malloc "pular" para endereço arbitrário → Escrever no endereço arbitrário
top_chunk->size = -1malloc(target - top_chunk - 0x10)malloc(small_size) → retorna target- Usar o endereço à La Carte!