LibAFL
LibAFL es una biblioteca modular de fuzzing que implementa características de fuzzers basados en AFL como AFL++. A diferencia de los fuzzers tradicionales, LibAFL proporciona toda su funcionalidad de manera modular y personalizable como una biblioteca de Rust. Se puede usar como reemplazo directo de libFuzzer o como biblioteca para construir fuzzers personalizados desde cero.
Cuándo Usarlo
| Fuzzer | Mejor Para | Complejidad |
|---|---|---|
| libFuzzer | Configuración rápida, monohilo | Baja |
| AFL++ | Multinúcleo, propósito general | Media |
| LibAFL | Fuzzers personalizados, funciones avanzadas, investigación | Alta |
Elige LibAFL cuando:
- Necesites estrategias de mutación o mecanismos de retroalimentación personalizados
- Los fuzzers estándar no soporten tu arquitectura objetivo
- Quieras implementar técnicas de fuzzing novedosas
- Necesites control detallado sobre los componentes del fuzzing
- Estés realizando investigación en fuzzing
Inicio Rápido
LibAFL se puede usar como reemplazo directo de libFuzzer con una configuración mínima:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Llama a tu código con los datos proporcionados por el fuzzer
my_function(data, size);
return 0;
}
Construye la capa de compatibilidad con libFuzzer de LibAFL:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
Compila y ejecuta:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
Instalación
Prerrequisitos
- Clang/LLVM 15-18
- Rust (a través de rustup)
- Dependencias adicionales del sistema
Linux/macOS
Instala Clang:
apt install clang
O instala una versión específica a través de apt.llvm.org:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Configura el entorno para Rust:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Instala Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Instala dependencias adicionales:
apt install libssl-dev pkg-config
Para el modo de compatibilidad con libFuzzer, instala Rust nightly:
rustup toolchain install nightly --component llvm-tools
Verificación
Construye LibAFL para verificar la instalación:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Debería producir libFuzzer.a
Escribiendo un Harness
Los harnesses de LibAFL siguen el mismo patrón que libFuzzer cuando se usa en modo de reemplazo directo:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Tu código objetivo de fuzzing aquí
return 0;
}
Cuando se construyen fuzzers personalizados con LibAFL como biblioteca Rust, la lógica del harness se integra directamente en el fuzzer. Consulta la sección "Escribiendo un Fuzzer Personalizado" a continuación para ver el patrón completo.
Ver También: Para técnicas detalladas de escritura de harnesses, consulta la habilidad técnica harness-writing.
Modos de Uso
LibAFL soporta dos modos de uso principales:
1. Reemplazo Directo de libFuzzer
Usa LibAFL como reemplazo de libFuzzer con harnesses existentes.
Compilación:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
Ejecución:
./fuzz corpus/
Recomendado para campañas largas:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Fuzzer Personalizado como Biblioteca Rust
Construye un fuzzer completamente personalizado usando componentes de LibAFL.
Crea el proyecto:
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"
Configura Cargo.toml:
[lib]
crate-type = ["staticlib"]
Escribiendo un Fuzzer Personalizado
Ver También: Para técnicas detalladas de escritura de harnesses, patrones para manejar entradas complejas, y estrategias avanzadas, consulta la habilidad técnica fuzz-harness-writing.
Componentes del Fuzzer
Un fuzzer de LibAFL consta de componentes modulares:
- Observadores - Recogen retroalimentación de la ejecución (cobertura, tiempo)
- Retroalimentación - Determinan si las entradas son interesantes
- Objetivo - Definen las metas del fuzzing (crashes, timeouts)
- Estado - Mantienen el corpus y los metadatos
- Mutadores - Generan nuevas entradas
- Planificador - Seleccionan qué entradas mutar
- Ejecutor - Ejecutan el objetivo con las entradas
Estructura Básica del 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 retroalimentación
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. Crear o 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 planificador
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Crear 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 ejecutor
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Cargar entradas iniciales
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(())
};
// Lanzar fuzzer
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
Compilación
Modo Detallado
Especifica manualmente todas las banderas de instrumentación:
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
Envoltorio de Compilador (Recomendado)
Crea un envoltorio de compilador de LibAFL para manejar la instrumentación automáticamente.
Crea 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();
}
Compila y usa:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
Ver También: Para configuraciones detalladas de sanitizadores, problemas comunes y banderas avanzadas, consulta las habilidades técnicas address-sanitizer y undefined-behavior-sanitizer.
Ejecutando Campañas
Ejecución Básica
./fuzz --cores 0 --input corpus/
Fuzzing Multinúcleo
./fuzz --cores 0,8-15 --input corpus/
Esto ejecuta 9 clientes: uno en el núcleo 0 y 8 en los núcleos 8-15.
Con Opciones
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
Interfaz de Usuario de Texto (TUI)
Habilita la vista de estadísticas gráficas:
./fuzz -tui=1 corpus/
Interpretando la Salida
| Salida | Significado |
|---|---|
corpus: N | Número de casos de prueba interesantes encontrados |
objectives: N | Número de crashes/timeouts encontrados |
executions: N | Número total de invocaciones al objetivo |
exec/sec: N | Rendimiento de ejecución actual |
edges: X% | Porcentaje de cobertura de código |
clients: N | Número de procesos de fuzzing paralelos |
El fuzzer emite dos tipos principales de eventos:
- UserStats - Latido regular con estadísticas actuales
- Testcase - Nueva entrada interesante descubierta
Uso Avanzado
Consejos y Trucos
| Consejo | Por Qué Ayuda |
|---|---|
Usa -fork=1 -ignore_crashes=1 | Continúa fuzzeando después del primer crash |
Usa InMemoryOnDiskCorpus | Persiste el corpus tras reinicios |
Habilita TUI con -tui=1 | Mejor visualización del progreso |
| Usa una versión específica de LLVM | Evita problemas de compatibilidad |
Configura RUSTFLAGS correctamente | Previene errores de enlazado |
Deduplicación de Crashes
Evita almacenar crashes duplicados del mismo error:
Agrega un observador de backtrace:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
Actualiza el ejecutor:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
Actualiza el objetivo con retroalimentación de hash:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
Esto asegura que solo se guarden crashes con backtraces únicos.
Fuzzing con Diccionarios
Usa diccionarios para guiar el fuzzing hacia tokens específicos:
Agrega tokens desde un archivo:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
Actualiza el mutador:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
Ejemplo de tokens codificados (PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // Cabecera PNG
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
Ver También: Para estrategias detalladas de creación de diccionarios y diccionarios específicos de formato, consulta la habilidad técnica fuzzing-dictionaries.
Tokens Automáticos
Extrae automáticamente valores mágicos y sumas de verificación del programa:
Habilita en el envoltorio de compilador:
cc.add_pass(LLVMPasses::AutoTokens)
Carga tokens automáticos en el fuzzer:
tokens += libafl_targets::autotokens()?;
Verifica la sección de tokens:
echo "p (uint8_t *)__token_start" | gdb fuzz
Ajuste de Rendimiento
| Configuración | Impacto |
|---|---|
| Fuzzing multinúcleo | Aceleración lineal con los núcleos |
InMemoryCorpus | Más rápido pero no persistente |
InMemoryOnDiskCorpus | Velocidad y persistencia equilibradas |
| Sanitizadores | Ralentización de 2-5x, esencial para errores |
Nivel de optimización -O2 | Equilibrio entre velocidad y cobertura |
Depuración del Fuzzer
Ejecuta el fuzzer en modo monoproceso para una depuración más fácil:
// Reemplaza el lanzador con una llamada directa
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Comenta:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
Luego depura con GDB:
gdb --args ./fuzz --cores 0 --input corpus/
Ejemplos del Mundo Real
Ejemplo: libpng
Fuzzeando libpng usando LibAFL:
1. Obtén el código fuente:
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. Configura el envoltorio de compilador:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. Construye la biblioteca estática:
./configure --enable-shared=no
make
4. Obtén el harness:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Enlaza el fuzzer:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. Prepara semillas:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. Obtén el diccionario (opcional):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. Inicia el fuzzing:
./fuzz --input seeds/ --cores 0 -x png.dict
Ejemplo: Proyecto CMake
Integra LibAFL con el sistema de compilación 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)
Construye el binario no instrumentado:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
Construye el fuzzer:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
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
Ejecuta:
./fuzz --input seeds/ --cores 0
Los fuzzers construidos con CMake pueden integrarse en sistemas de CI/CD y scripts de automatización, lo que los hace ideales para equipos que ya usan CMake.
Preguntas Frecuentes
Problemas Comunes
| Problema | Causa | Solución |
|---|---|---|
linking with cc failed | Compilador no coincide | Configura CC, CXX y RUSTFLAGS correctamente |
undefined symbol: __sancov | Error de enlazado de instrumentación | Usa el envoltorio de compilador o agrega banderas de instrumentación manualmente |
Permission denied | Problemas de permisos de corpus | Verifica los permisos de los directorios de entrada y salida |
signal: SIGSEGV | Crash en el programa objetivo | Ejecuta el fuzzer en modo monoproceso para depurar |