diff --git a/fuzzers/others/libafl-fuzz/Makefile.toml b/fuzzers/others/libafl-fuzz/Makefile.toml index c5a9404e9a..b8f9598a0f 100644 --- a/fuzzers/others/libafl-fuzz/Makefile.toml +++ b/fuzzers/others/libafl-fuzz/Makefile.toml @@ -15,7 +15,7 @@ LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [ AFL_VERSION = "db23931e7c1727ddac8691a6241c97b2203ec6fc" AFL_DIR_NAME = { value = "./AFLplusplus-${AFL_VERSION}" } AFL_CC_PATH = { value = "${AFL_DIR_NAME}/afl-clang-fast" } - +CC = { value = "clang" } [tasks.build_afl] script_runner = "@shell" @@ -28,7 +28,9 @@ if [ ! -d "$AFL_DIR_NAME" ]; then unzip ${AFL_VERSION}.zip cd ${AFL_DIR_NAME} LLVM_CONFIG=${LLVM_CONFIG} make - cd .. + cd frida_mode + LLVM_CONFIG=${LLVM_CONFIG} make + cd ../.. fi ''' @@ -40,6 +42,11 @@ windows_alias = "unsupported" [tasks.test_unix] script_runner = "@shell" +script = "echo done" +dependencies = ["build_afl", "test_instr", "test_cmplog", "test_frida"] + +[tasks.test_instr] +script_runner = "@shell" script = ''' cargo build --profile ${PROFILE} AFL_PATH=${AFL_DIR_NAME} ${AFL_CC_PATH} ./test/test-instr.c -o ./test/out-instr @@ -64,16 +71,63 @@ test -d "./test/output/fuzzer_main/crashes" || { echo "No crashes directory found" exit 1 } +''' +dependencies = ["build_afl"] + +[tasks.test_cmplog] +script_runner = "@shell" +script = ''' +cargo build --profile ${PROFILE} # cmplog TODO: AFL_BENCH_UNTIL_CRASH=1 instead of timeout 15s AFL_LLVM_CMPLOG=1 AFL_PATH=${AFL_DIR_NAME} ${AFL_CC_PATH} ./test/test-cmplog.c -o ./test/out-cmplog -AFL_CORES=1 timeout 5 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/cmplog-output -c 0 ./test/out-cmplog || true -test -n "$( find "./test/cmplog-output/fuzzer_main/crashes/" -maxdepth 1 -type f) 2>/dev/null )" || { +AFL_CORES=1 timeout 5 ${FUZZER} -Z -l 3 -m 0 -V30 -i ./test/seeds_cmplog -o ./test/output-cmplog -c 0 ./test/out-cmplog || true +test -n "$( ls -A ./test/output-cmplog/fuzzer_main/crashes/)" || { echo "No crashes found" exit 1 } ''' dependencies = ["build_afl"] +[tasks.test_frida] +script_runner = "@shell" +script = ''' +cargo build --profile ${PROFILE} +${CC} -no-pie ./test/test-instr.c -o ./test/out-frida +AFL_PATH=${AFL_DIR_NAME} AFL_CORES=1 AFL_STATS_INTERVAL=1 timeout 5 ${FUZZER} -m 0 -V07 -O -i ./test/seeds-frida -o ./test/output-frida -- ./test/out-frida || true +test -n "$( ls ./test/output-frida/fuzzer_main/queue/id:000002* 2>/dev/null )" || { + echo "No new corpus entries found for FRIDA mode" + exit 1 +} + +${CC} ./test/test-cmpcov.c -o ./test/out-frida-cmpcov +AFL_PATH=${AFL_DIR_NAME} LIBAFL_DEBUG_OUTPUT=1 AFL_DEBUG=1 AFL_CORES=1 AFL_FRIDA_VERBOSE=1 timeout 10 ${FUZZER} -m 0 -V07 -O -c 0 -l 3 -i ./test/seeds-frida -o ./test/output-frida-cmpcov -- ./test/out-frida-cmpcov || true +test -n "$( ls ./test/output-frida-cmpcov/fuzzer_main/queue/id:000003* 2>/dev/null )" || { + echo "No new corpus entries found for FRIDA cmplog mode" + exit 1 +} +export AFL_FRIDA_PERSISTENT_ADDR=0x`nm ./test/out-frida | grep -Ei "T _main|T main" | awk '{print $1}'` +AFL_PATH=${AFL_DIR_NAME} AFL_STATS_INTERVAL=1 AFL_CORES=1 timeout 5 ${FUZZER} -m 0 -V07 -O -i ./test/seeds-frida -o ./test/output-frida-persistent -- ./test/out-frida || true +# TODO: change it to id:000003* once persistent mode is fixed +test -n "$( ls ./test/output-frida-persistent/fuzzer_main/queue/id:000002* 2>/dev/null )" || { + echo "No new corpus entries found for FRIDA persistent mode" + exit 1 +} +RUNTIME_PERSISTENT=`grep execs_done ./test/output-frida-persistent/fuzzer_main/fuzzer_stats | awk '{print$3}'` +RUNTIME=`grep execs_done ./test/output-frida/fuzzer_main/fuzzer_stats | awk '{print$3}'` +test -n "$RUNTIME" -a -n "$RUNTIME_PERSISTENT" && { + DIFF=`expr $RUNTIME_PERSISTENT / $RUNTIME` + test "$DIFF" -gt 1 && { # must be at least twice as fast + echo "persistent frida_mode was noticeably faster than standard frida_mode" + } || { + echo "persistent frida_mode" $RUNTIME_PERSISTENT "was not noticeably faster than standard frida_mode" $RUNTIME + exit 1 + } +} || { + echo "we got no data on executions performed? weird!" +} +''' +dependencies = ["build_afl"] + [tasks.clean] linux_alias = "clean_unix" mac_alias = "clean_unix" @@ -86,4 +140,10 @@ rm -rf AFLplusplus-${AFL_VERSION} rm ${AFL_VERSION}.zip rm -rf ./test/out-instr rm -rf ./test/output +rm -rf ./test/cmplog-output +rm -rf ./test/output-frida +rm -rf ./test/output-frida-cmpcov +rm -rf ./test/output-frida-persistent +rm -rf ./test/output-cmplog +rm ./test/out-* ''' diff --git a/fuzzers/others/libafl-fuzz/src/corpus.rs b/fuzzers/others/libafl-fuzz/src/corpus.rs index cc95ff5106..ed2a9f82d2 100644 --- a/fuzzers/others/libafl-fuzz/src/corpus.rs +++ b/fuzzers/others/libafl-fuzz/src/corpus.rs @@ -151,12 +151,12 @@ pub fn check_autoresume( path.display() )))?), ); - if let Err(e) = cpy_res { - if matches!(e.kind(), io::ErrorKind::InvalidInput) { + match cpy_res { + Err(e) if e.kind() == io::ErrorKind::InvalidInput => { println!("skipping {} since it is not a regular file", path.display()); - } else { - return Err(e.into()); } + Err(e) => return Err(e.into()), + Ok(_) => {} } } } diff --git a/fuzzers/others/libafl-fuzz/src/env_parser.rs b/fuzzers/others/libafl-fuzz/src/env_parser.rs index c1e6f385da..fc58715ffe 100644 --- a/fuzzers/others/libafl-fuzz/src/env_parser.rs +++ b/fuzzers/others/libafl-fuzz/src/env_parser.rs @@ -110,6 +110,9 @@ pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> { } else { opt.foreign_sync_interval = Duration::from_secs(AFL_DEFAULT_FOREIGN_SYNC_INTERVAL); } + if let Ok(res) = std::env::var("AFL_USE_FASAN") { + opt.frida_asan = parse_bool(&res)?; + } Ok(()) } diff --git a/fuzzers/others/libafl-fuzz/src/executor.rs b/fuzzers/others/libafl-fuzz/src/executor.rs index bf7dabeeda..f4685d7c1a 100644 --- a/fuzzers/others/libafl-fuzz/src/executor.rs +++ b/fuzzers/others/libafl-fuzz/src/executor.rs @@ -6,9 +6,13 @@ use std::{ use libafl::Error; use memmap2::{Mmap, MmapOptions}; +use nix::libc::{S_IRUSR, S_IXUSR}; use crate::{Opt, DEFER_SIG, PERSIST_SIG}; +const AFL_PATH: &str = "/usr/local/lib/afl/"; +const BIN_PATH: &str = "/usr/local/bin/"; + // TODO better error messages and logging pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> { println!("Validating target binary..."); @@ -115,7 +119,9 @@ pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> { if opt.forkserver_cs || opt.qemu_mode || opt.frida_mode && is_instrumented(&mmap, shmem_env_var) { - return Err(Error::illegal_argument("Instrumentation found in -Q mode")); + return Err(Error::illegal_argument( + "Instrumentation found in -Q/-O mode", + )); } if mmap_has_substr(&mmap, "__asan_init") @@ -130,6 +136,8 @@ pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> { } else if opt.is_persistent { println!("persistent mode enforced"); } else if opt.frida_persistent_addr.is_some() { + opt.is_persistent = true; + opt.defer_forkserver = true; println!("FRIDA persistent mode configuration options detected"); } @@ -162,7 +170,7 @@ fn is_instrumented(mmap: &Mmap, shmem_env_var: &str) -> bool { mmap_has_substr(mmap, shmem_env_var) } -fn find_executable_in_path(executable: &Path) -> Option { +fn find_executable_in_path>(executable: &P) -> Option { std::env::var_os("PATH").and_then(|paths| { std::env::split_paths(&paths).find_map(|dir| { let full_path = dir.join(executable); @@ -174,3 +182,57 @@ fn find_executable_in_path(executable: &Path) -> Option { }) }) } + +pub fn find_afl_binary(filename: &str, same_dir_as: Option) -> Result { + let is_library = + filename.contains('.') && filename.ends_with(".so") || filename.ends_with(".dylib"); + + let permission = if is_library { + S_IRUSR // user can read + } else { + S_IXUSR // user can exec + }; + + // First we check if it is present in AFL_PATH + if let Ok(afl_path) = std::env::var("AFL_PATH") { + let file = PathBuf::from(afl_path).join(filename); + if check_file_found(&file, permission) { + return Ok(file); + } + } + + // next we check the same directory as the provided parameter + if let Some(same_dir_as) = same_dir_as { + if let Some(parent_dir) = same_dir_as.parent() { + let file = parent_dir.join(filename); + if check_file_found(&file, permission) { + return Ok(file); + } + } + } + + // check sensible defaults + let file = PathBuf::from(if is_library { AFL_PATH } else { BIN_PATH }).join(filename); + let found = check_file_found(&file, permission); + if found { + return Ok(file); + } + + if !is_library { + // finally, check the path for the binary + return find_executable_in_path(&filename) + .ok_or(Error::unknown(format!("cannot find {filename}"))); + } + + Err(Error::unknown(format!("cannot find {filename}"))) +} + +fn check_file_found(file: &PathBuf, perm: u32) -> bool { + if !file.exists() { + return false; + } + if let Ok(metadata) = file.metadata() { + return metadata.permissions().mode() & perm != 0; + } + false +} diff --git a/fuzzers/others/libafl-fuzz/src/fuzzer.rs b/fuzzers/others/libafl-fuzz/src/fuzzer.rs index 7d69a21e26..f9a360bfd1 100644 --- a/fuzzers/others/libafl-fuzz/src/fuzzer.rs +++ b/fuzzers/others/libafl-fuzz/src/fuzzer.rs @@ -46,6 +46,7 @@ use crate::{ afl_stats::{AflStatsStage, CalibrationTime, FuzzTime, SyncTime}, corpus::{set_corpus_filepath, set_solution_filepath}, env_parser::AFL_DEFAULT_MAP_SIZE, + executor::find_afl_binary, feedback::{ filepath::CustomFilepathToTestcaseFeedback, persistent_record::PersitentRecordFeedback, seed::SeedFeedback, @@ -209,6 +210,29 @@ where // Create our Fuzzer let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + // Set LD_PRELOAD (Linux) && DYLD_INSERT_LIBRARIES (OSX) for target. + if let Some(preload_env) = &opt.afl_preload { + std::env::set_var("LD_PRELOAD", preload_env); + std::env::set_var("DYLD_INSERT_LIBRARIES", preload_env); + } + + // Insert appropriate shared libraries if frida_mode + if opt.frida_mode { + if opt.frida_asan { + std::env::set_var("ASAN_OPTIONS", "detect_leaks=false"); + } + let frida_bin = find_afl_binary("afl-frida-trace.so", Some(opt.executable.clone()))? + .display() + .to_string(); + let preload = if let Some(preload_env) = &opt.afl_preload { + format!("{preload_env}:{frida_bin}") + } else { + frida_bin + }; + std::env::set_var("LD_PRELOAD", &preload); + std::env::set_var("DYLD_INSERT_LIBRARIES", &preload); + } + // Create the base Executor let mut executor = base_executor(opt, &mut shmem_provider); // Set a custom exit code to be interpreted as a Crash if configured. @@ -279,12 +303,6 @@ where // Tell [`SeedFeedback`] that we're done loading seeds; rendering it benign. fuzzer.feedback_mut().done_loading_seeds(); - // Set LD_PRELOAD (Linux) && DYLD_INSERT_LIBRARIES (OSX) for target. - if let Some(preload_env) = &opt.afl_preload { - std::env::set_var("LD_PRELOAD", preload_env); - std::env::set_var("DYLD_INSERT_LIBRARIES", preload_env); - } - // Create a Sync stage to sync from foreign fuzzers let sync_stage = IfStage::new( |_, _, _, _| Ok(is_main_node && !opt.foreign_sync_dirs.is_empty()), @@ -395,7 +413,6 @@ fn base_executor<'a>( ) -> ForkserverExecutorBuilder<'a, StdShMemProvider> { let mut executor = ForkserverExecutor::builder() .program(opt.executable.clone()) - .shmem_provider(shmem_provider) .coverage_map_size(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE)) .debug_child(opt.debug_child) .is_persistent(opt.is_persistent) @@ -409,6 +426,9 @@ fn base_executor<'a>( if let Some(kill_signal) = opt.kill_signal { executor = executor.kill_signal(kill_signal); } + if opt.is_persistent { + executor = executor.shmem_provider(shmem_provider); + } if let Some(harness_input_type) = &opt.harness_input_type { executor = executor.parse_afl_cmdline([harness_input_type]); } diff --git a/fuzzers/others/libafl-fuzz/src/main.rs b/fuzzers/others/libafl-fuzz/src/main.rs index 4df2a894f7..3bcb8098d9 100644 --- a/fuzzers/others/libafl-fuzz/src/main.rs +++ b/fuzzers/others/libafl-fuzz/src/main.rs @@ -6,6 +6,7 @@ #![allow(clippy::similar_names)] #![allow(clippy::too_many_lines)] #![allow(clippy::struct_excessive_bools)] +#![allow(clippy::case_sensitive_file_extension_comparisons)] use std::{collections::HashMap, path::PathBuf, time::Duration}; mod afl_stats; @@ -236,6 +237,8 @@ struct Opt { /// use binary-only instrumentation (FRIDA mode) #[arg(short = 'O')] frida_mode: bool, + #[clap(skip)] + frida_asan: bool, /// use binary-only instrumentation (QEMU mode) #[arg(short = 'Q')] qemu_mode: bool, diff --git a/fuzzers/others/libafl-fuzz/test/seeds-frida/init b/fuzzers/others/libafl-fuzz/test/seeds-frida/init new file mode 100644 index 0000000000..5c0ac06f55 --- /dev/null +++ b/fuzzers/others/libafl-fuzz/test/seeds-frida/init @@ -0,0 +1 @@ +00000 diff --git a/fuzzers/others/libafl-fuzz/test/test-cmpcov.c b/fuzzers/others/libafl-fuzz/test/test-cmpcov.c new file mode 100644 index 0000000000..eb0eb4fbc1 --- /dev/null +++ b/fuzzers/others/libafl-fuzz/test/test-cmpcov.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +char global_cmpval[] = "GLOBALVARIABLE"; + +int main(int argc, char **argv) { + char *input = argv[1], *buf, buffer[20]; + char cmpval[] = "LOCALVARIABLE"; + char shortval[4] = "abc"; + + if (argc < 2) { + ssize_t ret = read(0, buffer, sizeof(buffer) - 1); + buffer[ret] = 0; + input = buffer; + } + + if (strcmp(input, "LIBTOKENCAP") == 0) + printf("your string was LIBTOKENCAP\n"); + else if (strcmp(input, "BUGMENOT") == 0) + printf("your string was BUGMENOT\n"); + else if (strncmp(input, "BANANA", 3) == 0) + printf("your string started with BAN\n"); + else if (strcmp(input, "APRI\0COT") == 0) + printf("your string was APRI\n"); + else if (strcasecmp(input, "Kiwi") == 0) + printf("your string was Kiwi\n"); + else if (strncasecmp(input, "avocado", 9) == 0) + printf("your string was avocado\n"); + else if (strncasecmp(input, "Grapes", argc > 2 ? atoi(argv[2]) : 3) == 0) + printf("your string was a prefix of Grapes\n"); + else if (strstr(input, "tsala") != NULL) + printf("your string is a fruit salad\n"); + else if (strcmp(input, "BUFFEROVERFLOW") == 0) { + buf = (char *)malloc(16); + strcpy(buf, "TEST"); + strcat(buf, input); + printf("This will only crash with libdislocator: %s\n", buf); + + } else if (*(unsigned int *)input == 0xabadcafe) + + printf("GG you eat cmp tokens for breakfast!\n"); + else if (memcmp(cmpval, input, 8) == 0) + printf("local var memcmp works!\n"); + else if (memcmp(shortval, input, 4) == 0) + printf("short local var memcmp works!\n"); + else if (memcmp(global_cmpval, input, sizeof(global_cmpval)) == 0) + printf("global var memcmp works!\n"); + else if (strncasecmp("-h", input, 2) == 0) + printf("this is not the help you are looking for\n"); + else + printf("I do not know your string\n"); + + return 0; +}