AFL++
AFL++ é um fork do fuzzer AFL original que oferece melhor desempenho de fuzzing e recursos mais avançados, mantendo a estabilidade. Uma grande vantagem sobre o libFuzzer é que o AFL++ possui suporte estável para executar campanhas de fuzzing em múltiplos núcleos, tornando-o ideal para esforços de fuzzing em larga escala.
Quando Usar
| Fuzzer | Melhor Para | Complexidade |
|---|---|---|
| AFL++ | Fuzzing multi-core, mutações diversas, projetos maduros | Média |
| libFuzzer | Configuração rápida, single-threaded, harnesses simples | Baixa |
| LibAFL | Fuzzers personalizados, pesquisa, casos de uso avançados | Alta |
Escolha AFL++ quando:
- Você precisa de fuzzing multi-core para maximizar a taxa de transferência
- Seu projeto pode ser compilado com Clang ou GCC
- Você deseja estratégias de mutação diversas e ferramentas maduras
- libFuzzer atingiu um platô e você precisa de mais cobertura
- Você está fazendo fuzzing em bases de código de produção que se beneficiam da execução paralela
Início Rápido
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Chame seu código com os dados fornecidos pelo fuzzer
check_buf((char*)data, size);
return 0;
}
Compile e execute:
# Configure o script wrapper AFL++ primeiro (veja Instalação)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Instalação
AFL++ tem muitas dependências, incluindo LLVM, Python e Rust. Recomendamos usar uma distribuição Debian ou Ubuntu atual para fuzzing com AFL++.
| Método | Quando Usar | Compiladores Suportados |
|---|---|---|
| Repositórios Ubuntu/Debian | Ubuntu recente, apenas recursos básicos | Ubuntu 23.10: Clang 14 e GCC 13Debian 12: Clang 14 e GCC 12 |
| Docker (do Docker Hub) | Versão específica do AFL++, suporte Apple Silicon | A partir de 4.35c: Clang 19 e GCC 11 |
| Docker (a partir do código fonte) | Testar recursos não lançados, aplicar patches | Configurável no Dockerfile |
| A partir do código fonte | Evitar Docker, precisar de patches específicos | Ajustável via variável de ambiente LLVM_CONFIG |
Ubuntu/Debian
Antes de instalar o afl++, verifique a dependência da versão do clang do pacote com apt-cache show afl++ e instale a versão correspondente do lld (ex.: lld-17).
apt install afl++ lld-17
Docker (do Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker (a partir do código fonte)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
A partir do código fonte
Consulte o Dockerfile para requisitos de versão do Ubuntu e dependências. Defina LLVM_CONFIG para especificar a versão do Clang (ex.: llvm-config-18).
Configuração do Script Wrapper
Crie um script wrapper para executar o AFL++ no host ou no Docker:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -ti \
--privileged \
-v ./:/src \
--rm \
--name afl_fuzzing \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Uso: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
Aviso de Segurança: Os scripts afl-system-config e afl-persistent-config exigem privilégios de root e desativam recursos de segurança do SO. Não faça fuzzing em sistemas de produção ou em seu ambiente de desenvolvimento. Use uma VM dedicada.
Configuração do Sistema
Execute após cada reinicialização para obter até 15% mais execuções por segundo:
./afl++ <host/docker> afl-system-config
Para desempenho máximo, desative mitigações de segurança do kernel (requer bootloader grub, não suportado no Docker):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ <host/docker> afl-system-config
Verifique com cat /proc/cmdline - a saída deve incluir mitigations=off.
Escrevendo um Harness
Estrutura do Harness
AFL++ suporta harnesses no estilo libFuzzer:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Valide o tamanho da entrada, se necessário
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Chame a função alvo com os dados do fuzzer
target_function(data, size);
// 3. Retorne 0 (diferente de zero reservado para uso futuro)
return 0;
}
Regras do Harness
| Faça | Não Faça |
|---|---|
| Redefina o estado global entre as execuções | Dependa do estado de execuções anteriores |
| Trate casos de borda com elegância | Saia em entrada inválida |
| Mantenha o harness determinístico | Use geradores de números aleatórios |
| Libere a memória alocada | Crie vazamentos de memória |
| Valide tamanhos de entrada | Processe entrada ilimitada |
Veja Também: Para técnicas detalhadas de escrita de harness, padrões para lidar com entradas complexas e estratégias avançadas, veja a habilidade técnica fuzz-harness-writing.
Compilação
AFL++ oferece vários modos de compilação com diferentes compromissos.
Árvore de Decisão do Modo de Compilação
Escolha seu modo de compilação:
- Modo LTO (
afl-clang-lto): Melhor desempenho e instrumentação. Tente primeiro. - Modo LLVM (
afl-clang-fast): Recorra se o LTO falhar ao compilar. - Plugin GCC (
afl-gcc-fast): Para projetos que exigem GCC.
Compilação Básica (modo LLVM)
./afl++ <host/docker> afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Compilação com GCC
./afl++ <host/docker> afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Importante: A versão do GCC deve corresponder à versão usada para compilar o plugin GCC do AFL++.
Com Sanitizers
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Veja Também: Para configuração detalhada de sanitizers, problemas comuns e flags avançadas, veja as habilidades técnicas address-sanitizer e undefined-behavior-sanitizer.
Flags de Build
Observe que -g não é necessário, pois é adicionado por padrão pelos compiladores AFL++.
| Flag | Propósito |
|---|---|
-DNO_MAIN=1 | Pular a função main ao usar harness libFuzzer |
-O2 | Nível de otimização de produção (recomendado para fuzzing) |
-fsanitize=fuzzer | Ativar modo de compatibilidade libFuzzer e adicionar o runtime do fuzzer ao vincular o executável |
-fsanitize=fuzzer-no-link | Instrumentar sem vincular o runtime do fuzzer (para bibliotecas estáticas e arquivos objeto) |
Gerenciamento de Corpus
Criando Corpus Inicial
AFL++ requer pelo menos um arquivo de semente não vazio:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
Para projetos reais, colete entradas representativas:
- Baixe arquivos de exemplo para o formato que você está fuzzeando
- Extraia casos de teste do conjunto de testes do projeto
- Use entradas mínimas válidas para seu formato de arquivo
Minimização de Corpus
Após uma campanha, minimize o corpus para manter apenas a cobertura única:
./afl++ <host/docker> afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
Veja Também: Para estratégias de criação de corpus, dicionários e seleção de sementes, veja a habilidade técnica fuzzing-corpus.
Executando Campanhas
Execução Básica
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
Configurando Variáveis de Ambiente
./afl++ <host/docker> AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
Interpretando a Saída
A interface do AFL++ mostra estatísticas de fuzzing em tempo real:
| Saída | Significado |
|---|---|
| execs/sec | Velocidade de execução - quanto maior, melhor |
| cycles done | Número de passagens da fila concluídas |
| corpus count | Número de casos de teste únicos na fila |
| saved crashes | Número de crashes únicos encontrados |
| stability | % de arestas estáveis (deve estar perto de 100%) |
Estrutura do Diretório de Saída
out/default/
├── cmdline # Como o SUT foi invocado?
├── crashes/ # Entradas que causam crash no SUT
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Entradas que travam o SUT
├── queue/ # Casos de teste reproduzindo o estado final do fuzzer
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Estatísticas da campanha
└── plot_data # Dados para plotagem
Analisando Resultados
Veja estatísticas da campanha ao vivo:
./afl++ <host/docker> afl-whatsup out
Crie gráficos de cobertura:
apt install gnuplot
./afl++ <host/docker> afl-plot out/default out_graph/
Reexecutando Casos de Teste
./afl++ <host/docker> ./fuzz out/default/crashes/<test_case>
Opções do Fuzzer
| Opção | Propósito |
|---|---|
-G 4000 | Comprimento máximo da entrada de teste (padrão: 1048576 bytes) |
-t 1000 | Timeout em milissegundos para cada caso de teste (padrão: 1000ms) |
-m 1000 | Limite de memória em megabytes (padrão: 0 = ilimitado) |
-x ./dict.dict | Usar arquivo de dicionário para guiar mutações |
Fuzzing Multi-Core
AFL++ se destaca no fuzzing multi-core com duas grandes vantagens:
- Mais execuções por segundo (escala linearmente com núcleos físicos)
- Fuzzing assimétrico (ex.: um job ASan, o restante sem sanitizers)
Iniciando uma Campanha
Inicie o fuzzer primário (em segundo plano):
./afl++ <host/docker> afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error &
Inicie fuzzers secundários (quantos núcleos você tiver):
./afl++ <host/docker> afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error &
./afl++ <host/docker> afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Monitorando Campanhas Multi-Core
Liste todos os jobs em execução:
jobs
Veja estatísticas ao vivo (atualiza a cada segundo):
./afl++ <host/docker> watch -n1 --color afl-whatsup state/
Parando Todos os Fuzzers
kill $(jobs -p)
Análise de Cobertura
AFL++ rastreia automaticamente a cobertura por meio de instrumentação de arestas. As informações de cobertura são armazenadas em fuzzer_stats e plot_data.
Medindo Cobertura
Use afl-plot para visualizar a cobertura ao longo do tempo:
./afl++ <host/docker> afl-plot out/default out_graph/
Melhorando a Cobertura
- Use dicionários para fuzzing ciente do formato
- Execute campanhas mais longas (cycles_wo_finds indica platô)
- Tente diferentes estratégias de mutação com fuzzing multi-core
- Analise lacunas de cobertura e adicione entradas de semente direcionadas
Veja Também: Para técnicas detalhadas de análise de cobertura, identificação de lacunas de cobertura e melhoria sistemática de cobertura, veja a habilidade técnica coverage-analysis.
CMPLOG
CMPLOG/RedQueen é o melhor mecanismo de resolução de restrições de caminho disponível em qualquer fuzzer. Para ativá-lo, o alvo do fuzzer precisa ser instrumentado para isso. Antes de compilar o alvo do fuzzing, defina a variável de ambiente:
./afl++ <host/docker> AFL_LLVM_CMPLOG=1 make
Nenhuma ação especial é necessária para compilar e vincular o harness.
Para executar uma instância do fuzzer com um alvo de fuzzing instrumentado com CMPLOG, adicione -c0 aos argumentos da linha de comando:
./afl++ <host/docker> afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Integração com Sanitizers
Sanitizers são essenciais para encontrar bugs de corrupção de memória que não causam crashes imediatos.
AddressSanitizer (ASan)
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Nota: O limite de memória (-m) não é suportado com ASan devido à reserva de memória virtual de 20 TB.
UndefinedBehaviorSanitizer (UBSan)
./afl++ <host/docker> AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz
Problemas Comuns com Sanitizers
| Problema | Solução |
|---|---|
| ASan reduz a velocidade do fuzzing | Use apenas 1 job ASan em configuração multi-core |
| Exaustão de pilha | Aumente a pilha com ASAN_OPTIONS=stack_size=... |
| Incompatibilidade de versão do GCC | Certifique-se de que o GCC do sistema corresponda à versão do plugin AFL++ |
Veja Também: Para configuração abrangente de sanitizers e solução de problemas, veja a habilidade técnica address-sanitizer.
Uso Avançado
Dicas e Truques
| Dica | Por Que Ajuda |
|---|---|
| Use harnesses LLVMFuzzerTestOneInput sempre que possível | Se uma campanha de fuzzing tiver pelo menos 85% de estabilidade, este é o estilo de fuzzing mais eficiente. Caso contrário, tente fuzzing de entrada padrão ou de arquivo |
| Use dicionários | Ajuda o fuzzer a descobrir palavras-chave específicas do formato e bytes mágicos |
| Defina timeouts realistas | Evita falsos positivos devido à carga do sistema |
| Limite o tamanho da entrada | Entradas maiores não exploram necessariamente mais espaço |
| Monitore a estabilidade | Baixa estabilidade indica comportamento não determinístico |
Fuzzing de Entrada Padrão
AFL++ pode fuzzear programas que leem da stdin sem um harness libFuzzer:
./afl++ <host/docker> afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_stdin
Isso é mais lento que o modo persistente, mas não requer código de harness.
Fuzzing de Entrada de Arquivo
Para programas que leem arquivos, use o espaço reservado @@:
./afl++ <host/docker> afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_file @@
Para melhor desempenho, use fmemopen para criar descritores de arquivo a partir da memória.
Fuzzing de Argumentos
Fuzzee argumentos de linha de comando usando argv-fuzz-inl.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_COMPILER
#include "argv-fuzz-inl.h"
#endif
void check_buf(char *buf, size_t buf_len) {
if(buf_len > 0 && buf[0] == 'a') {
if(buf_len > 1 && buf[1] == 'b') {
if(buf_len > 2 && buf[2] == 'c') {
abort();
}
…
[truncated for translation]