Libafl-fuzz: introduce unicorn mode (#2499)

* libafl-fuzz: introduce unicorn mode

* taplo format

* libafl-fuzz: fix qemumode

* taplo format
This commit is contained in:
Aarnav 2024-08-26 11:32:44 +02:00 committed by GitHub
parent a388012429
commit 07db74b416
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 51 deletions

View File

@ -12,7 +12,7 @@ FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}'
LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [ LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [
"LLVM_CONFIG", "LLVM_CONFIG",
] } } ] } }
AFL_VERSION = "db23931e7c1727ddac8691a6241c97b2203ec6fc" AFL_VERSION = "598a3c6b5e24bd33e84b914e145810d39f88adf6"
AFL_DIR = { value = "./AFLplusplus" } AFL_DIR = { value = "./AFLplusplus" }
AFL_CC_PATH = { value = "${AFL_DIR}/afl-clang-fast" } AFL_CC_PATH = { value = "${AFL_DIR}/afl-clang-fast" }
CC = { value = "clang" } CC = { value = "clang" }
@ -39,6 +39,16 @@ cd ${AFL_DIR}/qemu_mode
cd ../.. cd ../..
''' '''
dependencies = ["build_afl"] 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 # Test
[tasks.test] [tasks.test]
linux_alias = "test_unix" linux_alias = "test_unix"
@ -54,6 +64,7 @@ dependencies = [
"test_cmplog", "test_cmplog",
"test_frida", "test_frida",
"test_qemu", "test_qemu",
"test_unicorn_mode",
] ]
[tasks.build_libafl_fuzz] [tasks.build_libafl_fuzz]
@ -115,20 +126,20 @@ export AFL_PATH=${AFL_DIR}
export AFL_CORES=1 export AFL_CORES=1
export AFL_STATS_INTERVAL=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 )" || { test -n "$( ls ./test/output-frida/fuzzer_main/queue/id:000002* 2>/dev/null )" || {
echo "No new corpus entries found for FRIDA mode" echo "No new corpus entries found for FRIDA mode"
exit 1 exit 1
} }
${CC} ./test/test-cmpcov.c -o ./test/out-frida-cmpcov ${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 )" || { 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" echo "No new corpus entries found for FRIDA cmplog mode"
exit 1 exit 1
} }
export AFL_FRIDA_PERSISTENT_ADDR=0x`nm ./test/out-frida | grep -Ei "T _main|T main" | awk '{print $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 )" || { 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" 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_CORES=1
export AFL_STATS_INTERVAL=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 )" || { test -n "$( ls ./test/output-qemu/fuzzer_main/queue/id:000002* 2>/dev/null )" || {
echo "No new corpus entries found for QEMU mode" echo "No new corpus entries found for QEMU mode"
exit 1 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}'` 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 )" || { 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" echo "No new corpus entries found for QEMU mode with AFL_ENTRYPOINT"
exit 1 exit 1
@ -179,7 +190,7 @@ unset AFL_ENTRYPOINT
export AFL_PRELOAD=${AFL_DIR}/libcompcov.so export AFL_PRELOAD=${AFL_DIR}/libcompcov.so
export AFL_COMPCOV_LEVEL=2 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 )" || { test -n "$( ls ./test/output-qemu-cmpcov/fuzzer_main/queue/id:000002* 2>/dev/null )" || {
echo "No new corpus entries found for QEMU mode" echo "No new corpus entries found for QEMU mode"
exit 1 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"] 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] [tasks.clean]
linux_alias = "clean_unix" linux_alias = "clean_unix"
mac_alias = "clean_unix" mac_alias = "clean_unix"
@ -196,11 +228,11 @@ windows_alias = "unsupported"
script_runner = "@shell" script_runner = "@shell"
script = ''' script = '''
rm -rf AFLplusplus rm -rf AFLplusplus
rm -rf ./test/out-instr
rm -rf ./test/output rm -rf ./test/output
rm -rf ./test/cmplog-output rm -rf ./test/cmplog-output
rm -rf ./test/output-frida* rm -rf ./test/output-frida*
rm -rf ./test/output-cmplog rm -rf ./test/output-cmplog
rm -rf ./test/output-qemu* rm -rf ./test/output-qemu*
rm -rf ./test/output-unicorn*
rm ./test/out-* rm ./test/out-*
''' '''

