From 07db74b4162856ba7f57b236a92959aa30f092d4 Mon Sep 17 00:00:00 2001 From: Aarnav Date: Mon, 26 Aug 2024 11:32:44 +0200 Subject: [PATCH] Libafl-fuzz: introduce unicorn mode (#2499) * libafl-fuzz: introduce unicorn mode * taplo format * libafl-fuzz: fix qemumode * taplo format --- fuzzers/others/libafl-fuzz/Makefile.toml | 48 ++++++++++++++--- fuzzers/others/libafl-fuzz/src/fuzzer.rs | 66 ++++++++++++------------ fuzzers/others/libafl-fuzz/src/main.rs | 10 ---- 3 files changed, 73 insertions(+), 51 deletions(-) diff --git a/fuzzers/others/libafl-fuzz/Makefile.toml b/fuzzers/others/libafl-fuzz/Makefile.toml index 1e2fdd3c1d..868ed12e00 100644 --- a/fuzzers/others/libafl-fuzz/Makefile.toml +++ b/fuzzers/others/libafl-fuzz/Makefile.toml @@ -12,7 +12,7 @@ FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}' LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [ "LLVM_CONFIG", ] } } -AFL_VERSION = "db23931e7c1727ddac8691a6241c97b2203ec6fc" +AFL_VERSION = "598a3c6b5e24bd33e84b914e145810d39f88adf6" AFL_DIR = { value = "./AFLplusplus" } AFL_CC_PATH = { value = "${AFL_DIR}/afl-clang-fast" } CC = { value = "clang" } @@ -39,6 +39,16 @@ cd ${AFL_DIR}/qemu_mode cd ../.. ''' dependencies = ["build_afl"] + +[tasks.build_unicorn_mode] +script_runner = "@shell" +script = ''' +cd ${AFL_DIR}/unicorn_mode +./build_unicorn_support.sh +cd ../.. +''' +dependencies = ["build_afl"] + # Test [tasks.test] linux_alias = "test_unix" @@ -54,6 +64,7 @@ dependencies = [ "test_cmplog", "test_frida", "test_qemu", + "test_unicorn_mode", ] [tasks.build_libafl_fuzz] @@ -115,20 +126,20 @@ export AFL_PATH=${AFL_DIR} export AFL_CORES=1 export AFL_STATS_INTERVAL=1 -timeout 5 ${FUZZER} -m 0 -V07 -O -i ./test/seeds_frida -o ./test/output-frida -- ./test/out-frida || true +timeout 5 ${FUZZER} -m 0 -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_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 +AFL_FRIDA_VERBOSE=1 timeout 10 ${FUZZER} -m 0 -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}'` -timeout 5 ${FUZZER} -m 0 -V07 -O -i ./test/seeds_frida -o ./test/output-frida-persistent -- ./test/out-frida || true +timeout 5 ${FUZZER} -m 0 -O -i ./test/seeds_frida -o ./test/output-frida-persistent -- ./test/out-frida || true 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" @@ -163,14 +174,14 @@ export AFL_PATH=${AFL_DIR} export AFL_CORES=1 export AFL_STATS_INTERVAL=1 -timeout 5 ${FUZZER} -m 0 -V07 -Q -i ./test/seeds_qemu -o ./test/output-qemu -- ./test/out-qemu || true +timeout 5 ${FUZZER} -m 0 -Q -i ./test/seeds_qemu -o ./test/output-qemu -- ./test/out-qemu || true test -n "$( ls ./test/output-qemu/fuzzer_main/queue/id:000002* 2>/dev/null )" || { echo "No new corpus entries found for QEMU mode" exit 1 } export AFL_ENTRYPOINT=`printf 1 | AFL_DEBUG=1 ${AFL_DIR}/afl-qemu-trace ./test/out-qemu 2>&1 >/dev/null | awk '/forkserver/{print $4; exit}'` -timeout 5 ${FUZZER} -m 0 -V2 -Q -i ./test/seeds_qemu -o ./test/output-qemu-entrypoint -- ./test/out-qemu || true +timeout 5 ${FUZZER} -m 0 -Q -i ./test/seeds_qemu -o ./test/output-qemu-entrypoint -- ./test/out-qemu || true test -n "$( ls ./test/output-qemu-entrypoint/fuzzer_main/queue/id:000002* 2>/dev/null )" || { echo "No new corpus entries found for QEMU mode with AFL_ENTRYPOINT" exit 1 @@ -179,7 +190,7 @@ unset AFL_ENTRYPOINT export AFL_PRELOAD=${AFL_DIR}/libcompcov.so export AFL_COMPCOV_LEVEL=2 -timeout 5 ${FUZZER} -V07 -Q -i ./test/seeds_qemu -o ./test/output-qemu-cmpcov -- ./test/out-qemu-cmpcov || true +timeout 5 ${FUZZER} -Q -i ./test/seeds_qemu -o ./test/output-qemu-cmpcov -- ./test/out-qemu-cmpcov || true test -n "$( ls ./test/output-qemu-cmpcov/fuzzer_main/queue/id:000002* 2>/dev/null )" || { echo "No new corpus entries found for QEMU mode" exit 1 @@ -187,6 +198,27 @@ test -n "$( ls ./test/output-qemu-cmpcov/fuzzer_main/queue/id:000002* 2>/dev/nul ''' dependencies = ["build_afl", "build_qemuafl", "build_libafl_fuzz"] +[tasks.test_unicorn_mode] +script_runner = "@shell" +script = ''' +export AFL_PATH=${AFL_DIR} +export AFL_CORES=1 +export AFL_STATS_INTERVAL=1 +# TODO: test unicorn persistent mode once it's fixed on AFL++ +LIBAFL_DEBUG_OUTPUT=1 AFL_DEBUG=1 AFL_DEBUG_CHILD=1 timeout 15s ${FUZZER} -m 0 -U -i ./test/seeds_unicorn -o ./test/output-unicorn-python -- python3 ${AFL_DIR}/unicorn_mode/samples/python_simple/simple_test_harness.py @@ || true +test -n "$( ls ./test/output-unicorn-python/fuzzer_main/queue/id:000003* 2>/dev/null )" || { + echo "No new corpus entries found for Unicorn python3 mode" + exit 1 +} +export AFL_COMPCOV_LEVEL=2 +LIBAFL_DEBUG_OUTPUT=1 AFL_DEBUG=1 AFL_DEBUG_CHILD=1 timeout 15s ${FUZZER} -m 0 -U -i ./test/seeds_unicorn_cmpcov -o ./test/output-unicorn-cmpcov -- python3 ${AFL_DIR}/unicorn_mode/samples/compcov_x64/compcov_test_harness.py @@ || true +test -n "$( ls ./test/output-unicorn-cmpcov/fuzzer_main/queue/id:000002* 2>/dev/null )" || { + echo "No new corpus entries found for Unicorn cmpcov mode" + exit 1 +} +''' +dependencies = ["build_libafl_fuzz", "build_afl", "build_unicorn_mode"] + [tasks.clean] linux_alias = "clean_unix" mac_alias = "clean_unix" @@ -196,11 +228,11 @@ windows_alias = "unsupported" script_runner = "@shell" script = ''' rm -rf AFLplusplus -rm -rf ./test/out-instr rm -rf ./test/output rm -rf ./test/cmplog-output rm -rf ./test/output-frida* rm -rf ./test/output-cmplog rm -rf ./test/output-qemu* +rm -rf ./test/output-unicorn* rm ./test/out-* ''' diff --git a/fuzzers/others/libafl-fuzz/src/fuzzer.rs b/fuzzers/others/libafl-fuzz/src/fuzzer.rs index dce415b7d4..2da969cb0b 100644 --- a/fuzzers/others/libafl-fuzz/src/fuzzer.rs +++ b/fuzzers/others/libafl-fuzz/src/fuzzer.rs @@ -53,7 +53,8 @@ use crate::{ }, scheduler::SupportedSchedulers, stages::{mutational_stage::SupportedMutationalStages, time_tracker::TimeTrackingStageWrapper}, - Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, SHMEM_ENV_VAR, + Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, AFL_HARNESS_FILE_INPUT, + SHMEM_ENV_VAR, }; pub type LibaflFuzzState = @@ -234,7 +235,7 @@ where } // Create the base Executor - let mut executor = base_executor(opt, &mut shmem_provider); + let mut executor = base_executor(opt, &mut shmem_provider, fuzzer_dir)?; // Set a custom exit code to be interpreted as a Crash if configured. if let Some(crash_exitcode) = opt.crash_exitcode { executor = executor.crash_exitcode(crash_exitcode); @@ -245,24 +246,6 @@ where executor = executor.autotokens(&mut tokens); }; - // Set a custom directory for the current_input file if configured; - // Relevant only if harness input type is @@ - if opt.harness_input_type.is_some() { - let mut file = get_unique_std_input_file(); - if let Some(ext) = &opt.input_ext { - file = format!("{file}.{ext}"); - } - if let Some(cur_input_dir) = &opt.cur_input_dir { - executor = executor.arg_input_file(cur_input_dir.join(file)); - } else { - executor = executor.arg_input_file(fuzzer_dir.join(file)); - } - } else if opt.cur_input_dir.is_some() { - return Err(Error::illegal_argument( - "cannot use AFL_TMPDIR with stdin input type.", - )); - } - // Finalize and build our Executor let mut executor = executor .build(tuple_list!(time_observer, edges_observer)) @@ -339,7 +322,7 @@ where // Create the CmpLog executor. // Cmplog has 25% execution overhead so we give it double the timeout - let cmplog_executor = base_executor(opt, &mut shmem_provider) + let cmplog_executor = base_executor(opt, &mut shmem_provider, fuzzer_dir)? .timeout(Duration::from_millis(opt.hang_timeout * 2)) .program(cmplog_executable_path) .build(tuple_list!(cmplog_observer)) @@ -410,7 +393,8 @@ where fn base_executor<'a>( opt: &'a Opt, shmem_provider: &'a mut StdShMemProvider, -) -> ForkserverExecutorBuilder<'a, StdShMemProvider> { + fuzzer_dir: &PathBuf, +) -> Result, Error> { let mut executor = ForkserverExecutor::builder() .program(opt.executable.clone()) .coverage_map_size(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE)) @@ -429,24 +413,40 @@ fn base_executor<'a>( if opt.is_persistent || opt.qemu_mode { executor = executor.shmem_provider(shmem_provider); } - if let Some(harness_input_type) = &opt.harness_input_type { - executor = executor.parse_afl_cmdline([harness_input_type]); + // Set arguments for the target if necessary + let exec = opt.executable.display().to_string(); + // we skip all libafl-fuzz arguments. + let (mut skip, _) = env::args() + .enumerate() + .find(|i| i.1 == exec) + .expect("invariant; should never occur"); + // we need the binary to remain as an argument if we are in qemu_mode + if !opt.qemu_mode { + skip += 1; + } + let args = env::args().skip(skip); + for arg in args { + if arg == AFL_HARNESS_FILE_INPUT { + let mut file = get_unique_std_input_file(); + if let Some(ext) = &opt.input_ext { + file = format!("{file}.{ext}"); + } + if let Some(cur_input_dir) = &opt.cur_input_dir { + executor = executor.arg_input_file(cur_input_dir.join(file)); + } else { + executor = executor.arg_input_file(fuzzer_dir.join(file)); + } + } else { + executor = executor.arg(arg); + } } if opt.qemu_mode { - let exec = opt.executable.display().to_string(); executor = executor.program( find_afl_binary("afl-qemu-trace", Some(opt.executable.clone())) .expect("to find afl-qemu-trace"), ); - // we skip all libafl-fuzz arguments. - let (skip, _) = env::args() - .enumerate() - .find(|i| i.1 == exec) - .expect("invariant; should never occur"); - let args = env::args().skip(skip); - executor = executor.args(args); } - executor + Ok(executor) } pub fn fuzzer_target_mode(opt: &Opt) -> Cow<'static, str> { diff --git a/fuzzers/others/libafl-fuzz/src/main.rs b/fuzzers/others/libafl-fuzz/src/main.rs index 3bcb8098d9..69960a9b9a 100644 --- a/fuzzers/others/libafl-fuzz/src/main.rs +++ b/fuzzers/others/libafl-fuzz/src/main.rs @@ -112,9 +112,6 @@ fn main() { struct Opt { executable: PathBuf, - #[arg(value_parser = validate_harness_input_stdin)] - harness_input_type: Option<&'static str>, - // NOTE: afl-fuzz does not accept multiple input directories #[arg(short = 'i')] input_dir: PathBuf, @@ -258,13 +255,6 @@ struct Opt { non_instrumented_mode: bool, } -fn validate_harness_input_stdin(s: &str) -> Result<&'static str, String> { - if s != "@@" { - return Err("Unknown harness input type. Use \"@@\" for file, omit for stdin ".to_string()); - } - Ok(AFL_HARNESS_FILE_INPUT) -} - #[allow(dead_code)] #[derive(Debug, Clone)] pub struct CmplogOpts {