├── .gitignore ├── Cargo.toml ├── README.md ├── assets └── screenshot.png ├── rustfmt.toml └── src ├── bin ├── libafl_cc.rs └── libafl_cxx.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # OS/IDE Specific 13 | .vscode 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stdfuzzer" 3 | version = "0.13.1" 4 | authors = ["Andrea Fioraldi "] 5 | edition = "2021" 6 | 7 | [profile.release] 8 | lto = true 9 | codegen-units = 1 10 | opt-level = 3 11 | debug = true 12 | 13 | [build-dependencies] 14 | cc = { version = "1.0", features = ["parallel"] } 15 | which = { version = "6" } 16 | num_cpus = "1.0" 17 | 18 | [dependencies] 19 | libafl = "0.13.1" 20 | libafl_bolts = "0.13.1" 21 | libafl_targets = { version = "0.13.1", features = ["sancov_pcguard_hitcounts", "sancov_cmplog", "libfuzzer"] } 22 | # TODO Include it only when building cc 23 | libafl_cc = "0.13.1" 24 | mimalloc = { version = "*", default-features = false } 25 | clap = { version = "4.5", features = ["derive"] } 26 | 27 | [lib] 28 | name = "stdfuzzer" 29 | crate-type = ["staticlib"] 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StdFuzzer 2 | 3 | # **Deprecated! This has not been maintained in a while. Instead. check out [the example fuzzers in the main LibAFL repo](https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers)** 4 | 5 | StdFuzzer is the reference implementation of a generic source-based bit-level fuzzer with LibAFL 6 | 7 | ![screenshot](assets/screenshot.png) 8 | 9 | ## Building 10 | 11 | Build with 12 | 13 | ``` 14 | $ cargo build --release 15 | ``` 16 | 17 | ## Compiling a target 18 | 19 | Compile a target setting the compiler wrappers for C and C++ as compilers in the build system. 20 | 21 | For instance: 22 | 23 | ``` 24 | $ CC=/path/to/StdFuzzer/target/build/libafl_cc CXX=/path/to/StdFuzzer/target/build/libafl_cxx ./configure 25 | $ make 26 | ``` 27 | 28 | This fuzzer assumes that the target is exporting a LibFuzzer harness (`LLVMTestOneInput`). 29 | 30 | ## Run the fuzzer 31 | 32 | Take the output harness binary and execute with -h to see the required command line: 33 | 34 | ``` 35 | $ ./harness -h 36 | StdFuzzer 0.1.0 37 | Andrea Fioraldi 38 | StdFuzzer is the reference implementation of a generic bit-level fuzzer with LibAFL 39 | 40 | USAGE: 41 | harness [OPTIONS] --cores --broker-port 42 | 43 | FLAGS: 44 | -h, --help Prints help information 45 | -V, --version Prints version information 46 | 47 | OPTIONS: 48 | -c, --cores Spawn a client in each of the provided cores. Broker runs in the 0th core. 49 | 'all' to select all available cores. 'none' to run a client without binding to 50 | any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6. 51 | -i, --input ... Set an initial corpus directory 52 | -o, --output Set the output directory, default is ./out [default: ./out] 53 | -p, --broker-port Choose the broker TCP port, default is 1337 54 | -a, --remote-broker-addr Specify a remote broker 55 | -t, --timeout Set the exeucution timeout in milliseconds, default is 1000 [default: 1000] 56 | -x, --tokens ... Feed the fuzzer with an user-specified list of tokens (often called 57 | "dictionary" 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFLplusplus/StdFuzzer/a8ce3505e6a9fe393274d1ba2391c8900298f2fe/assets/screenshot.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Crate" 3 | -------------------------------------------------------------------------------- /src/bin/libafl_cc.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper}; 4 | 5 | pub fn main() { 6 | let args: Vec = env::args().collect(); 7 | if args.len() > 1 { 8 | let mut dir = env::current_exe().unwrap(); 9 | let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); 10 | 11 | let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { 12 | "cc" => false, 13 | "++" | "pp" | "xx" => true, 14 | _ => panic!("Could not figure out if c or c++ warpper was called. Expected {:?} to end with c or cxx", dir), 15 | }; 16 | 17 | dir.pop(); 18 | 19 | let mut cc = ClangWrapper::new(); 20 | if let Some(code) = cc 21 | .cpp(is_cpp) 22 | // silence the compiler wrapper output, needed for some configure scripts. 23 | .silence(true) 24 | .parse_args(&args) 25 | .expect("Failed to parse the command line") 26 | .link_staticlib(&dir, "stdfuzzer") 27 | .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp") 28 | .add_pass(LLVMPasses::CmpLogRtn) 29 | .run() 30 | .expect("Failed to run the wrapped compiler") 31 | { 32 | std::process::exit(code); 33 | } 34 | } else { 35 | panic!("LibAFL CC: No Arguments given"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/bin/libafl_cxx.rs: -------------------------------------------------------------------------------- 1 | pub mod libafl_cc; 2 | 3 | fn main() { 4 | libafl_cc::main() 5 | } 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A libfuzzer-like fuzzer with llmp-multithreading support and restarts 2 | //! The `launcher` will spawn new processes for each cpu core. 3 | use mimalloc::MiMalloc; 4 | #[global_allocator] 5 | static GLOBAL: MiMalloc = MiMalloc; 6 | 7 | use core::time::Duration; 8 | use std::{env, net::SocketAddr, path::PathBuf, ptr::addr_of_mut}; 9 | 10 | use clap::{self, Parser}; 11 | use libafl::{ 12 | corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, 13 | events::{launcher::Launcher, EventConfig}, 14 | executors::{inprocess::InProcessExecutor, ExitKind}, 15 | feedback_or, 16 | feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, 17 | fuzzer::{Fuzzer, StdFuzzer}, 18 | generators::RandBytesGenerator, 19 | inputs::{BytesInput, HasTargetBytes}, 20 | monitors::tui::{ui::TuiUI, TuiMonitor}, 21 | mutators::{ 22 | scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, 23 | token_mutations::{I2SRandReplace, Tokens}, 24 | StdMOptMutator, 25 | }, 26 | observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver}, 27 | schedulers::{ 28 | powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, PowerQueueScheduler, 29 | }, 30 | stages::{ 31 | calibrate::CalibrationStage, power::StdPowerMutationalStage, StdMutationalStage, 32 | TracingStage, 33 | }, 34 | state::{HasCorpus, StdState}, 35 | Error, HasMetadata, 36 | }; 37 | use libafl_bolts::{ 38 | core_affinity::Cores, 39 | current_nanos, 40 | rands::StdRand, 41 | shmem::{ShMemProvider, StdShMemProvider}, 42 | tuples::{tuple_list, Merge}, 43 | AsSlice, 44 | }; 45 | use libafl_targets::{ 46 | edges_max_num, libfuzzer_initialize, libfuzzer_test_one_input, CmpLogObserver, CMPLOG_MAP, 47 | EDGES_MAP, 48 | }; 49 | 50 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 51 | 52 | /// Parses a millseconds int into a [`Duration`], used for commandline arg parsing 53 | fn timeout_from_millis_str(time: &str) -> Result { 54 | Ok(Duration::from_millis(time.parse()?)) 55 | } 56 | 57 | #[derive(Debug, Parser)] 58 | #[command( 59 | name = "StdFuzzer", 60 | about = "StdFuzzer is the reference implementation of a generic bit-level fuzzer with LibAFL", 61 | author = "Andrea Fioraldi ", 62 | version = VERSION 63 | )] 64 | struct Opt { 65 | #[arg( 66 | short, 67 | long, 68 | value_parser = Cores::from_cmdline, 69 | help = "Spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6.", 70 | name = "CORES" 71 | )] 72 | cores: Cores, 73 | 74 | #[arg( 75 | short = 'p', 76 | long, 77 | help = "Choose the broker TCP port, default is 1337", 78 | name = "PORT" 79 | )] 80 | broker_port: u16, 81 | 82 | #[arg(short = 'a', long, help = "Specify a remote broker", name = "REMOTE")] 83 | remote_broker_addr: Option, 84 | 85 | #[arg(short, long, help = "Set an initial corpus directory", name = "INPUT")] 86 | input: Vec, 87 | 88 | #[arg( 89 | short, 90 | long, 91 | help = "Set the output directory, default is ./out", 92 | name = "OUTPUT", 93 | default_value = "./out" 94 | )] 95 | output: PathBuf, 96 | 97 | #[arg( 98 | value_parser = timeout_from_millis_str, 99 | short, 100 | long, 101 | help = "Set the execution timeout in milliseconds, default is 1000", 102 | name = "TIMEOUT", 103 | default_value = "1000" 104 | )] 105 | timeout: Duration, 106 | 107 | #[arg( 108 | short = 'x', 109 | long, 110 | help = "Feed the fuzzer with an user-specified list of tokens (often called \"dictionary\"", 111 | name = "TOKENS" 112 | )] 113 | tokens: Vec, 114 | 115 | #[arg( 116 | long, 117 | help = "Disable unicode in the UI (for old terminals)", 118 | name = "DISABLE_UNICODE" 119 | )] 120 | disable_unicode: bool, 121 | } 122 | 123 | /// The main fn, `no_mangle` as it is a C symbol 124 | #[no_mangle] 125 | pub fn libafl_main() { 126 | let workdir = env::current_dir().unwrap(); 127 | 128 | let opt = Opt::parse(); 129 | 130 | let cores = opt.cores; 131 | let broker_port = opt.broker_port; 132 | let remote_broker_addr = opt.remote_broker_addr; 133 | let input_dirs = opt.input; 134 | let output_dir = opt.output; 135 | let token_files = opt.tokens; 136 | let timeout_ms = opt.timeout; 137 | // let cmplog_enabled = matches.is_present("cmplog"); 138 | 139 | println!("Workdir: {:?}", workdir.to_string_lossy().to_string()); 140 | 141 | let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); 142 | 143 | let monitor = TuiMonitor::new(TuiUI::new( 144 | format!("LibAFL's StdFuzzer v{}", VERSION), 145 | !opt.disable_unicode, 146 | )); 147 | 148 | let mut run_client = |state: Option<_>, mut mgr, core_id| { 149 | // Create an observation channel using the coverage map 150 | let edges_observer = HitcountsMapObserver::new(unsafe { 151 | StdMapObserver::from_mut_ptr("edges", EDGES_MAP.as_mut_ptr(), edges_max_num()) 152 | }) 153 | .track_indices(); 154 | 155 | // Create an observation channel to keep track of the execution time 156 | let time_observer = TimeObserver::new("time"); 157 | 158 | // Create the Cmp observer 159 | let cmplog_observer = 160 | unsafe { CmpLogObserver::with_map_ptr("cmplog", addr_of_mut!(CMPLOG_MAP), true) }; 161 | 162 | // Feedback to rate the interestingness of an input 163 | // This one is composed by two Feedbacks in OR 164 | let mut feedback = feedback_or!( 165 | // New maximization map feedback linked to the edges observer and the feedback state 166 | MaxMapFeedback::new(&edges_observer), 167 | // Time feedback, this one does not need a feedback state 168 | TimeFeedback::new(&time_observer) 169 | ); 170 | 171 | // A feedback to choose if an input is a solution or not 172 | let mut objective = feedback_or!(CrashFeedback::new(), TimeoutFeedback::new()); 173 | 174 | // If not restarting, create a State from scratch 175 | let mut state = state.unwrap_or_else(|| { 176 | StdState::new( 177 | // RNG 178 | StdRand::with_seed(current_nanos()), 179 | // Corpus that will be evolved, we keep it in memory for performance 180 | InMemoryCorpus::new(), 181 | // Corpus in which we store solutions (crashes in this example), 182 | // on disk so the user can get them after stopping the fuzzer 183 | OnDiskCorpus::new(output_dir.clone()).unwrap(), 184 | // States of the feedbacks. 185 | // The feedbacks can report the data that should persist in the State. 186 | &mut feedback, 187 | // Same for objective feedbacks 188 | &mut objective, 189 | ) 190 | .unwrap() 191 | }); 192 | 193 | // Create a dictionary if not existing 194 | state.metadata_or_insert_with(|| { 195 | Tokens::new() 196 | .add_from_files(&token_files) 197 | .expect("Could not read tokens files.") 198 | }); 199 | 200 | // The actual target run starts here. 201 | // Call LLVMFUzzerInitialize() if present. 202 | let args: Vec = env::args().collect(); 203 | if libfuzzer_initialize(&args) == -1 { 204 | println!("Warning: LLVMFuzzerInitialize failed with -1") 205 | } 206 | 207 | let map_feedback = MaxMapFeedback::new(&edges_observer); 208 | let calibration = CalibrationStage::new(&map_feedback); 209 | 210 | // Setup a randomic Input2State stage 211 | let i2s = 212 | StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); 213 | 214 | // Setup a MOPT mutator 215 | let mutator = StdMOptMutator::new( 216 | &mut state, 217 | havoc_mutations().merge(tokens_mutations()), 218 | 7, 219 | 5, 220 | )?; 221 | 222 | let power = StdPowerMutationalStage::new(mutator); 223 | 224 | // A minimization+queue policy to get testcasess from the corpus 225 | let scheduler = IndexesLenTimeMinimizerScheduler::new( 226 | &edges_observer, 227 | PowerQueueScheduler::new(&mut state, &edges_observer, PowerSchedule::FAST), 228 | ); 229 | 230 | // A fuzzer with feedbacks and a corpus scheduler 231 | let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); 232 | 233 | // The wrapped harness function, calling out to the LLVM-style harness 234 | let mut harness = |input: &BytesInput| { 235 | let target = input.target_bytes(); 236 | let buf = target.as_slice(); 237 | libfuzzer_test_one_input(buf); 238 | ExitKind::Ok 239 | }; 240 | 241 | // Create the executor for an in-process function with one observer for edge coverage and one for the execution time 242 | let mut executor = InProcessExecutor::with_timeout( 243 | &mut harness, 244 | tuple_list!(edges_observer, time_observer), 245 | &mut fuzzer, 246 | &mut state, 247 | &mut mgr, 248 | timeout_ms, 249 | )?; 250 | 251 | // Secondary harness due to mut ownership 252 | let mut harness = |input: &BytesInput| { 253 | let target = input.target_bytes(); 254 | let buf = target.as_slice(); 255 | libfuzzer_test_one_input(buf); 256 | ExitKind::Ok 257 | }; 258 | 259 | // Setup a tracing stage in which we log comparisons 260 | let tracing = TracingStage::new(InProcessExecutor::new( 261 | &mut harness, 262 | tuple_list!(cmplog_observer), 263 | &mut fuzzer, 264 | &mut state, 265 | &mut mgr, 266 | )?); 267 | 268 | // The order of the stages matter! 269 | let mut stages = tuple_list!(calibration, tracing, i2s, power); 270 | 271 | // In case the corpus is empty (on first run), reset 272 | if state.must_load_initial_inputs() { 273 | if input_dirs.is_empty() { 274 | // Generator of printable bytearrays of max size 32 275 | let mut generator = RandBytesGenerator::new(32); 276 | 277 | // Generate 8 initial inputs 278 | state 279 | .generate_initial_inputs( 280 | &mut fuzzer, 281 | &mut executor, 282 | &mut generator, 283 | &mut mgr, 284 | 8, 285 | ) 286 | .expect("Failed to generate the initial corpus"); 287 | println!( 288 | "We imported {} inputs from the generator.", 289 | state.corpus().count() 290 | ); 291 | } else { 292 | println!("Loading from {:?}", &input_dirs); 293 | // Load from disk 294 | state 295 | .load_initial_inputs_multicore( 296 | &mut fuzzer, 297 | &mut executor, 298 | &mut mgr, 299 | &input_dirs, 300 | &core_id, 301 | &cores, 302 | ) 303 | .unwrap_or_else(|_| { 304 | panic!("Failed to load initial corpus at {:?}", &input_dirs) 305 | }); 306 | println!("We imported {} inputs from disk.", state.corpus().count()); 307 | } 308 | } 309 | 310 | fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; 311 | Ok(()) 312 | }; 313 | 314 | match Launcher::builder() 315 | .shmem_provider(shmem_provider) 316 | .configuration(EventConfig::from_build_id()) 317 | .monitor(monitor) 318 | .run_client(&mut run_client) 319 | .cores(&cores) 320 | .broker_port(broker_port) 321 | .remote_broker_addr(remote_broker_addr) 322 | .stdout_file(Some("/dev/null")) 323 | .build() 324 | .launch() 325 | { 326 | Ok(_) | Err(Error::ShuttingDown) => (), 327 | Err(e) => panic!("{:?}", e), 328 | }; 329 | } 330 | --------------------------------------------------------------------------------