diff --git a/Cargo.toml b/Cargo.toml index 77ae433219..98347cc3da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ #example fuzzers "fuzzers/libfuzzer_libpng", + "fuzzers/frida_libpng", "fuzzers/libfuzzer_libmozjpeg", "fuzzers/libfuzzer_libpng_cmpalloc", ] diff --git a/fuzzers/frida_libpng/.gitignore b/fuzzers/frida_libpng/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/frida_libpng/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml new file mode 100644 index 0000000000..347724ca56 --- /dev/null +++ b/fuzzers/frida_libpng/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "frida_libpng" +version = "0.1.0" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" +build = "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["std", "frida"] +std = [] +frida = ["frida-gum", "frida-gum-sys"] + +#[profile.release] +#lto = true +#codegen-units = 1 +#opt-level = 3 +#debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +num_cpus = "1.0" + +[dependencies] +libafl = { path = "../../libafl/" } +#frida-gum = { path = "../../../frida-rust/frida-gum", version = "0.2.3", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] } +#frida-gum-sys = { path = "../../../frida-rust/frida-gum-sys", version = "0.2.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] } +frida-gum = { version = "0.3.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] } +frida-gum-sys = { version = "0.2.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] } +lazy_static = "1.4.0" +libc = "0.2" +libloading = "0.7.0" + +[[example]] +name = "frida_libpng" +path = "./src/fuzzer.rs" +test = false +bench = false diff --git a/fuzzers/frida_libpng/README.md b/fuzzers/frida_libpng/README.md new file mode 100644 index 0000000000..f56138c2b5 --- /dev/null +++ b/fuzzers/frida_libpng/README.md @@ -0,0 +1,25 @@ +# Libfuzzer for libpng + +This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. +To show off crash detection, we added a ud2 instruction to the harness, edit harness.cc if you want a non-crashing example. +It has been tested on Linux. + +## Build + +To build this example, run `cargo build --example libfuzzer_libpng --release`. +This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web. +Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`. +Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`. + +## Run + +The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. + +Each following execution will run a fuzzer client. +As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`). +This means each client will start itself again to listen for crashes and timeouts. +By restarting the actual fuzzer, it can recover from these exit conditions. + +In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). + +For convenience, you may just run `./test.sh` in this folder to test it. diff --git a/fuzzers/frida_libpng/build.rs b/fuzzers/frida_libpng/build.rs new file mode 100644 index 0000000000..94e44cd8f4 --- /dev/null +++ b/fuzzers/frida_libpng/build.rs @@ -0,0 +1,114 @@ +// build.rs + +use std::{ + env, + path::Path, + process::{exit, Command}, +}; + +const LIBPNG_URL: &str = + "https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; + +fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libpng example on Windows"); + exit(0); + } + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); + let out_dir = out_dir.to_string_lossy().to_string(); + let out_dir_path = Path::new(&out_dir); + std::fs::create_dir_all(&out_dir).expect(&format!("Failed to create {}", &out_dir)); + + println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",); + println!("cargo:rerun-if-changed=harness.cc"); + + let libpng = format!("{}/libpng-1.6.37", &out_dir); + let libpng_path = Path::new(&libpng); + let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd); + + // Enforce clang for its -fsanitize-coverage support. + std::env::set_var("CC", "clang"); + std::env::set_var("CXX", "clang++"); + let ldflags = match env::var("LDFLAGS") { + Ok(val) => val, + Err(_) => "".to_string(), + }; + + // println!("cargo:warning=output path is {}", libpng); + if !libpng_path.is_dir() { + if !Path::new(&libpng_tar).is_file() { + println!("cargo:warning=Libpng not found, downloading..."); + // Download libpng + Command::new("wget") + .arg("-c") + .arg(LIBPNG_URL) + .arg("-O") + .arg(&libpng_tar) + .status() + .unwrap(); + } + Command::new("tar") + .current_dir(&out_dir_path) + .arg("xvf") + .arg(&libpng_tar) + .status() + .unwrap(); + Command::new(format!("{}/configure", &libpng)) + .current_dir(&libpng_path) + .args(&[ + "--disable-shared", + &format!("--host={}", env::var("TARGET").unwrap())[..], + ]) + .env("CC", "clang") + .env("CXX", "clang++") + .env( + "CFLAGS", + "-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer", + ) + .env( + "CXXFLAGS", + "-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer", + ) + .env( + "LDFLAGS", + //format!("-g -fPIE -fsanitize=address {}", ldflags), + format!("-g -fPIE {}", ldflags), + ) + .status() + .unwrap(); + Command::new("make") + .current_dir(&libpng_path) + .status() + .unwrap(); + } + + let status = cc::Build::new() + .cpp(true) + .get_compiler() + .to_command() + .current_dir(&cwd) + .arg("-I") + .arg(format!("{}", &libpng)) + //.arg("-D") + //.arg("HAS_DUMMY_CRASH=1") + .arg("-fPIC") + .arg("-shared") + .arg(if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" { + "-static-libstdc++" + } else { + "" + }) + .arg("-o") + .arg(format!("{}/libpng-harness.so", &out_dir)) + .arg("./harness.cc") + .arg(format!("{}/.libs/libpng16.a", &libpng)) + .arg("-l") + .arg("z") + .status() + .unwrap(); + assert!(status.success()); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/fuzzers/frida_libpng/harness.cc b/fuzzers/frida_libpng/harness.cc new file mode 100644 index 0000000000..eaf9aa27ba --- /dev/null +++ b/fuzzers/frida_libpng/harness.cc @@ -0,0 +1,201 @@ +// libpng_read_fuzzer.cc +// Copyright 2017-2018 Glenn Randers-Pehrson +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that may +// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE + +// Last changed in libpng 1.6.35 [July 15, 2018] + +// The modifications in 2017 by Glenn Randers-Pehrson include +// 1. addition of a PNG_CLEANUP macro, +// 2. setting the option to ignore ADLER32 checksums, +// 3. adding "#include " which is needed on some platforms +// to provide memcpy(). +// 4. adding read_end_info() and creating an end_info structure. +// 5. adding calls to png_set_*() transforms commonly used by browsers. + +#include +#include +#include + +#include + +#define PNG_INTERNAL +#include "png.h" + +#define PNG_CLEANUP \ + if(png_handler.png_ptr) \ + { \ + if (png_handler.row_ptr) \ + png_free(png_handler.png_ptr, png_handler.row_ptr); \ + if (png_handler.end_info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + &png_handler.end_info_ptr); \ + else if (png_handler.info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + nullptr); \ + else \ + png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \ + png_handler.png_ptr = nullptr; \ + png_handler.row_ptr = nullptr; \ + png_handler.info_ptr = nullptr; \ + png_handler.end_info_ptr = nullptr; \ + } + +struct BufState { + const uint8_t* data; + size_t bytes_left; +}; + +struct PngObjectHandler { + png_infop info_ptr = nullptr; + png_structp png_ptr = nullptr; + png_infop end_info_ptr = nullptr; + png_voidp row_ptr = nullptr; + BufState* buf_state = nullptr; + + ~PngObjectHandler() { + if (row_ptr) + png_free(png_ptr, row_ptr); + if (end_info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr); + else if (info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + else + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + delete buf_state; + } +}; + +void user_read_data(png_structp png_ptr, png_bytep data, size_t length) { + BufState* buf_state = static_cast(png_get_io_ptr(png_ptr)); + if (length > buf_state->bytes_left) { + png_error(png_ptr, "read error"); + } + memcpy(data, buf_state->data, length); + buf_state->bytes_left -= length; + buf_state->data += length; +} + +static const int kPngHeaderSize = 8; + +extern "C" int afl_libfuzzer_init() { + return 0; +} + +// Entry point for LibFuzzer. +// Roughly follows the libpng book example: +// http://www.libpng.org/pub/png/book/chapter13.html +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < kPngHeaderSize) { + return 0; + } + + std::vector v(data, data + size); + if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { + // not a PNG. + return 0; + } + + PngObjectHandler png_handler; + png_handler.png_ptr = nullptr; + png_handler.row_ptr = nullptr; + png_handler.info_ptr = nullptr; + png_handler.end_info_ptr = nullptr; + + png_handler.png_ptr = png_create_read_struct + (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_handler.png_ptr) { + return 0; + } + + png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.info_ptr) { + PNG_CLEANUP + return 0; + } + + png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.end_info_ptr) { + PNG_CLEANUP + return 0; + } + + png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); +#ifdef PNG_IGNORE_ADLER32 + png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); +#endif + + // Setting up reading from buffer. + png_handler.buf_state = new BufState(); + png_handler.buf_state->data = data + kPngHeaderSize; + png_handler.buf_state->bytes_left = size - kPngHeaderSize; + png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); + png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); + + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + // Reading. + png_read_info(png_handler.png_ptr, png_handler.info_ptr); + + // reset error handler to put png_deleter into scope. + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type; + int filter_type; + + if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, + &height, &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_type)) { + PNG_CLEANUP + return 0; + } + + // This is going to be too slow. + if (width && height > 100000000 / width) { + PNG_CLEANUP +#ifdef HAS_DUMMY_CRASH + #ifdef __aarch64__ + asm volatile (".word 0xf7f0a000\n"); + #else + asm("ud2"); + #endif +#endif + return 0; + } + + // Set several transforms that browsers typically use: + png_set_gray_to_rgb(png_handler.png_ptr); + png_set_expand(png_handler.png_ptr); + png_set_packing(png_handler.png_ptr); + png_set_scale_16(png_handler.png_ptr); + png_set_tRNS_to_alpha(png_handler.png_ptr); + + int passes = png_set_interlace_handling(png_handler.png_ptr); + + png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); + + png_handler.row_ptr = png_malloc( + png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, + png_handler.info_ptr)); + + for (int pass = 0; pass < passes; ++pass) { + for (png_uint_32 y = 0; y < height; ++y) { + png_read_row(png_handler.png_ptr, + static_cast(png_handler.row_ptr), nullptr); + } + } + + png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); + + PNG_CLEANUP + return 0; +} + diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs new file mode 100644 index 0000000000..dac2564c4e --- /dev/null +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -0,0 +1,524 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. + +use libafl::{ + bolts::{ + shmem::UnixShMem, + tuples::{tuple_list, Named}, + }, + corpus::{ + Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, + QueueCorpusScheduler, + }, + events::{setup_restarting_mgr, EventManager}, + executors::{inprocess::InProcessExecutor, Executor, ExitKind, HasObservers}, + feedbacks::{CrashFeedback, MaxMapFeedback}, + fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, + inputs::{HasTargetBytes, Input}, + mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, + observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver}, + stages::mutational::StdMutationalStage, + state::{HasCorpus, HasMetadata, State}, + stats::SimpleStats, + utils::{current_nanos, StdRand}, + Error, +}; + +#[cfg(target_arch = "x86_64")] +use frida_gum::instruction_writer::X86Register; +#[cfg(target_arch = "aarch64")] +use frida_gum::instruction_writer::{Aarch64Register, IndexMode}; +use frida_gum::{ + instruction_writer::InstructionWriter, + stalker::{NoneEventSink, Stalker, Transformer}, +}; +use frida_gum::{Gum, MemoryRange, Module, NativePointer, PageProtection}; + +use libloading; + +use std::{cell::RefCell, env, ffi::c_void, path::PathBuf}; + +/// An helper that feeds FridaInProcessExecutor with user-supplied instrumentation +pub trait FridaHelper<'a> { + fn transformer(&self) -> &Transformer<'a>; +} + +const MAP_SIZE: usize = 64 * 1024; + +/// An helper that feeds FridaInProcessExecutor with edge-coverage instrumentation +struct FridaEdgeCoverageHelper<'a> { + map: [u8; MAP_SIZE], + previous_pc: RefCell, + base_address: u64, + size: usize, + current_log_impl: u64, + /// Transformer that has to be passed to FridaInProcessExecutor + transformer: Option>, +} + +impl<'a> FridaHelper<'a> for FridaEdgeCoverageHelper<'a> { + fn transformer(&self) -> &Transformer<'a> { + self.transformer.as_ref().unwrap() + } +} + +/// Helper function to get the size of a module's CODE section from frida +pub fn get_module_size(module_name: &str) -> usize { + let mut code_size = 0; + let code_size_ref = &mut code_size; + Module::enumerate_ranges(module_name, PageProtection::ReadExecute, move |details| { + *code_size_ref = details.memory_range().size() as usize; + true + }); + + code_size +} + +/// A minimal maybe_log implementation. We insert this into the transformed instruction stream +/// every time we need a copy that is within a direct branch of the start of the transformed basic +/// block. +#[cfg(target_arch = "x86_64")] +const MAYBE_LOG_CODE: [u8; 69] = [ + 0x9c, // pushfq + 0x50, // push rax + 0x51, // push rcx + 0x52, // push rdx + 0x56, // push rsi + 0x89, 0xf8, // mov eax, edi + 0xc1, 0xe0, 0x08, // shl eax, 8 + 0xc1, 0xef, 0x04, // shr edi, 4 + 0x31, 0xc7, // xor edi, eax + 0x0f, 0xb7, 0xc7, // movzx eax, di + 0x48, 0x8d, 0x0d, 0x34, 0x00, 0x00, 0x00, // lea rcx, sym._afl_area_ptr_ptr + 0x48, 0x8b, 0x09, // mov rcx, qword [rcx] + 0x48, 0x8d, 0x15, 0x22, 0x00, 0x00, 0x00, // lea rdx, sym._afl_prev_loc_ptr + 0x48, 0x8b, 0x32, // mov rsi, qword [rdx] + 0x48, 0x8b, 0x36, // mov rsi, qword [rsi] + 0x48, 0x31, 0xc6, // xor rsi, rax + 0x48, 0x81, 0xe6, 0xff, 0x1f, 0x00, + 0x00, // and rsi, 0x1fff (8 * 1024 - 1) TODO: make this variable + 0xfe, 0x04, 0x31, // inc byte [rcx + rsi] + 0x48, 0xd1, 0xe8, // shr rax, 1 + 0x48, 0x8b, 0x0a, // mov rcx, qword [rdx] + 0x48, 0x89, 0x01, // mov qword [rcx], rax + 0x5e, // pop rsi + 0x5a, // pop rdx + 0x59, // pop rcx + 0x58, // pop rax + 0x9d, // popfq + 0xc3, // ret + // Read-only data goes here: + // uint64_t* afl_prev_loc_ptr + // uint8_t** afl_area_ptr_ptr + // unsigned int afl_instr_rms +]; + +#[cfg(target_arch = "aarch64")] +const MAYBE_LOG_CODE: [u8; 104] = [ + 0xE1, 0x0B, 0xBF, 0xA9, // stp x1, x2, [sp, -0x10]! + 0xE3, 0x13, 0xBF, 0xA9, // stp x3, x4, [sp, -0x10]! + 0xE1, 0x03, 0x00, 0xAA, // mov x1, x0 + 0x00, 0xDC, 0x78, 0xD3, // lsl x0, x0, #8 + 0x21, 0xFC, 0x44, 0xD3, // lsr x1, x1, #4 + 0x00, 0x00, 0x01, 0xCA, // eor x0, x0, x1 + 0x00, 0x3C, 0x00, 0x53, // uxth w0, w0 + 0xa1, 0x02, 0x00, 0x58, // ldr x1, =area_ptr + 0x42, 0x02, 0x00, 0x58, // ldr x2, =pc_ptr + 0x43, 0x00, 0x40, 0xF9, // ldr x3, [x2] + 0x63, 0x00, 0x00, 0xCA, // eor x3, x3, x0 + 0x63, 0x40, 0x40, 0x92, // and x3, x3, #0x1ffff + 0x21, 0x00, 0x03, 0x8B, // add x1, x1, x3 + 0x24, 0x00, 0x40, 0x39, // ldrb w4, [x1, #0 + 0x84, 0x04, 0x00, 0x91, // add x4, x4, #1 + 0x24, 0x00, 0x00, 0x39, // strb w4, [x1, #0] + 0x00, 0xFC, 0x41, 0xD3, // lsr x0, x0, #1 + 0x40, 0x00, 0x00, 0xF9, // str x0, [x2] + 0xE3, 0x13, 0xc1, 0xA8, // ldp x3, x4, [sp], #0x10 + 0xE1, 0x0B, 0xc1, 0xA8, // ldp x1, x2, [sp], #0x10 + 0xC0, 0x03, 0x5F, 0xD6, // ret + 0x1f, 0x20, 0x03, 0xD5, // nop + 0x1f, 0x20, 0x03, 0xD5, // nop + 0x1f, 0x20, 0x03, 0xD5, // nop + 0x1f, 0x20, 0x03, 0xD5, // nop + 0x1f, 0x20, 0x03, 0xD5, // nop +]; + +/// The implementation of the FridaEdgeCoverageHelper +impl<'a> FridaEdgeCoverageHelper<'a> { + /// Constructor function to create a new FridaEdgeCoverageHelper, given a module_name. + pub fn new(gum: &'a Gum, module_name: &str) -> Self { + let mut helper = Self { + map: [0u8; MAP_SIZE], + previous_pc: RefCell::new(0x0), + base_address: Module::find_base_address(module_name).0 as u64, + size: get_module_size(module_name), + current_log_impl: 0, + transformer: None, + }; + + let transformer = Transformer::from_callback(gum, |basic_block, _output| { + let mut first = true; + for instruction in basic_block { + if first { + first = false; + let address = unsafe { (*instruction.instr()).address }; + if address >= helper.base_address + && address <= helper.base_address + helper.size as u64 + { + let writer = _output.writer(); + if helper.current_log_impl == 0 + || !writer.can_branch_directly_to(helper.current_log_impl) + || !writer.can_branch_directly_between( + writer.pc() + 128, + helper.current_log_impl, + ) + { + let after_log_impl = writer.code_offset() + 1; + + #[cfg(target_arch = "x86_64")] + writer.put_jmp_near_label(after_log_impl); + #[cfg(target_arch = "aarch64")] + writer.put_b_label(after_log_impl); + + helper.current_log_impl = writer.pc(); + writer.put_bytes(&MAYBE_LOG_CODE); + let prev_loc_pointer = helper.previous_pc.as_ptr() as *mut _ as usize; + let map_pointer = helper.map.as_ptr() as usize; + + writer.put_bytes(&prev_loc_pointer.to_ne_bytes()); + writer.put_bytes(&map_pointer.to_ne_bytes()); + + writer.put_label(after_log_impl); + } + #[cfg(target_arch = "x86_64")] + { + println!("here"); + writer.put_lea_reg_reg_offset( + X86Register::Rsp, + X86Register::Rsp, + -(frida_gum_sys::GUM_RED_ZONE_SIZE as i32), + ); + writer.put_push_reg(X86Register::Rdi); + writer.put_mov_reg_address(X86Register::Rdi, address); + writer.put_call_address(helper.current_log_impl); + writer.put_pop_reg(X86Register::Rdi); + writer.put_lea_reg_reg_offset( + X86Register::Rsp, + X86Register::Rsp, + frida_gum_sys::GUM_RED_ZONE_SIZE as i32, + ); + } + #[cfg(target_arch = "aarch64")] + { + writer.put_stp_reg_reg_reg_offset( + Aarch64Register::Lr, + Aarch64Register::X0, + Aarch64Register::Sp, + -(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64, + IndexMode::PreAdjust, + ); + writer.put_ldr_reg_u64(Aarch64Register::X0, address); + writer.put_bl_imm(helper.current_log_impl); + writer.put_ldp_reg_reg_reg_offset( + Aarch64Register::Lr, + Aarch64Register::X0, + Aarch64Register::Sp, + 16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64, + IndexMode::PostAdjust, + ); + } + } + } + instruction.keep() + } + }); + + helper.transformer = Some(transformer); + helper + } +} + +struct FridaInProcessExecutor<'a, FH, H, I, OT> +where + FH: FridaHelper<'a>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + base: InProcessExecutor<'a, H, I, OT>, + /// Frida's dynamic rewriting engine + stalker: Stalker<'a>, + /// User provided callback for instrumentation + helper: &'a FH, + followed: bool, +} + +impl<'a, FH, H, I, OT> Executor for FridaInProcessExecutor<'a, FH, H, I, OT> +where + FH: FridaHelper<'a>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + /// Called right before exexution starts + #[inline] + fn pre_exec(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error> + where + EM: EventManager, + { + if !self.followed { + self.followed = true; + self.stalker + .follow_me::(self.helper.transformer(), None); + } else { + self.stalker.activate(NativePointer( + self.base.harness_mut() as *mut _ as *mut c_void + )) + } + self.base.pre_exec(state, event_mgr, input) + } + + /// Instruct the target about the input and run + #[inline] + fn run_target(&mut self, input: &I) -> Result { + self.base.run_target(input) + } + + /// Called right after execution finished. + #[inline] + fn post_exec( + &mut self, + state: &mut S, + event_mgr: &mut EM, + input: &I, + ) -> Result<(), Error> + where + EM: EventManager, + { + self.stalker.deactivate(); + self.base.post_exec(state, event_mgr, input) + } +} + +impl<'a, FH, H, I, OT> HasObservers for FridaInProcessExecutor<'a, FH, H, I, OT> +where + FH: FridaHelper<'a>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + #[inline] + fn observers(&self) -> &OT { + self.base.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.base.observers_mut() + } +} + +impl<'a, FH, H, I, OT> Named for FridaInProcessExecutor<'a, FH, H, I, OT> +where + FH: FridaHelper<'a>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + fn name(&self) -> &str { + self.base.name() + } +} + +impl<'a, FH, H, I, OT> FridaInProcessExecutor<'a, FH, H, I, OT> +where + FH: FridaHelper<'a>, + H: FnMut(&[u8]) -> ExitKind, + I: Input + HasTargetBytes, + OT: ObserversTuple, +{ + pub fn new(gum: &'a Gum, base: InProcessExecutor<'a, H, I, OT>, helper: &'a FH) -> Self { + let mut stalker = Stalker::new(gum); + + // Let's exclude the main module and libc.so at least: + stalker.exclude(&MemoryRange::new( + Module::find_base_address(&env::args().next().unwrap()), + get_module_size(&env::args().next().unwrap()), + )); + stalker.exclude(&MemoryRange::new( + Module::find_base_address("libc.so"), + get_module_size("libc.so"), + )); + + Self { + base: base, + stalker: stalker, + helper: helper, + followed: false, + } + } +} + +/// The main fn, usually parsing parameters, and starting the fuzzer +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + unsafe { + fuzz( + &env::args().nth(1).expect("no module specified"), + &env::args().nth(2).expect("no symbol specified"), + vec![PathBuf::from("./corpus")], + PathBuf::from("./crashes"), + 1337, + ) + .expect("An error occurred while fuzzing"); + } +} + +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz( + _module_name: &str, + _symbol_name: &str, + _corpus_dirs: Vec, + _objective_dir: PathBuf, + _broker_port: u16, +) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + +/// The actual fuzzer +#[cfg(unix)] +unsafe fn fuzz( + module_name: &str, + symbol_name: &str, + corpus_dirs: Vec, + objective_dir: PathBuf, + broker_port: u16, +) -> Result<(), Error> { + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let stats = SimpleStats::new(|s| println!("{}", s)); + + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + let (state, mut restarting_mgr) = + match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + let gum = Gum::obtain(); + let lib = libloading::Library::new(module_name).unwrap(); + let target_func: libloading::Symbol i32> = + lib.get(symbol_name.as_bytes()).unwrap(); + let mut frida_helper = FridaEdgeCoverageHelper::new(&gum, module_name); + + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( + "edges", + frida_helper.map.as_mut_ptr(), + MAP_SIZE, + )); + + let mut frida_harness = move |buf: &[u8]| { + (target_func)(buf.as_ptr(), buf.len()); + ExitKind::Ok + }; + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + State::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Feedbacks to rate the interestingness of an input + tuple_list!(MaxMapFeedback::new_with_observer_track( + &edges_observer, + true, + false + )), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // Feedbacks to recognize an input as solution + tuple_list!(CrashFeedback::new()), + ) + }); + + println!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::new(vec![ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + "IHDR".as_bytes().to_vec(), + "IDAT".as_bytes().to_vec(), + "PLTE".as_bytes().to_vec(), + "IEND".as_bytes().to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = HavocBytesMutator::default(); + let stage = StdMutationalStage::new(mutator); + + // A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); + let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); + + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + "in-process(edges)", + &mut frida_harness, + tuple_list!(edges_observer), + &mut state, + &mut restarting_mgr, + )?, + &frida_helper, + ); + // Let's exclude the main module and libc.so at least: + executor.stalker.exclude(&MemoryRange::new( + Module::find_base_address(&env::args().next().unwrap()), + get_module_size(&env::args().next().unwrap()), + )); + executor.stalker.exclude(&MemoryRange::new( + Module::find_base_address("libc.so"), + get_module_size("libc.so"), + )); + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs( + &mut executor, + &mut restarting_mgr, + fuzzer.scheduler(), + &corpus_dirs, + ) + .expect(&format!( + "Failed to load initial corpus at {:?}", + &corpus_dirs + )); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; + + // Never reached + Ok(()) +} diff --git a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs index ffbbcdfa78..cafe05bc1b 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs @@ -36,20 +36,6 @@ extern "C" { static __lafl_max_edges_size: u32; } -/// The wrapped harness function, calling out to the LLVM-style harness -#[cfg(unix)] -fn harness(_executor: &E, buf: &[u8]) -> ExitKind -where - E: Executor, - I: Input, -{ - // println!("{:?}", buf); - unsafe { - LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()); - } - ExitKind::Ok -} - /// The main fn, usually parsing parameters, and starting the fuzzer pub fn main() { // Registry the metadata types used in this fuzzer @@ -121,10 +107,16 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // A fuzzer with just one stage and a random policy to get testcasess from the corpus let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage)); + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |buf: &[u8]| { + unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) }; + ExitKind::Ok + }; + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = InProcessExecutor::new( "in-process(edges)", - harness, + &mut harness, tuple_list!(edges_observer), &mut state, &mut restarting_mgr, diff --git a/fuzzers/libfuzzer_libpng/src/fuzzer.rs b/fuzzers/libfuzzer_libpng/src/fuzzer.rs index 9bd2febdc3..32531944b5 100644 --- a/fuzzers/libfuzzer_libpng/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng/src/fuzzer.rs @@ -12,10 +12,9 @@ use libafl::{ QueueCorpusScheduler, }, events::setup_restarting_mgr, - executors::{inprocess::InProcessExecutor, Executor, ExitKind, TimeoutExecutor}, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, - inputs::Input, mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, @@ -39,20 +38,6 @@ extern "C" { static __lafl_max_edges_size: u32; } -/// The wrapped harness function, calling out to the LLVM-style harness -#[cfg(unix)] -fn harness(_executor: &E, buf: &[u8]) -> ExitKind -where - E: Executor, - I: Input, -{ - // println!("{:?}", buf); - unsafe { - LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()); - } - ExitKind::Ok -} - /// The main fn, usually parsing parameters, and starting the fuzzer pub fn main() { // Registry the metadata types used in this fuzzer @@ -143,11 +128,17 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |buf: &[u8]| { + unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) }; + ExitKind::Ok + }; + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = TimeoutExecutor::new( InProcessExecutor::new( "in-process(edges)", - harness, + &mut harness, tuple_list!(edges_observer, TimeObserver::new("time")), &mut state, &mut restarting_mgr, diff --git a/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs b/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs index 58215afc63..c4f26cabb4 100644 --- a/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng_cmpalloc/src/fuzzer.rs @@ -11,10 +11,9 @@ use libafl::{ QueueCorpusScheduler, }, events::setup_restarting_mgr, - executors::{inprocess::InProcessExecutor, Executor, ExitKind}, + executors::{inprocess::InProcessExecutor, ExitKind}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, - inputs::Input, mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, @@ -41,20 +40,6 @@ extern "C" { static __lafl_max_edges_size: u32; } -/// The wrapped harness function, calling out to the LLVM-style harness -#[cfg(unix)] -fn harness(_executor: &E, buf: &[u8]) -> ExitKind -where - E: Executor, - I: Input, -{ - // println!("{:?}", buf); - unsafe { - LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()); - } - ExitKind::Ok -} - /// The main fn, usually parsing parameters, and starting the fuzzer pub fn main() { // Registry the metadata types used in this fuzzer @@ -154,10 +139,16 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |buf: &[u8]| { + unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) }; + ExitKind::Ok + }; + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = InProcessExecutor::new( "in-process(edges,cmps,allocs)", - harness, + &mut harness, tuple_list!( edges_observer, cmps_observer, diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index fa42f0da9a..0071f194a0 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -25,26 +25,25 @@ use crate::{ Error, }; -/// The inmem executor harness -type HarnessFunction = fn(&E, &[u8]) -> ExitKind; - /// The inmem executor simply calls a target function, then returns afterwards. -pub struct InProcessExecutor +pub struct InProcessExecutor<'a, H, I, OT> where + H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { /// The name of this executor instance, to address it from other components name: &'static str, /// The harness function, being executed for each fuzzing loop execution - harness_fn: HarnessFunction, + harness_fn: &'a mut H, /// The observers, observing each run observers: OT, phantom: PhantomData, } -impl Executor for InProcessExecutor +impl<'a, H, I, OT> Executor for InProcessExecutor<'a, H, I, OT> where + H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { @@ -95,7 +94,7 @@ where #[inline] fn run_target(&mut self, input: &I) -> Result { let bytes = input.target_bytes(); - let ret = (self.harness_fn)(self, bytes.as_slice()); + let ret = (self.harness_fn)(bytes.as_slice()); Ok(ret) } @@ -126,8 +125,9 @@ where } } -impl Named for InProcessExecutor +impl<'a, H, I, OT> Named for InProcessExecutor<'a, H, I, OT> where + H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { @@ -136,8 +136,9 @@ where } } -impl HasObservers for InProcessExecutor +impl<'a, H, I, OT> HasObservers for InProcessExecutor<'a, H, I, OT> where + H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { @@ -152,8 +153,9 @@ where } } -impl InProcessExecutor +impl<'a, H, I, OT> InProcessExecutor<'a, H, I, OT> where + H: FnMut(&[u8]) -> ExitKind, I: Input + HasTargetBytes, OT: ObserversTuple, { @@ -166,7 +168,7 @@ where /// This may return an error on unix, if signal handler setup fails pub fn new( name: &'static str, - harness_fn: HarnessFunction, + harness_fn: &'a mut H, observers: OT, _state: &mut S, _event_mgr: &mut EM, @@ -215,6 +217,18 @@ where phantom: PhantomData, }) } + + /// Retrieve the harness function. + #[inline] + pub fn harness(&self) -> &H { + self.harness_fn + } + + /// Retrieve the harness function for a mutable reference. + #[inline] + pub fn harness_mut(&mut self) -> &mut H { + self.harness_fn + } } #[cfg(unix)] @@ -627,19 +641,15 @@ mod tests { use crate::{ bolts::tuples::tuple_list, executors::{Executor, ExitKind, InProcessExecutor}, - inputs::Input, + inputs::NopInput, }; - fn test_harness_fn_nop, I: Input>(_executor: &E, _buf: &[u8]) -> ExitKind { - ExitKind::Ok - } - #[test] fn test_inmem_exec() { - use crate::inputs::NopInput; + let mut harness = |_buf: &[u8]| ExitKind::Ok; - let mut in_process_executor = InProcessExecutor:: { - harness_fn: test_harness_fn_nop, + let mut in_process_executor = InProcessExecutor::<_, NopInput, ()> { + harness_fn: &mut harness, observers: tuple_list!(), name: "main", phantom: PhantomData, diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 55170850ad..c58307a045 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -137,8 +137,8 @@ mod tests { use crate::{ bolts::tuples::tuple_list, corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase}, - executors::{Executor, ExitKind, InProcessExecutor}, - inputs::{BytesInput, Input}, + executors::{ExitKind, InProcessExecutor}, + inputs::BytesInput, mutators::{mutation_bitflip, ComposedByMutations, StdScheduledMutator}, stages::StdMutationalStage, state::{HasCorpus, State}, @@ -150,10 +150,6 @@ mod tests { #[cfg(feature = "std")] use crate::events::SimpleEventManager; - fn harness, I: Input>(_executor: &E, _buf: &[u8]) -> ExitKind { - ExitKind::Ok - } - #[test] fn test_fuzzer() { let rand = StdRand::with_seed(0); @@ -175,9 +171,10 @@ mod tests { }); let mut event_manager = SimpleEventManager::new(stats); + let mut harness = |_buf: &[u8]| ExitKind::Ok; let mut executor = InProcessExecutor::new( "main", - harness, + &mut harness, tuple_list!(), //Box::new(|_, _, _, _, _| ()), &mut state, diff --git a/libafl/src/observers/map.rs b/libafl/src/observers/map.rs index 141f75f02d..825a62b8b9 100644 --- a/libafl/src/observers/map.rs +++ b/libafl/src/observers/map.rs @@ -126,6 +126,16 @@ where } } + /// Creates a new MapObserver with an owned map + pub fn new_owned(name: &'static str, map: Vec) -> Self { + let initial = if map.is_empty() { T::default() } else { map[0] }; + Self { + map: ArrayMut::Owned(map), + name: name.to_string(), + initial, + } + } + /// Creates a new MapObserver from a raw pointer /// # Safety /// Will dereference the map_ptr with up to len elements.