Libafl-fuzz: introduce unicorn mode (#2499)
* libafl-fuzz: introduce unicorn mode * taplo format * libafl-fuzz: fix qemumode * taplo format
This commit is contained in:
parent
a388012429
commit
07db74b416
@ -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-*
|
||||
'''
|
||||
|
@ -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<ForkserverExecutorBuilder<'a, StdShMemProvider>, 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> {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user