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:
parent
c9a78f154b
commit
38ea17b426
5
fuzzers/frida_executable_libpng/.gitignore
vendored
Normal file
5
fuzzers/frida_executable_libpng/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
libpng-*
|
||||
corpus_discovered
|
||||
libafl_frida
|
||||
frida_libpng
|
||||
zlib*
|
46
fuzzers/frida_executable_libpng/Cargo.toml
Normal file
46
fuzzers/frida_executable_libpng/Cargo.toml
Normal 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"
|
115
fuzzers/frida_executable_libpng/Makefile.toml
Normal file
115
fuzzers/frida_executable_libpng/Makefile.toml
Normal 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
|
||||
'''
|
36
fuzzers/frida_executable_libpng/README.md
Normal file
36
fuzzers/frida_executable_libpng/README.md
Normal 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
|
||||
```
|
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty.png
Normal file
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_alpha.png
Normal file
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 B |
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_gamma.png
Normal file
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_gamma.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 B |
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_icc.png
Normal file
BIN
fuzzers/frida_executable_libpng/corpus/not_kitty_icc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 B |
228
fuzzers/frida_executable_libpng/harness.cc
Normal file
228
fuzzers/frida_executable_libpng/harness.cc
Normal 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;
|
||||
}
|
483
fuzzers/frida_executable_libpng/src/fuzzer.rs
Normal file
483
fuzzers/frida_executable_libpng/src/fuzzer.rs
Normal 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()
|
||||
}
|
58
fuzzers/frida_executable_libpng/src/lib.rs
Normal file
58
fuzzers/frida_executable_libpng/src/lib.rs
Normal 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;
|
||||
}
|
||||
}
|
@ -12,4 +12,3 @@ categories = ["development-tools::testing"]
|
||||
[dependencies]
|
||||
cargo_toml = "0.14"
|
||||
walkdir = "2"
|
||||
log = "0.4.17"
|
||||
|
Loading…
x
Reference in New Issue
Block a user