LibAFL
O LibAFL é uma biblioteca modular de fuzzing que implementa recursos de fuzzers baseados em AFL, como o AFL++. Diferente de fuzzers tradicionais, o LibAFL oferece toda a funcionalidade de forma modular e personalizável como uma biblioteca Rust. Ele pode ser usado como substituto direto do libFuzzer ou como uma biblioteca para construir fuzzers personalizados do zero.
Quando Usar
| Fuzzer | Melhor Para | Complexidade |
|---|---|---|
| libFuzzer | Configuração rápida, single-threaded | Baixa |
| AFL++ | Multi-core, uso geral | Média |
| LibAFL | Fuzzers personalizados, recursos avançados, pesquisa | Alta |
Escolha LibAFL quando:
- Você precisar de estratégias de mutação ou mecanismos de feedback personalizados
- Fuzzers padrão não suportam sua arquitetura alvo
- Quiser implementar técnicas de fuzzing inovadoras
- Precisar de controle refinado sobre os componentes de fuzzing
- Estiver realizando pesquisa em fuzzing
Início Rápido
O LibAFL pode ser usado como substituto direto do libFuzzer com configuração mínima:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Chame seu código com os dados fornecidos pelo fuzzer
my_function(data, size);
return 0;
}
Construa a camada de compatibilidade com libFuzzer do LibAFL:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
Compile e execute:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
Instalação
Pré-requisitos
- Clang/LLVM 15-18
- Rust (via rustup)
- Dependências adicionais do sistema
Linux/macOS
Instale o Clang:
apt install clang
Ou instale uma versão específica via apt.llvm.org:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Configure o ambiente para Rust:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Instale Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Instale dependências adicionais:
apt install libssl-dev pkg-config
Para modo de compatibilidade com libFuzzer, instale o Rust nightly:
rustup toolchain install nightly --component llvm-tools
Verificação
Construa o LibAFL para verificar a instalação:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Deve produzir libFuzzer.a
Escrevendo um Harness
Os harnesses do LibAFL seguem o mesmo padrão do libFuzzer quando usados no modo de substituição direta:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Código do alvo de fuzzing aqui
return 0;
}
Ao construir fuzzers personalizados com LibAFL como biblioteca Rust, a lógica do harness é integrada diretamente no fuzzer. Veja a seção "Escrevendo um Fuzzer Personalizado" abaixo para o padrão completo.
Veja Também: Para técnicas detalhadas de escrita de harnesses, veja a skill de técnica harness-writing.
Modos de Uso
O LibAFL suporta dois modos principais de uso:
1. Substituto Direto do libFuzzer
Use o LibAFL como substituto do libFuzzer com harnesses existentes.
Compilação:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
Execução:
./fuzz corpus/
Recomendado para campanhas longas:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Fuzzer Personalizado como Biblioteca Rust
Construa um fuzzer totalmente personalizado usando componentes do LibAFL.
Crie o projeto:
cargo init --lib my_fuzzer
cd my_fuzzer
cargo add [email protected] [email protected] [email protected] [email protected] \
--features "[email protected]/libfuzzer,[email protected]/sancov_pcguard_hitcounts"
Configure o Cargo.toml:
[lib]
crate-type = ["staticlib"]
Escrevendo um Fuzzer Personalizado
Veja Também: Para técnicas detalhadas de escrita de harnesses, padrões para lidar com entradas complexas, e estratégias avançadas, veja a skill de técnica fuzz-harness-writing.
Componentes do Fuzzer
Um fuzzer LibAFL consiste em componentes modulares:
- Observadores - Coletam feedback da execução (cobertura, tempo)
- Feedback - Determinam se as entradas são interessantes
- Objetivo - Definem metas de fuzzing (crashs, timeouts)
- Estado - Mantêm corpus e metadados
- Mutadores - Geram novas entradas
- Escalonador - Selecionam quais entradas sofrerão mutação
- Executor - Executa o alvo com as entradas
Estrutura Básica do Fuzzer
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};
#[no_mangle]
pub extern "C" fn libafl_main() {
let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
// 1. Configurar observadores
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. Definir feedback
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. Definir objetivo
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. Criar ou restaurar estado
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. Configurar mutador
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. Configurar escalonador
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Criar fuzzer
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. Definir harness
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. Configurar executor
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Carregar entradas iniciais
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. Iniciar fuzzing
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// Lançar fuzzer
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
Compilação
Modo Verboso
Especifique manualmente todas as flags de instrumentação:
clang++-15 -DNO_MAIN -g -O2 \
-fsanitize-coverage=trace-pc-guard \
-fsanitize=address \
-Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
main.cc harness.cc -o fuzz
Wrapper do Compilador (Recomendado)
Crie um wrapper de compilador LibAFL para lidar com a instrumentação automaticamente.
Crie src/bin/libafl_cc.rs:
use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
let mut cc = ClangWrapper::new();
cc.cpp(is_cpp)
.parse_args(&args)
.link_staticlib(&dir, "my_fuzzer")
.add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
.add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
.run()
.unwrap();
}
Compile e use:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
Veja Também: Para configuração detalhada de sanitizers, problemas comuns e flags avançadas, veja as skills de técnica address-sanitizer e undefined-behavior-sanitizer.
Executando Campanhas
Execução Básica
./fuzz --cores 0 --input corpus/
Fuzzing Multi-Core
./fuzz --cores 0,8-15 --input corpus/
Isso executa 9 clientes: um no núcleo 0, e 8 nos núcleos 8-15.
Com Opções
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
Interface de Usuário Textual (TUI)
Habilite a visualização gráfica de estatísticas:
./fuzz -tui=1 corpus/
Interpretando a Saída
| Saída | Significado |
|---|---|
corpus: N | Número de casos de teste interessantes encontrados |
objectives: N | Número de crashs/timeouts encontrados |
executions: N | Número total de invocações do alvo |
exec/sec: N | Taxa de execução atual |
edges: X% | Percentual de cobertura de código |
clients: N | Número de processos de fuzzing paralelos |
O fuzzer emite dois tipos principais de eventos:
- UserStats - Batimento cardíaco regular com estatísticas atuais
- Testcase - Nova entrada interessante descoberta
Uso Avançado
Dicas e Truques
| Dica | Por que Ajuda |
|---|---|
Use -fork=1 -ignore_crashes=1 | Continuar fuzzing após o primeiro crash |
Use InMemoryOnDiskCorpus | Persistir corpus entre reinicializações |
Ative TUI com -tui=1 | Melhor visualização do progresso |
| Use versão específica do LLVM | Evitar problemas de compatibilidade |
Configure RUSTFLAGS corretamente | Prevenir erros de linking |
Deduplicação de Crashs
Evite armazenar crashs duplicados do mesmo bug:
Adicione observer de backtrace:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
Atualize o executor:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
Atualize o objetivo com feedback de hash:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
Isso garante que apenas crashs com backtraces únicos sejam salvos.
Fuzzing com Dicionário
Use dicionários para guiar o fuzzing em direção a tokens específicos:
Adicione tokens de um arquivo:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
Atualize o mutador:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
Exemplo de tokens codificados (PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // Cabeçalho PNG
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
Veja Também: Para estratégias detalhadas de criação de dicionários e dicionários específicos de formato, veja a skill de técnica fuzzing-dictionaries.
Tokens Automáticos
Extraia automaticamente valores mágicos e checksums do programa:
Ative no wrapper do compilador:
cc.add_pass(LLVMPasses::AutoTokens)
Carregue tokens automáticos no fuzzer:
tokens += libafl_targets::autotokens()?;
Verifique a seção de tokens:
echo "p (uint8_t *)__token_start" | gdb fuzz
Ajuste de Desempenho
| Configuração | Impacto |
|---|---|
| Fuzzing multi-core | Aceleração linear com núcleos |
InMemoryCorpus | Mais rápido, mas não persistente |
InMemoryOnDiskCorpus | Velocidade e persistência equilibradas |
| Sanitizers | Desaceleração de 2-5x, essencial para bugs |
Nível de otimização -O2 | Equilíbrio entre velocidade e cobertura |
Depuração do Fuzzer
Execute o fuzzer em modo de processo único para depuração mais fácil:
// Substitua o launcher por uma chamada direta
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Comente:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
Depois depure com GDB:
gdb --args ./fuzz --cores 0 --input corpus/
Exemplos do Mundo Real
Exemplo: libpng
Fuzzing do libpng usando LibAFL:
1. Obtenha o código fonte:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev
2. Configure o wrapper do compilador:
export FUZZER_CARGO_DIR="/caminho/para/o/projeto/libafl"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. Construa a biblioteca estática:
./configure --enable-shared=no
make
4. Obtenha o harness:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Link o fuzzer:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. Prepare as sementes:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. Obtenha o dicionário (opcional):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. Inicie o fuzzing:
./fuzz --input seeds/ --cores 0 -x png.dict
Exemplo: Projeto CMake
Integre o LibAFL com o sistema de build CMake:
CMakeLists.txt:
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)
Construa o binário não instrumentado:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
Construa o fuzzer:
export FUZZER_CARGO_DIR="/caminho/para/o/projeto/libafl"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
cmake -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX .
cmake --build . --target fuzz
Referência da API
| Tipo | Propósito |
|---|---|
StdFuzzer | Fuzzer principal com escalonador, feedback e objetivo |
InProcessExecutor | Executa o alvo no mesmo processo |
ForkserverExecutor | Executa o alvo em processos separados |
InMemoryCorpus | Armazena entradas interessantes na memória |
OnDiskCorpus | Armazena entradas interessantes no disco |
InMemoryOnDiskCorpus | Espelha entradas na memória e no disco |
QueueScheduler | Escalonador FIFO |
IndexesLenTimeMinimizerScheduler | Prioriza entradas por cobertura e tempo |
StdScheduledMutator | Aplica mutações a entradas |
StdMutationalStage | Um estágio de fuzzing mutacional |
Solução de Problemas
| Problema | Solução |
|---|---|
| Erros de linking | Verifique as flags do linker e as bibliotecas |
| Incompatibilidade do LLVM | Instale a versão correta do LLVM (15-18) |
| Erros de compilação do Rust | Atualize Rust e dependências |
| Sem cobertura | Certifique-se de que a instrumentação esteja ativa |
| Desempenho lento | Reduza o número de sanitizers ativos |
| Multithreading instável | Ajuste o número de núcleos e timeouts |
Leitura Adicional
- Documentação oficial do LibAFL
- Repositório do LibAFL no GitHub
- Tutorial de Fuzzing com LibAFL
- Guia de Referência do AFL++
Veja Também: Para outras skills técnicas relacionadas a fuzzing e desenvolvimento de segurança, consulte: address-sanitizer, undefined-behavior-sanitizer, fuzzing-dictionaries, harness-writing, fuzz-harness-writing.