67
Categoria: Heap, estilo Notes
Dificuldade: Médio
Proteções: Todas as proteções da Stack, Glibc moderna (Safe-linking, Tcache). Impossível atacar a stack diretamente.
Técnicas: UAF, Vazamento de libc com Tcache e Unsorted Bin, Safe-Linking Bypass, Safe Linking Heap Leak, Tcache Poisoning, Environ (Stack Leak).
Visão inicial
strings libc.so.6 | grep "GNU C Library" -> Ver a versão da libc sendo utilizada
Se não der pra ver, usar: ./ld-linux-x86-64.so.2 ./libc.so.6 -> Imprime informações da libc.
pwninit -> Esse comando cria um chall_patched que vai utilizar a libc do diretório e não do pc
Vemos que é uma libc na versão 2.42. É de cara um chall de heap (tem malloc, dá a libc, enfim). O que podemos explorar nessa versão?
- Temos tcache
- Unlink attack não prático
- Safe-linking
- Metadata integrity checks (para overflow)
Basicamente, temos uma versão moderna de libc, o que dificulta bastante as coisas. Bom, a boa notícia é que temos o arquivo da libc e podemos calcular os offsets das funções. Se vazarmos a libc por meio da heap, dá pra calcular o endereço exato de alguma função, como system.
Rodando programa
Vamos ao programa. Podemos:
1. Create Note
2. Delete Note
3. Read Note
4. Edit Note
5. Exit
> 1
Index: 0
Size: 90
Data: AAAA
Note created!
> 2
Index: 0
Note deleted!
> 3
Index: 0
Data: sda????%?
Usei:
pwndbg> heap -> Não funcionou (sem mp_)
pwndbg> bins -> Não funcinou (sem mp_)
pwndbg> vis-heap-chunks -> Não funcionou (sem mp_)
Vai ter que ser manual.
pwndbg> vmmap heap -> Mostra início da heap. Normalmente,
pwndbg> telescope heap_addr 40 -> Pula linhas iguais (como zeros) e mostra só o que é relevante.
pwndbg> telescope 0x55555555b000 100
00:0000│ 0x55555555b000 ◂— 0
01:0008│ 0x55555555b008 ◂— 0x301 -> CHUNK DO TCACHE (GUARDA CONTAGENS E CABEÇALHOS DE CADA BIN DO TCACHE)
02:0010│ 0x55555555b010 ◂— 0x7000700070007
... ↓ 18 skipped
15:00a8│ 0x55555555b0a8 ◂— 0
... ↓ 75 skipped
61:0308│ 0x55555555b308 ◂— 0x21 /* '!' */ -> CHUNK DA NOTA QUE CRIEI
62:0310│ 0x55555555b310 ◂— 0xa4141414141 /* 'AAAAA\n' */
63:0318│ 0x55555555b318 ◂— 0
Boa! Olha minha nota que criei aí.
Quando eu liberar essa nota, ela vai pro tcache. O tcache tem limite de 7 itens. O oitavo item vai para a unsortedbin (se for maior que 0x80). Com UM SÓ item na unsorted bin e um UAF, se eu LER o conteúdo dessa nota, lá estará meu endereço da libc (o fd).
É interessante que, ao ler uma nota, ele limita o index entre 0-9. E 9 notas é exatamente o que eu preciso para vazar a LIBC com Tcache.
Testando exploit
Testando, vemos que tem UAF. Dá pra fazer o vazamento levando o 8 chunk liberado para a unsorted bin e lendo ele.
Criando todas as 9 notas, de 0-8, (criar uma a mais garante que funcione) e só depois excluindo todas, de 0-8 (se criar e excluir antes, os chunks serão jogados na tcache e reutilizados, portanto, sempre crie tudo e depois exclua tudo), ao ver a nota 7, vem algo que parece um endereço. Ao analisar:
(gdb) x/10gx 0x7fd72b728ba0
0x7fd72b728ba0: 0x00007fd72b728b90 0x00007fd72b728b90
0x7fd72b728bb0: 0x000055579136d6f0 0x000055579136d6f0
Observe que o primeiro valor e o segundo são iguais. O que é exatamente o mesmo comportamento de bin[0] e bin[1] quando a unsorted bin tem o vazamento da libc.
Esse valor aponta para a main_arena.
OBS: Por que o vazamento aparece na Nota 7 como se fosse um chunk único?
- Consolidação: Quando a Nota 8 é liberada logo após a Nota 7, e ambas são adjacentes na memória, a glibc as consolida (funde) em um único chunk maior para evitar fragmentação.
- O chunk resultante dessa fusão começa no endereço da Nota 7.
- Como esse novo chunk "gigante" (Nota 7 + Nota 8) é o único na unsorted bin, os ponteiros fd e bk escritos no cabeçalho da Nota 7 apontarão ambos para a main_arena na libc.
Após vazamento, vamos calcular offset para achar libc
Para ver endereço base da libc atual: (gdb) info proc mappings.
Vamos pegar o menor Start Addr de libc.so.6
Para saber o offset: leak_encontrada - libc_base = 1E7BA0
Boom, temos o offset que nos leva à libc_base (leak - 1E7BA0)
Chamando system (problema!)
Agora que temos a libc, é dar um jeito de chamar system. A stack tá totalmente protegida:
- Sem BOF
- Sem GOT Overwrite
- Sem Shecllode
- Sem ROP (IBT impede ROP livre)
- Tem canary, se tiver BOF tenho que vazar isso
Desistir? Não. Descobri algo chamado ENVIRON. Existe, na LIBC, uma variável que aponta para a stack. Isso vaza a stack para nós, e aí usamos Double Free na Heap (Tcache Poisoning) para escrita arbitrária onde o ENVIRON aponta + offset até o return address.
Chall completo
- Cria 9 notas
- Deleta 9 notas (0 a 8)
- Lê
HEAP_LEAKna nota 0 (o fd dela aponta para nulo, então com XOR é o próprio endereço da heap). O endereço da heap sempre é algo como0x555..XXX. Quando se faz0x555...XXX << 12, estamos descartando esses últimos XXX. Essa é aXOR_KEY, que vai ser usada para codificar/decodificar tudo. Lembre que os chunks são alocados lado a lado, e com poucas notas dificilmente se passa de fff em tamanho (4095 bytes). Assim, fd_encoded = XOR_KEY ^ fd para todo chunk para nós. (&chunk << 12dá emXOR_KEYtambém). - Lê
LIBC_LEAKna nota 7 (foi pra unsorted bin). Aparentemente, ter a nota 8 ali não importa pro vazamento. Usar isso para acharLIBC_BASE. - Com
LIBC_LEAK, usa offset para pegarROPspara montar payload e endereço deENVIRON(vazamento de stack na LIBC) - Cria 2 notas (0 e 1). Faz tcache poisoning para ler ENVIRON (free 0, free 1, edit 1 (coloca ENVIRON ^ XOR_KEY no fd), malloc 1, malloc 0, read 0) para alocar no ENVIRON (LIBC). Lê a nota (lemos o ENVIRON, endereço vazado da stack) para STACK_LEAK
- Calcula offset da stack (pra onde ENVIRON aponta) para o rbp. Só usar GDB pra análise dinâmica, o offset sempre vai ser o mesmo.
- Cria 2 notas (0 e 1). Faz tcache poisonoing para escrever onde ENVIRON aponta (stack) (free 0, free 1, edit 1 (coloca STACK_LEAK ^ XOR_KEY no fd), malloc 1, malloc 0, edit 0) para alocar onde o ENVIRON aponta + offset (RBP). Edita a nota 0 colocando o PAYLOAD do ROP (8 nulos pra sobrepor o antigo RBP e o resto é payload).
OBS: Era necessário fazer o ROP na create_note() em vez da main() porque a main() fecha com exit(0) e, portanto, não tem faz ret
OBS: Todos os offsets foram calculados do jeito preguiçoso: durante uma execução real do programa, pega os dois endereços no GDB e os subtrai
OBS: Tcache poisoning é um double free
OBS: Comportamento da Unsorted Bin (Passo 4). Se a nota 7 for a última antes do Top Chunk, ao ser liberada, ela será consolidada com o Top Chunk em vez de ir para a Unsorted Bin. A nota 8 serve como uma "barreira" (wilderness protector) para garantir que a nota 7 fique isolada e receba os ponteiros da libc (main_arena).
Environ é uma variável na libc que aponta para variáveis de ambiente na stack. Podemos vazar a stack com isso.