From 38ea17b426c0977287b77ac0f30e0cf7e4f6f975 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 22 Mar 2023 23:18:21 +0900 Subject: [PATCH] libafl_frida for Linux executables (#1117) * add frida_executable_libpng * fix makefile * fix README.md * remove author from Cargo.toml * fix fuzzer * fix fuzzer * fix Makefile * fix linter * fix clang-format-13 * unsupport mac os * fix build_and_test_fuzzers * fix cargo fmt * cargo fmt * add safer libc_start_main * fix call rax addr * fix frida * fix cargo fmt * fix metadata() to metadata_map() * fix toml * fix maxmapfeedback --- fuzzers/frida_executable_libpng/.gitignore | 5 + fuzzers/frida_executable_libpng/Cargo.toml | 46 ++ fuzzers/frida_executable_libpng/Makefile.toml | 115 +++++ fuzzers/frida_executable_libpng/README.md | 36 ++ .../corpus/not_kitty.png | Bin 0 -> 218 bytes .../corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes .../corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes .../corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/frida_executable_libpng/harness.cc | 228 +++++++++ fuzzers/frida_executable_libpng/src/fuzzer.rs | 483 ++++++++++++++++++ fuzzers/frida_executable_libpng/src/lib.rs | 58 +++ utils/build_and_test_fuzzers/Cargo.toml | 1 - 12 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 fuzzers/frida_executable_libpng/.gitignore create mode 100644 fuzzers/frida_executable_libpng/Cargo.toml create mode 100644 fuzzers/frida_executable_libpng/Makefile.toml create mode 100644 fuzzers/frida_executable_libpng/README.md create mode 100644 fuzzers/frida_executable_libpng/corpus/not_kitty.png create mode 100644 fuzzers/frida_executable_libpng/corpus/not_kitty_alpha.png create mode 100644 fuzzers/frida_executable_libpng/corpus/not_kitty_gamma.png create mode 100644 fuzzers/frida_executable_libpng/corpus/not_kitty_icc.png create mode 100644 fuzzers/frida_executable_libpng/harness.cc create mode 100644 fuzzers/frida_executable_libpng/src/fuzzer.rs create mode 100644 fuzzers/frida_executable_libpng/src/lib.rs diff --git a/fuzzers/frida_executable_libpng/.gitignore b/fuzzers/frida_executable_libpng/.gitignore new file mode 100644 index 0000000000..8d08c6cf96 --- /dev/null +++ b/fuzzers/frida_executable_libpng/.gitignore @@ -0,0 +1,5 @@ +libpng-* +corpus_discovered +libafl_frida +frida_libpng +zlib* \ No newline at end of file diff --git a/fuzzers/frida_executable_libpng/Cargo.toml b/fuzzers/frida_executable_libpng/Cargo.toml new file mode 100644 index 0000000000..48556ffda0 --- /dev/null +++ b/fuzzers/frida_executable_libpng/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "frida_executable_fuzzer" +version = "0.1.0" +edition = "2021" + +[lib] +name = "frida_executable_fuzzer" +crate_type = ["cdylib"] + +[features] +default = ["std"] +std = [] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[build-dependencies] +cc = { version = "1.0.42", features = ["parallel"] } +which = "4.1" +xz2 = "0.1.6" +flate2 = "1.0.22" +tar = "0.4.37" +reqwest = { version = "0.11.4", features = ["blocking"] } + + + + +[dependencies] +libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public", "frida_cli" ] } #, "llmp_small_maps", "llmp_debug"]} +capstone = "0.11.0" +frida-gum = { version = "0.8.1", features = [ "auto-download", "event-sink", "invocation-listener"] } +libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } +libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] } +libc = "0.2" +libloading = "0.7" +num-traits = "0.2" +rangemap = "1" +clap = { version = "4.0", features = ["derive"] } +serde = "1.0" +mimalloc = { version = "*", default-features = false } + +backtrace = "0.3" +color-backtrace = "0.5" diff --git a/fuzzers/frida_executable_libpng/Makefile.toml b/fuzzers/frida_executable_libpng/Makefile.toml new file mode 100644 index 0000000000..b412afa4a6 --- /dev/null +++ b/fuzzers/frida_executable_libpng/Makefile.toml @@ -0,0 +1,115 @@ +# Variables +[env] +CARGO_TARGET_DIR = { value = "target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } } + + +[tasks.unsupported] +script_runner="@shell" +script=''' +echo "Cargo-make not integrated yet on this" +''' + +# libpng +[tasks.libpng] +linux_alias = "libpng_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.libpng_unix] +condition = { files_not_exist = ["./libpng-1.6.37"]} +script_runner="@shell" +script=''' +wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz +tar -xvf libpng-1.6.37.tar.xz +''' + +# Library +[tasks.lib] +linux_alias = "lib_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.lib_unix] +script_runner="@shell" +script=''' +cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes +cd .. +make -C libpng-1.6.37 +''' +dependencies = [ "libpng" ] + +# Harness +[tasks.harness] +linux_alias = "harness_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.harness_unix] +script_runner="@shell" +script=''' +clang++ -O0 -c -fPIC harness.cc -o harness.o +clang++ -O0 harness.cc libpng-1.6.37/.libs/libpng16.a -lz -o libpng-harness -g +''' +dependencies = [ "lib" ] + +# Fuzzer +[tasks.fuzzer] +linux_alias = "fuzzer_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.fuzzer_unix] +script_runner="@shell" +script=''' +cargo build --release +''' + +# Run the fuzzer +[tasks.run] +linux_alias = "run_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.run_unix] +script_runner = "@shell" +script=''' +LD_PRELOAD=$CARGO_TARGET_DIR/release/libfrida_executable_fuzzer.so ./libpng-harness -i corpus -o out -H ./libpng-harness +''' +dependencies = [ "fuzzer", "harness" ] + +# Test +[tasks.test] +linux_alias = "test_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.test_unix] +script_runner = "@shell" +script=''' +rm -rf libafl_unix_shmem_server || true +LD_PRELOAD=$CARGO_TARGET_DIR/release/libfrida_executable_fuzzer.so ./libpng-harness -i corpus -o out -H ./libpng-harness > fuzz_stdout.log & +sleep 10s && pkill libpng-harness +if [ -z "$(grep "corpus: 30" fuzz_stdout.log)" ]; then + echo "Fuzzer does not generate any testcases or any crashes" + exit 1 +else + echo "Fuzzer is working" +fi +''' +dependencies = [ "fuzzer", "harness" ] + +# Clean up +[tasks.clean] +linux_alias = "clean_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.clean_unix] +# Disable default `clean` definition +clear = true +script_runner="@shell" +script=''' +rm -f ./libpng-harness +make -C libpng-1.6.37 clean +cargo clean +''' diff --git a/fuzzers/frida_executable_libpng/README.md b/fuzzers/frida_executable_libpng/README.md new file mode 100644 index 0000000000..fd3cae6bbd --- /dev/null +++ b/fuzzers/frida_executable_libpng/README.md @@ -0,0 +1,36 @@ +# Fuzzing libpng with frida as executale + +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 --release` in this folder. +This will call (the build.rs)[./build.rs], which in turn downloads a libpng archive from the web. +Then, it will build (the C++ harness)[./harness.cc] and the instrumented `libpng`. +Then, it will create frida fuzzer shared library in `./target/release/libfrida_fuzzer.so`. +On unix platforms, you'll need [libc++](https://libcxx.llvm.org/) to build it. + +Alternatively you can run `cargo make run` and this command will automatically build and run the fuzzer + +### Build For Android +When building for android using a cross-compiler, make sure you have a [_standalone toolchain_](https://developer.android.com/ndk/guides/standalone_toolchain), and then add the following: +1. In the ~/.cargo/config file add a target with the correct cross-compiler toolchain name (in this case aarch64-linux-android, but names may vary) +`[target.aarch64-linux-android]` +`linker="aarch64-linux-android-clang"` +2. add path to installed toolchain to PATH env variable. +3. define CLANG_PATH and add target to the build command line: +`CLANG_PATH=/bin/aarch64-linux-android-clang cargo -v build --release --target=aarch64-linux-android` + +## Run + +This example uses in-process-fuzzing, using the `launcher` feature, in combination with a Restarting Event Manager. +This means running --cores each client will start itself again to listen for crashes and timeouts. +By restarting the actual fuzzer, it can recover from these exit conditions. + +After building the libpng-harness, you can run `find . -name libpng-harness` to find the location of your harness, then run + +``` +LD_PRELOAD=./target/release/libfrida_fuzzer.so ./libpng-harness -i corpus -o out -l ./libpng-harness.so +``` \ No newline at end of file diff --git a/fuzzers/frida_executable_libpng/corpus/not_kitty.png b/fuzzers/frida_executable_libpng/corpus/not_kitty.png new file mode 100644 index 0000000000000000000000000000000000000000..eff7c1707b936a8f8df725814f604d454b78b5c3 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X_yc@GT+_~+`TzevkY_wIZRYx+5&y#hyq+?%!C8<`)MX5lF!N|bSRM)^r*U&J;z}U*bz{;0L z1Vuw`eoAIqC5i?kD`P_|6GMoGiCWXn12ss3YzWRzD=AMbN@Z|N$xljE@XSq2PYp^< WOsOn9nQ8-6#Ng@b=d#Wzp$PyV*n0l} literal 0 HcmV?d00001 diff --git a/fuzzers/frida_executable_libpng/corpus/not_kitty_gamma.png b/fuzzers/frida_executable_libpng/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000000000000000000000000000000000..939d9d29a9b9f95bac5e9a72854361ee85469921 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmTQ929t;oCfmw1AIbU z)6Sgv|NlRbXFM})=KnKxKI=t+9LW;bh?3y^w370~qErUQl>DSr1<%~X^wgl##FWay zlc_d9MbVxvjv*GO?@o5)YH;9THa`3B|5>?^8?LvjJ}xLe>!7e@k)r^sLedir0mCVe z=5sMjEm$*~tHD+}{NS_$nMdb|ABqg-@UGMMsZ=uY-X%Cq@&3vmZ%&@H{P?6&+U!yq VvuXWlo?M_c44$rjF6*2UngF4cP+$N6 literal 0 HcmV?d00001 diff --git a/fuzzers/frida_executable_libpng/corpus/not_kitty_icc.png b/fuzzers/frida_executable_libpng/corpus/not_kitty_icc.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c7804d99829cc6307c1c6ae9915cf42d555414 GIT binary patch literal 427 zcmV;c0aX5pP)9xSWu9|B*4Isn^#g47m^r~thH)GiR<@yX0fO)OF<2Kt#qCldyUF#H?{4jV?XGw9)psxE&K1B1m^ z1_tH{2(hG@3=G>_85ksPA;eS`Ffj19FfeR8pIlm01~rBeWCZ{dbvfq;rA3DT000kA zOjJc?%*_A){{R30GnreSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM003J_L_t(I%iWVf3V=Wi12fJ3|IHp$*hSlV@t||fKp?cDK@bHXV&o_g zF_hw;3ILUGteXmeJsVfSmcVJno)^MdQwU3bFHCtNG)uY>mLcD%`0UBaIq~Fq8#dBr V12uok3~c}a002ovPDHLkV1nKBo!S5Z literal 0 HcmV?d00001 diff --git a/fuzzers/frida_executable_libpng/harness.cc b/fuzzers/frida_executable_libpng/harness.cc new file mode 100644 index 0000000000..4323b5628e --- /dev/null +++ b/fuzzers/frida_executable_libpng/harness.cc @@ -0,0 +1,228 @@ +// 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 + +#include + +#define PNG_INTERNAL +#include "libpng-1.6.37/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; +} + +static char *allocation = NULL; + +__attribute__((noinline)) void func3(char *alloc) { + // printf("func3\n"); + if (random() == 0) { + alloc[0x1ff] = 0xde; + printf("alloc[0x200]: %d\n", alloc[0x200]); + } +} +__attribute__((noinline)) void func2() { + allocation = (char *)malloc(0xff); + // printf("func2\n"); + func3(allocation); +} +__attribute__((noinline)) void func1() { + // printf("func1\n"); + func2(); +} + +// Export this symbol +#define HARNESS_EXPORTS + +// Entry point for LibFuzzer. +// Roughly follows the libpng book example: +// http://www.libpng.org/pub/png/book/chapter13.html + +int main(int argc, char **argv) { + if (argc != 3) { abort(); } + size_t size = atoi(argv[1]); + uint8_t *data = (uint8_t *)argv[2]; + + if (size >= 8 && *(uint64_t *)data == 0xABCDEFAA8F1324AA) { abort(); } + if (size < kPngHeaderSize) { return 0; } + +#ifdef TEST_ASAN + func1(); +#endif + + 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_executable_libpng/src/fuzzer.rs b/fuzzers/frida_executable_libpng/src/fuzzer.rs new file mode 100644 index 0000000000..9a71c883e8 --- /dev/null +++ b/fuzzers/frida_executable_libpng/src/fuzzer.rs @@ -0,0 +1,483 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. +use mimalloc::MiMalloc; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +use std::{path::PathBuf, ptr::null}; + +use frida_gum::Gum; +use libafl::{ + bolts::{ + cli::{parse_args, FuzzerOptions}, + current_nanos, + launcher::Launcher, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + AsSlice, + }, + corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus}, + events::{llmp::LlmpRestartingEventManager, EventConfig}, + executors::{inprocess::InProcessExecutor, ExitKind, ShadowExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::{ + scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, + token_mutations::{I2SRandReplace, Tokens}, + }, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, + schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, + stages::{ShadowTracingStage, StdMutationalStage}, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; +#[cfg(unix)] +use libafl::{feedback_and_fast, feedbacks::ConstFeedback}; +#[cfg(unix)] +use libafl_frida::asan::{ + asan_rt::AsanRuntime, + errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS}, +}; +use libafl_frida::{ + cmplog_rt::CmpLogRuntime, + coverage_rt::{CoverageRuntime, MAP_SIZE}, + executor::FridaInProcessExecutor, + helper::FridaInstrumentationHelper, +}; +use libafl_targets::cmplog::CmpLogObserver; + +pub unsafe fn lib(main: extern "C" fn(i32, *const *const u8, *const *const u8) -> i32) { + color_backtrace::install(); + + let options = parse_args(); + + let mut frida_harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + let len = buf.len().to_string(); + + let argv: [*const u8; 3] = [ + null(), // dummy value + len.as_ptr() as _, + buf.as_ptr() as _, + ]; + + let env: [*const u8; 2] = [ + null(), // dummy value + null(), // dummy value + ]; + + main(3, argv.as_ptr(), env.as_ptr()); + ExitKind::Ok + }; + + unsafe { + match fuzz(&options, &mut frida_harness) { + Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."), + Err(e) => panic!("Error during fuzzing: {e:?}"), + } + } +} + +/// The actual fuzzer +#[allow(clippy::too_many_lines, clippy::too_many_arguments)] +unsafe fn fuzz( + options: &FuzzerOptions, + mut frida_harness: &dyn Fn(&BytesInput) -> ExitKind, +) -> Result<(), Error> { + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let monitor = MultiMonitor::new(|s| println!("{s}")); + + let shmem_provider = StdShMemProvider::new()?; + + let mut run_client = |state: Option<_>, mgr: LlmpRestartingEventManager<_, _>, core_id| { + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + + // println!("{:?}", mgr.mgr_id()); + + if options.asan && options.asan_cores.contains(core_id) { + (|state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, _core_id| { + let gum = Gum::obtain(); + + let coverage = CoverageRuntime::new(); + #[cfg(unix)] + let asan = AsanRuntime::new(options.clone()); + + #[cfg(unix)] + let mut frida_helper = + FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan)); + #[cfg(windows)] + let mut frida_helper = + FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage)); + + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + frida_helper.map_mut_ptr().unwrap(), + MAP_SIZE, + )); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::tracking(&edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::with_observer(&time_observer) + ); + + // Feedbacks to recognize an input as solution + #[cfg(unix)] + let mut objective = feedback_or_fast!( + CrashFeedback::new(), + TimeoutFeedback::new(), + // true enables the AsanErrorFeedback + feedback_and_fast!(ConstFeedback::from(true), AsanErrorsFeedback::new()) + ); + #[cfg(windows)] + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + CachedOnDiskCorpus::no_meta(PathBuf::from("./corpus_discovered"), 64) + .unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(options.output.clone()).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + println!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata_map().get::().is_none() { + state.add_metadata(Tokens::from([ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + b"IHDR".to_vec(), + b"IDAT".to_vec(), + b"PLTE".to_vec(), + b"IEND".to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + #[cfg(unix)] + let observers = tuple_list!( + edges_observer, + time_observer, + AsanErrorsObserver::new(&ASAN_ERRORS) + ); + #[cfg(windows)] + let observers = tuple_list!(edges_observer, time_observer); + + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + &mut frida_harness, + observers, + &mut fuzzer, + &mut state, + &mut mgr, + )?, + &mut frida_helper, + ); + + // In case the corpus is empty (on first run), reset + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input) + .unwrap_or_else(|_| { + panic!("Failed to load initial corpus at {:?}", &options.input) + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + + Ok(()) + })(state, mgr, core_id) + } else if options.cmplog && options.cmplog_cores.contains(core_id) { + (|state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, _core_id| { + let gum = Gum::obtain(); + + let coverage = CoverageRuntime::new(); + let cmplog = CmpLogRuntime::new(); + + let mut frida_helper = + FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog)); + + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + frida_helper.map_mut_ptr().unwrap(), + MAP_SIZE, + )); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::tracking(&edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::with_observer(&time_observer) + ); + + #[cfg(unix)] + let mut objective = feedback_or_fast!( + CrashFeedback::new(), + TimeoutFeedback::new(), + feedback_and_fast!(ConstFeedback::from(false), AsanErrorsFeedback::new()) + ); + #[cfg(windows)] + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + CachedOnDiskCorpus::no_meta(PathBuf::from("./corpus_discovered"), 64) + .unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(options.output.clone()).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + println!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata_map().get::().is_none() { + state.add_metadata(Tokens::from([ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + b"IHDR".to_vec(), + b"IDAT".to_vec(), + b"PLTE".to_vec(), + b"IEND".to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + #[cfg(unix)] + let observers = tuple_list!( + edges_observer, + time_observer, + AsanErrorsObserver::new(&ASAN_ERRORS) + ); + #[cfg(windows)] + let observers = tuple_list!(edges_observer, time_observer,); + + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + &mut frida_harness, + observers, + &mut fuzzer, + &mut state, + &mut mgr, + )?, + &mut frida_helper, + ); + + // In case the corpus is empty (on first run), reset + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input) + .unwrap_or_else(|_| { + panic!("Failed to load initial corpus at {:?}", &options.input) + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + // Create an observation channel using cmplog map + let cmplog_observer = CmpLogObserver::new("cmplog", true); + + let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); + + let tracing = ShadowTracingStage::new(&mut executor); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!( + I2SRandReplace::new() + ))); + + // Setup a basic mutator + let mutational = StdMutationalStage::new(mutator); + + // The order of the stages matter! + let mut stages = tuple_list!(tracing, i2s, mutational); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + + Ok(()) + })(state, mgr, core_id) + } else { + (|state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, _core_id| { + let gum = Gum::obtain(); + + let coverage = CoverageRuntime::new(); + + let mut frida_helper = + FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage)); + + // Create an observation channel using the coverage map + let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + frida_helper.map_mut_ptr().unwrap(), + MAP_SIZE, + )); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::tracking(&edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::with_observer(&time_observer) + ); + + #[cfg(unix)] + let mut objective = feedback_or_fast!( + CrashFeedback::new(), + TimeoutFeedback::new(), + feedback_and_fast!(ConstFeedback::from(false), AsanErrorsFeedback::new()) + ); + #[cfg(windows)] + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + CachedOnDiskCorpus::no_meta(PathBuf::from("./corpus_discovered"), 64) + .unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(options.output.clone()).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + println!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata_map().get::().is_none() { + state.add_metadata(Tokens::from([ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + b"IHDR".to_vec(), + b"IDAT".to_vec(), + b"PLTE".to_vec(), + b"IEND".to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + #[cfg(unix)] + let observers = tuple_list!( + edges_observer, + time_observer, + AsanErrorsObserver::new(&ASAN_ERRORS) + ); + #[cfg(windows)] + let observers = tuple_list!(edges_observer, time_observer,); + + // Create the executor for an in-process function with just one observer for edge coverage + let mut executor = FridaInProcessExecutor::new( + &gum, + InProcessExecutor::new( + &mut frida_harness, + observers, + &mut fuzzer, + &mut state, + &mut mgr, + )?, + &mut frida_helper, + ); + + // In case the corpus is empty (on first run), reset + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input) + .unwrap_or_else(|_| { + panic!("Failed to load initial corpus at {:?}", &options.input) + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + + Ok(()) + })(state, mgr, core_id) + } + }; + + Launcher::builder() + .configuration(EventConfig::AlwaysUnique) + .shmem_provider(shmem_provider) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&options.cores) + .broker_port(options.broker_port) + .stdout_file(Some(&options.stdout)) + .remote_broker_addr(options.remote_broker_addr) + .build() + .launch() +} diff --git a/fuzzers/frida_executable_libpng/src/lib.rs b/fuzzers/frida_executable_libpng/src/lib.rs new file mode 100644 index 0000000000..5ae173639d --- /dev/null +++ b/fuzzers/frida_executable_libpng/src/lib.rs @@ -0,0 +1,58 @@ +use std::mem::transmute; + +use libc::{c_void, dlsym, RTLD_NEXT}; + +mod fuzzer; + +type LibcStartMainFunc = fn( + unsafe extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + i32, + *const *const char, + extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + extern "C" fn(), + extern "C" fn(), + *mut c_void, +) -> i32; + +type MainFunc = extern "C" fn(i32, *const *const u8, *const *const u8) -> i32; + +extern "C" fn _dummy_main(_argc: i32, _argv: *const *const u8, _env: *const *const u8) -> i32 { + 0 +} + +static mut ORIG_MAIN: MainFunc = _dummy_main; + +#[no_mangle] +pub unsafe extern "C" fn main_hook( + _argc: i32, + _argv: *const *const u8, + _env: *const *const u8, +) -> i32 { + fuzzer::lib(ORIG_MAIN); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn __libc_start_main( + main: extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + argc: i32, + argv: *const *const char, + init: extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + fini: extern "C" fn(), + rtld_fini: extern "C" fn(), + stack_end: *mut c_void, +) -> i32 { + unsafe { + ORIG_MAIN = main; + + let orig_libc_start_main_addr: *mut c_void = + dlsym(RTLD_NEXT, "__libc_start_main\0".as_ptr() as *const i8); + + let orig_libc_start_main: LibcStartMainFunc = transmute(orig_libc_start_main_addr); + + let exit_code = + orig_libc_start_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end); + + return exit_code; + } +} diff --git a/utils/build_and_test_fuzzers/Cargo.toml b/utils/build_and_test_fuzzers/Cargo.toml index a750e9e0d1..ee811fe8d0 100644 --- a/utils/build_and_test_fuzzers/Cargo.toml +++ b/utils/build_and_test_fuzzers/Cargo.toml @@ -12,4 +12,3 @@ categories = ["development-tools::testing"] [dependencies] cargo_toml = "0.14" walkdir = "2" -log = "0.4.17"