Skip to content

Commit

Permalink
It's frida time for libafl-fuzz (AFLplusplus#2469)
Browse files Browse the repository at this point in the history
* libafl-fuzz: misc nit in check_autoresume

* libafl-fuzz: add FRIDA mode

* libafl-fuzz: improve Makefile

* libafl-fuzz: fix Ci

* libafl-fuzz: clang-format test-cmpcov.c

* libafl-fuzz: no cmplog for persistent frida

* libafl-fuzz: minor CI fix

* libafl-fuzz: fix frida persistent mode

* libafl-fuzz: add frida seeds

* misc: typo
  • Loading branch information
R9295 authored Aug 5, 2024
1 parent 291fdeb commit 949a25a
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 17 deletions.
68 changes: 64 additions & 4 deletions fuzzers/others/libafl-fuzz/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
'''
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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-*
'''
8 changes: 4 additions & 4 deletions fuzzers/others/libafl-fuzz/src/corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_) => {}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions fuzzers/others/libafl-fuzz/src/env_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
66 changes: 64 additions & 2 deletions fuzzers/others/libafl-fuzz/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand Down Expand Up @@ -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")
Expand All @@ -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");
}

Expand Down Expand Up @@ -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<PathBuf> {
fn find_executable_in_path<P: AsRef<Path>>(executable: &P) -> Option<PathBuf> {
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths).find_map(|dir| {
let full_path = dir.join(executable);
Expand All @@ -174,3 +182,57 @@ fn find_executable_in_path(executable: &Path) -> Option<PathBuf> {
})
})
}

pub fn find_afl_binary(filename: &str, same_dir_as: Option<PathBuf>) -> Result<PathBuf, Error> {
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
}
34 changes: 27 additions & 7 deletions fuzzers/others/libafl-fuzz/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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)
Expand All @@ -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]);
}
Expand Down
3 changes: 3 additions & 0 deletions fuzzers/others/libafl-fuzz/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions fuzzers/others/libafl-fuzz/test/seeds-frida/init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000
56 changes: 56 additions & 0 deletions fuzzers/others/libafl-fuzz/test/test-cmpcov.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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;
}

0 comments on commit 949a25a

Please sign in to comment.