Frida for Windows (#287)

* harness.cc for win

* no backtrace for frida_gum

* build.rs message

* cfg guards

* at least libafl_frida builds with cfg guards

* fuzzer.rs builds on win

* clean up

* build instructions

* ps

* fix

* clang

* fix

* article

* static option to make it run on powershell

* vscode build instructions

* dllexport!

* fix

* build.rs

* fix & fmt

* message

* msys not necessary anymore

* Update README.md

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Toka 2021-09-30 05:10:15 +09:00 committed by GitHub
parent 5a246175cf
commit f63b862160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 295 additions and 128 deletions

View File

@ -19,11 +19,18 @@ debug = true
cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0"
which = "4.1"
xz = "0.1.0"
flate2 = "1.0.22"
tar = "0.4.37"
reqwest = { version = "0.11.4", features = ["blocking"] }
[target.'cfg(unix)'.dependencies]
[dependencies]
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]}
capstone = "0.8.0"
frida-gum = { version = "0.5.2", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
frida-gum = { version = "0.5.2", features = [ "auto-download", "event-sink", "invocation-listener"] }
libafl_frida = { path = "../../libafl_frida", version = "0.6.1", features = ["cmplog"] }
libafl_targets = { path = "../../libafl_targets", version = "0.6.1" , features = ["sancov_cmplog"] }
lazy_static = "1.4.0"

View File

@ -28,3 +28,73 @@ By restarting the actual fuzzer, it can recover from these exit conditions.
After building the libpng-harness, too, you can run `find . -name libpng-harness.so` to find the location of your harness, then run
`./target/release/frida_libpng ./libpng-harness.so LLVMFuzzerTestOneInput ./libpng-harness.so --cores=0`
## Windows
You can also fuzz libpng-1.6.37 on windows with frida mode!
### To build it with visual studio
1. Install clang for windows (make sure you add LLVM to the system path!)
[https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1](https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1)
2. Build libpng1.6.37
- Open libpng-1.6.37/projects/vstudio/vstudio.sln
- Open Build->Configuration Manager
- select Release for Active soltuion configuration and
- select <New>->x64 for Active solution platform (Copy settings from Win32)
- Then for libpng, pngstest, pngtest, pngunknown, pngvalid, zlib in Solution Explorer, choose General -> Configuration Type -> Static library(.lib)
- C/C++ -> Treat Warnings As Errors -> No
- C/C++ -> Code Generation -> Runtime Library -> Multi-threaded (/MT)
- Finally you can build libpng-1.6.37
3. Compile the harness
Fire up a powershell at this directory.
```
cp .\libpng-1.6.37\projects\vstudio\x64\Release\libpng16.lib .
cp .\libpng-1.6.37\projects\vstudio\x64\Release\zlib.lib .
cp .\target\release\frida_libpng.exe .
clang++ -O3 -c -I.\libpng-1.6.37 .\harness.cc -o .\harness.o
clang++ -L.\zlib.dll .\harness.o .\libpng16.lib -lzlib -shared -o .\libpng-harness.dll
```
4. Run the fuzzer
```
./frida_libpng.exe ./libpng-harness.dll LLVMFuzzerTestOneInput ./libpng-harness.dll --cores=0
```
### To build it with msys2
1. Install and setup msys2 (https://www.msys2.org/)
2. (Optional) If you prefer to compile libpng with clang, you can install it and its dependecy with
```
pacman -S mingw-w64-x86_64-clang
pacman -S mingw-w64-clang-x86_64-zlib
```
and
```
export LDFLAGS='-L/clang64/lib'
export CPPFLAGS='-I/clang64/include'
export CC=clang
export CXX=clang++
```
3. Compile frida_libpng (possibly from your powershell)
```
cargo build --release
cp ./target/release/frida_libpng.exe .
```
4. Compile libpng-1.6.37 with the following commands
```
cd libpng-1.6.37
./configure --enable-hardware-optimizations=yes --with-pic=yes
make
cd ..
```
5. Compile the harness with gcc or clang++
```
g++ -O3 -c -I./libpng-1.6.37 -fPIC harness.cc -o harness.o
g++ -O3 harness.o ./libpng-1.6.37/.libs/libpng16.a -static -shared -lz -o libpng-harness.dll
```
or
```
clang++ -O3 -c -I./libpng-1.6.37 -fPIC harness.cc -o harness.o
clang++ -O3 harness.o ./libpng-1.6.37/.libs/libpng16.a -static -shared -lz -o libpng-harness.dll
```
6. Run the fuzzer
```
./frida_libpng.exe ./libpng-harness.dll LLVMFuzzerTestOneInput ./libpng-harness.dll --cores=0
```

View File

@ -2,14 +2,21 @@
use std::{
env,
fs::{rename, File},
io,
path::Path,
process::{exit, Command},
};
use which::which;
use flate2::read::GzDecoder;
use tar::Archive;
use xz::read::XzDecoder;
const LIBPNG_URL: &str =
"https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz";
const ZLIB_URL: &str = "https://zlib.net/zlib-1.2.11.tar.gz";
fn build_dep_check(tools: &[&str]) {
for tool in tools {
@ -26,15 +33,58 @@ fn build_dep_check(tools: &[&str]) {
fn main() {
if cfg!(windows) {
println!("cargo:warning=Skipping libpng frida example on Windows");
exit(0);
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",);
println!("cargo:rerun-if-changed=harness.cc");
let libpng = format!("{}/libpng-1.6.37", &cwd);
let libpng_path = Path::new(&libpng);
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
let zlib = format!("{}/zlib", &cwd);
let zlib_1_2_11 = format!("{}/zlib-1.2.11", &cwd);
let zlib_path = Path::new(&zlib);
let zlib_tar = format!("{}/zlib-1.2.11.tar.gz", &cwd);
if !libpng_path.is_dir() {
if !Path::new(&libpng_tar).is_file() {
println!("cargo:warning=Libpng not found, downloading...");
// Download libpng
let mut resp = reqwest::blocking::get(LIBPNG_URL).expect("Libpng download failed");
let mut out = File::create(&libpng_tar).expect("Libpng download failed");
io::copy(&mut resp, &mut out).expect("Libpng downlaod failed");
let tar_xz = File::open(&libpng_tar).expect("Libpng extraction failed");
let tar = XzDecoder::new(tar_xz);
let mut archive = Archive::new(tar);
archive.unpack(&cwd).expect("Libpng extraction failed");
}
}
if !zlib_path.is_dir() {
if !Path::new(&zlib_tar).is_file() {
println!("cargo:warning=Zlib not found, downloading...");
// Download Zlib
let mut resp = reqwest::blocking::get(ZLIB_URL).expect("Zlib download failed");
let mut out = File::create(&zlib_tar).expect("Zlib download failed");
io::copy(&mut resp, &mut out).expect("Zlib downlaod failed");
let tar_gz = File::open(&zlib_tar).expect("Zlib extraction failed");
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(&cwd).expect("Zlib extraction failed");
rename(zlib_1_2_11, zlib).expect("Zlib extraction failed");
}
}
println!("cargo:warning=Now compile libpng with either visual studio or msys2");
} else {
let out_dir = env::var_os("OUT_DIR").unwrap();
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let out_dir = out_dir.to_string_lossy().to_string();
let out_dir_path = Path::new(&out_dir);
std::fs::create_dir_all(&out_dir).unwrap_or_else(|_| panic!("Failed to create {}", &out_dir));
std::fs::create_dir_all(&out_dir)
.unwrap_or_else(|_| panic!("Failed to create {}", &out_dir));
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",);
@ -135,3 +185,4 @@ fn main() {
.unwrap();
assert!(status.success());
}
}

View File

@ -88,10 +88,17 @@ static char * allocation = NULL;
__attribute__((noinline))
void func3( char * alloc) {
//printf("func3\n");
#ifdef _WIN32
if (rand() == 0) {
alloc[0x1ff] = 0xde;
printf("alloc[0x200]: %d\n", alloc[0x200]);
}
#else
if (random() == 0) {
alloc[0x1ff] = 0xde;
printf("alloc[0x200]: %d\n", alloc[0x200]);
}
#endif
}
__attribute__((noinline))
void func2() {
@ -104,10 +111,19 @@ void func1() {
//printf("func1\n");
func2();
}
// Export this symbol
#ifdef _WIN32
# define HARNESS_EXPORTS __declspec(dllexport)
#else
# define HARNESS_EXPORTS
#endif
// Entry point for LibFuzzer.
// Roughly follows the libpng book example:
// http://www.libpng.org/pub/png/book/chapter13.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
HARNESS_EXPORTS extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size >= 8 && *(uint64_t*)data == 0xABCDEFAA8F1324AA){
abort();

View File

@ -52,10 +52,13 @@ use std::{
};
use libafl_frida::{
asan_errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS},
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
FridaOptions,
};
#[cfg(unix)]
use libafl_frida::asan_errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS};
use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP};
struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT, S>
@ -107,6 +110,7 @@ where
if self.helper.stalker_enabled() {
self.stalker.deactivate();
}
#[cfg(unix)]
if unsafe { ASAN_ERRORS.is_some() && !ASAN_ERRORS.as_ref().unwrap().is_empty() } {
println!("Crashing target as it had ASAN errors");
unsafe {
@ -257,25 +261,7 @@ pub fn main() {
}
}
/// Not supported on windows right now
#[cfg(windows)]
#[allow(clippy::too_many_arguments)]
fn fuzz(
_module_name: &str,
_symbol_name: &str,
_corpus_dirs: &[PathBuf],
_objective_dir: &Path,
_broker_port: u16,
_cores: &[usize],
_stdout_file: Option<&str>,
_broker_addr: Option<SocketAddr>,
_configuration: String,
) -> Result<(), ()> {
todo!("Example not supported on Windows");
}
/// The actual fuzzer
#[cfg(unix)]
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
unsafe fn fuzz(
module_name: &str,
@ -343,12 +329,17 @@ unsafe fn fuzz(
);
// Feedbacks to recognize an input as solution
#[cfg(unix)]
let objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new(),
AsanErrorsFeedback::new()
);
#[cfg(windows)]
let 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(
@ -391,8 +382,11 @@ unsafe fn fuzz(
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
frida_helper.register_thread();
// Create the executor for an in-process function with just one observer for edge coverage
#[cfg(unix)]
let mut executor = FridaInProcessExecutor::new(
&gum,
InProcessExecutor::new(
@ -410,6 +404,20 @@ unsafe fn fuzz(
Duration::new(30, 0),
);
#[cfg(windows)]
let mut executor = FridaInProcessExecutor::new(
&gum,
InProcessExecutor::new(
&mut frida_harness,
tuple_list!(edges_observer, time_observer,),
&mut fuzzer,
&mut state,
&mut mgr,
)?,
&mut frida_helper,
Duration::new(30, 0),
);
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state

View File

@ -1,10 +1,4 @@
#[cfg(unix)]
mod fuzzer;
#[cfg(unix)]
pub fn main() {
fuzzer::main();
}
#[cfg(not(unix))]
pub fn main() {
todo!("Frida not yet supported on this OS.");
}

View File

@ -27,7 +27,7 @@ hashbrown = "0.11"
libloading = "0.7.0"
rangemap = "0.1.10"
frida-gum-sys = { version = "0.3", features = [ "auto-download", "event-sink", "invocation-listener"] }
frida-gum = { version = "0.5.2", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
frida-gum = { version = "0.5.2", features = [ "auto-download", "event-sink", "invocation-listener"] }
core_affinity = { version = "0.5", git = "https://github.com/s1341/core_affinity_rs", rev = "6648a7a" }
regex = "1.4"
dynasmrt = "1.0.1"

View File

@ -4,5 +4,6 @@ fn main() {
cc::Build::new().file("src/gettls.c").compile("libgettls.a");
// Force linking against libc++
#[cfg(unix)]
println!("cargo:rustc-link-lib=dylib=c++");
}

View File

@ -31,10 +31,15 @@ use num_traits::cast::FromPrimitive;
use rangemap::RangeMap;
#[cfg(unix)]
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
#[cfg(unix)]
use crate::{asan_rt::AsanRuntime, FridaOptions};
#[cfg(windows)]
use crate::FridaOptions;
#[cfg(feature = "cmplog")]
use crate::cmplog_rt::CmpLogRuntime;
@ -54,7 +59,7 @@ enum SpecialCmpLogCase {
#[cfg(target_vendor = "apple")]
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON;
#[cfg(not(target_vendor = "apple"))]
#[cfg(not(any(target_vendor = "apple", target_os = "windows")))]
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANONYMOUS;
/// An helper that feeds [`FridaInProcessExecutor`] with user-supplied instrumentation
@ -63,6 +68,7 @@ pub trait FridaHelper<'a> {
fn transformer(&self) -> &Transformer<'a>;
/// Register a new thread with this `FridaHelper`
#[cfg(unix)]
fn register_thread(&mut self);
/// Called prior to execution of an input
@ -94,6 +100,7 @@ pub struct FridaInstrumentationHelper<'a> {
transformer: Option<Transformer<'a>>,
#[cfg(target_arch = "aarch64")]
capstone: Capstone,
#[cfg(unix)]
asan_runtime: AsanRuntime,
#[cfg(feature = "cmplog")]
cmplog_runtime: CmpLogRuntime,
@ -109,6 +116,7 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
}
/// Register the current thread with the [`FridaInstrumentationHelper`]
#[cfg(unix)]
fn register_thread(&mut self) {
self.asan_runtime.register_thread();
}
@ -258,6 +266,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
modules_to_instrument: &'a [&str],
) -> Self {
// workaround frida's frida-gum-allocate-near bug:
#[cfg(unix)]
unsafe {
for _ in 0..512 {
mmap(
@ -295,6 +304,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
.detail(true)
.build()
.expect("Failed to create Capstone object"),
#[cfg(not(windows))]
asan_runtime: AsanRuntime::new(options.clone()),
#[cfg(feature = "cmplog")]
cmplog_runtime: CmpLogRuntime::new(),
@ -342,6 +352,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
if helper.options().coverage_enabled() {
helper.emit_coverage_mapping(address, &output);
}
#[cfg(unix)]
if helper.options().drcov_enabled() {
instruction.put_callout(|context| {
let real_address =
@ -393,6 +404,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
}
}
#[cfg(unix)]
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
helper.asan_runtime.add_stalked_address(
output.writer().pc() as usize - 4,
@ -404,6 +416,8 @@ impl<'a> FridaInstrumentationHelper<'a> {
}
});
helper.transformer = Some(transformer);
#[cfg(unix)]
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
helper.asan_runtime.init(gum, modules_to_instrument);
}
@ -779,6 +793,8 @@ impl<'a> FridaInstrumentationHelper<'a> {
writer.put_b_label(after_report_impl);
self.current_report_impl = writer.pc();
#[cfg(unix)]
writer.put_bytes(self.asan_runtime.blob_report());
writer.put_label(after_report_impl);
@ -915,6 +931,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
}
}
// Insert the check_shadow_mem code blob
#[cfg(unix)]
match width {
1 => writer.put_bytes(&self.asan_runtime.blob_check_mem_byte()),
2 => writer.put_bytes(&self.asan_runtime.blob_check_mem_halfword()),

View File

@ -4,10 +4,13 @@ It can report coverage and, on supported architecutres, even reports memory acce
*/
/// The frida-asan allocator
#[cfg(unix)]
pub mod alloc;
/// Handling of ASAN errors
#[cfg(unix)]
pub mod asan_errors;
/// The frida address sanitizer runtime
#[cfg(unix)]
pub mod asan_rt;
#[cfg(feature = "cmplog")]