View File

@ -53,7 +53,8 @@ use crate::{
}, },
scheduler::SupportedSchedulers, scheduler::SupportedSchedulers,
stages::{mutational_stage::SupportedMutationalStages, time_tracker::TimeTrackingStageWrapper}, 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 = pub type LibaflFuzzState =
@ -234,7 +235,7 @@ where
} }
// Create the base Executor // 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. // Set a custom exit code to be interpreted as a Crash if configured.
if let Some(crash_exitcode) = opt.crash_exitcode { if let Some(crash_exitcode) = opt.crash_exitcode {
executor = executor.crash_exitcode(crash_exitcode); executor = executor.crash_exitcode(crash_exitcode);
@ -245,24 +246,6 @@ where
executor = executor.autotokens(&mut tokens); 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 // Finalize and build our Executor
let mut executor = executor let mut executor = executor
.build(tuple_list!(time_observer, edges_observer)) .build(tuple_list!(time_observer, edges_observer))
@ -339,7 +322,7 @@ where
// Create the CmpLog executor. // Create the CmpLog executor.
// Cmplog has 25% execution overhead so we give it double the timeout // 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)) .timeout(Duration::from_millis(opt.hang_timeout * 2))
.program(cmplog_executable_path) .program(cmplog_executable_path)
.build(tuple_list!(cmplog_observer)) .build(tuple_list!(cmplog_observer))
@ -410,7 +393,8 @@ where
fn base_executor<'a>( fn base_executor<'a>(
opt: &'a Opt, opt: &'a Opt,
shmem_provider: &'a mut StdShMemProvider, shmem_provider: &'a mut StdShMemProvider,
) -> ForkserverExecutorBuilder<'a, StdShMemProvider> { fuzzer_dir: &PathBuf,
) -> Result<ForkserverExecutorBuilder<'a, StdShMemProvider>, Error> {
let mut executor = ForkserverExecutor::builder() let mut executor = ForkserverExecutor::builder()
.program(opt.executable.clone()) .program(opt.executable.clone())
.coverage_map_size(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE)) .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 { if opt.is_persistent || opt.qemu_mode {
executor = executor.shmem_provider(shmem_provider); executor = executor.shmem_provider(shmem_provider);
} }
if let Some(harness_input_type) = &opt.harness_input_type { // Set arguments for the target if necessary
executor = executor.parse_afl_cmdline([harness_input_type]); 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 { if opt.qemu_mode {
let exec = opt.executable.display().to_string();
executor = executor.program( executor = executor.program(
find_afl_binary("afl-qemu-trace", Some(opt.executable.clone())) find_afl_binary("afl-qemu-trace", Some(opt.executable.clone()))
.expect("to find afl-qemu-trace"), .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> { pub fn fuzzer_target_mode(opt: &Opt) -> Cow<'static, str> {

View File

@ -112,9 +112,6 @@ fn main() {
struct Opt { struct Opt {
executable: PathBuf, executable: PathBuf,
#[arg(value_parser = validate_harness_input_stdin)]
harness_input_type: Option<&'static str>,
// NOTE: afl-fuzz does not accept multiple input directories // NOTE: afl-fuzz does not accept multiple input directories
#[arg(short = 'i')] #[arg(short = 'i')]
input_dir: PathBuf, input_dir: PathBuf,
@ -258,13 +255,6 @@ struct Opt {
non_instrumented_mode: bool, 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)] #[allow(dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CmplogOpts { pub struct CmplogOpts {