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
This commit is contained in:
Vincent 2023-03-22 23:18:21 +09:00 committed by GitHub
parent c9a78f154b
commit 38ea17b426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 971 additions and 1 deletions

View File

@ -0,0 +1,5 @@
libpng-*
corpus_discovered
libafl_frida
frida_libpng
zlib*

View File

@ -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"

View File

@ -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
'''

View File

@ -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=<path to installed toolchain>/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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View File

@ -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 <string.h>" 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 <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <vector>
#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<BufState *>(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<unsigned char> 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_bytep>(png_handler.row_ptr), nullptr);
}
}
png_read_end(png_handler.png_ptr, png_handler.end_info_ptr);
PNG_CLEANUP
return 0;
}

View File

@ -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::<Tokens>().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::<Tokens>().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::<Tokens>().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()
}

View File

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

View File

@ -12,4 +12,3 @@ categories = ["development-tools::testing"]
[dependencies]
cargo_toml = "0.14"
walkdir = "2"
log = "0.4.17"