Como construir exploits funcionais, não apenas teóricos
A arte da exploração no cotidiano
Encontrar uma falha é a parte fácil. A parte que separa quem publica um CVE de quem constrói algo que realmente funciona é entender o que acontece depois que o bug é disparado. A maioria dos pesquisadores trava nessa transição. Eu já vi várias vezes. O problema principal é que encontrar uma vulnerabilidade e explorá-la são habilidades diferentes. Uma depende de análise estática, dinâmica e raciocínio sobre lógica de software. A outra exige conhecimento de arquitetura, sistemas operacionais, mitigações modernas e, muitas vezes, paciência para depurar comportamento não determinístico por horas.
Meu primeiro exploit funcional levou três semanas. Passei uma semana inteira tentando fazer um overflow de buffer em um servidor HTTP interno entregar shell reverso. O código funcionava em gdb, travava em produção, funcionava com ASLR desligado, falhava com ASLR ligado. O problema real não era o bug em si — era que o servidor reiniciava o processo antes que eu pudesse explorar a condição de corrida que permitia o controle de flow.
O processo real de desenvolvimento de exploits
Você precisa começar mapeando o contexto de execução. O que está sendo executado? Qual o nível de privilégio do processo alvo? Quais Mitigações estão ativas? ASLR, DEP/NX, CFG, stack canaries, CFI. Cada uma dessas coisas muda completamente a abordagem que você vai usar. Depois de entender o contexto, o próximo passo é identificar a primitive — o que você consegue fazer com a falha. Um overflow de buffer pode te dar escrita arbitrária em memória. Um use-after-free pode te dar leitura e escrita de endereços arbitrários. Um race condition pode te dar uma janela muito pequena para manipular dados antes que o sistema os processe.
Aqui está algo que pouca gente menciona: a primitive mais importante não é leitura ou escrita arbitrária. É controle de fluxo. Se você consegue direcionar a execução para código que você escolhe, as outras coisas se tornam acessórias. Escrever um byte em um endereço arbitrário pode ser suficiente para criar um shell, dependendo do que está naquele endereço e do que você pode sobrescrever nele. Para a exploração prática, eu divido o trabalho em três fases distintas. A primeira é a reconstrução do ambiente. Você precisa de um setup idêntico ao alvo — mesmas versões de bibliotecas, mesmo kernel, mesmas configurações. Diferenças sutis aqui fazem você perder dias depurando algo que na verdade é apenas incompatibilidade ambiental.
A segunda fase é transformar a primitive em algo utilizável. Se você tem um use-after-free, por exemplo, o que você faz com ele? Pode sobrescrever um ponteiro de função para redirecionar a execução. Pode manipular uma estrutura vtable em C++. Pode escrever sobre uma área de heap adjacented para corromper metadados e criar condições de alloc mais tarde. A escolha depende do que o código alvo permite. A terceira fase é o exploitation em si — transformar a primitiva controlada em algo útil. Shellcode é raramente a resposta certa em ambientes modernos. O mais comum é construir uma cadeia ROP ou usar técnicas de JIT spray, heap grooming e similares para contornar as defesas disponíveis.
👉 Clique no botão abaixo para saber mais sobre o assunto!
O que funciona na prática versus o que a literatura diz
Livros e artigos sobre o art of exploitation frequentemente apresentam problemas bem estruturados com uma solução clara. A realidade é mais bagunçada. Você vai encontrar sistemas onde a ordem de alocação de heap não segue o padrão esperado, onde o ASLR apresenta entropia insuficiente em determinadas configurações, onde a biblioteca C padrão tem comportamento diferente no alvo do que no seu ambiente de teste. Uma coisa que aprendi na prática e raramente vejo mencionada em tutoriais introdutórios: o timing é frequentemente o fator mais crítico em exploits que dependem de race conditions. Um delay de milissegundo entre duas operações pode significar a diferença entre sucesso e falha. Isso significa que você precisa entender o scheduler do sistema operacional, não apenas a lógica do bug.
Também é importante notar que a maioria dos exploits que você encontra online não vão funcionar contra o alvo real. Eles funcionam contra VMs configuradas com segurança reduzida para fins educacionais.Quando você pega um exploit pubicado e tenta rodar contra um sistema moderno, vai encontrar problemas com canários de pilha, RELRO completo, PXN no ARM, e uma variedade de outras proteções que tornam a exploração direta inviável sem adaptação significativa. O workaround que eu uso quando um exploit clássico falha é analisar o binary com ferramentas como Ghidra ou Radare2 para entender exatamente quais verificações de segurança estão sendo aplicadas e onde elas podem ser contornadas. Às vezes, a solução é simplesmente encontrar um caminho alternativo para o mesmo objetivo, usando uma vulnerability diferente no mesmo sistema ou uma técnica de bypass específica para as Mitigações em questão.
Ferramentas e técnicas específicas
Para reconstrução de ambiente, Docker é essencial mas tem limitações. Okernel dentro do container ainda é o do host, então configurações de segurança do kernel precisam ser ajustadas separadamente. QEMU com chroot permite simular arquiteturas diferentes, o que é útil para targets embarcados. Para análise estática, eu gosto de começar com objdump e readelf para entender a estrutura básica do binary — seções, símbolos, funções, imports. Depois migro para Ghidra para a análise mais profunda. GDB com plugins como Peda ou Pwndbg acelera muito o processo de debugging, especialmente para testes iterativos deexploit.
O crafting doexploit em si é feito com Python e a biblioteca pwntools. Ela abstrai muitas das operações repetitivas — conectar a serviço, enviar payloads, parsear respostas — permitindo que você foque na lógica deexploração. A maioria dos profissionais escreve seus exploits em Python por essa razão, mesmo que o shellcode final seja escrito em assembly para maximizar controle.
Quando a abordagem falha completamente
Não existe uma técnica universal que funcione em todos os cenários. Sistemas com heap hardening agressivo, como os encontrados em browsers modernos ou kernels com KASLR forte, podem tornar certas classes deexploit inviáveis sem research novo. Dispositivos embarcados com firmware assinado digitalmente impedem execução de código arbitrário a menos que você encontre uma falha na verificação de assinatura ou consiga injetar código dentro do espaço designed do fabricante. Quando as técnicas tradicionais deheap exploitation falham, alternativas incluem side-channel attacks que exploram vazamentos de informação através do tempo de execução ou consumo de energia, ou então fuzzing dirigido para descobrir novas vetores de entrada que possam levar a condições de exploração mais favoráveis.
O art of exploration continua sendo uma área onde a prática supera a teoria de forma consistente. Cada sistema que você enfrenta é único, e a habilidade mais importante que você pode desenvolver é saber quando abandonar uma abordagem e tentar outra, em vez de insistir em um método que simplesmente não está funcionando contra a defesa implementada.