diff options
Diffstat (limited to 'unicorn_mode')
-rw-r--r-- | unicorn_mode/README.md | 45 | ||||
-rw-r--r-- | unicorn_mode/UNICORNAFL_VERSION | 2 | ||||
-rwxr-xr-x | unicorn_mode/build_unicorn_support.sh | 2 | ||||
-rw-r--r-- | unicorn_mode/samples/c/COMPILE.md | 3 | ||||
-rw-r--r-- | unicorn_mode/samples/persistent/COMPILE.md | 12 | ||||
-rw-r--r-- | unicorn_mode/samples/speedtest/README.md | 15 | ||||
-rw-r--r-- | unicorn_mode/samples/speedtest/rust/Cargo.toml | 4 | ||||
-rw-r--r-- | unicorn_mode/samples/speedtest/rust/src/main.rs | 47 | ||||
m--------- | unicorn_mode/unicornafl | 0 | ||||
-rwxr-xr-x | unicorn_mode/update_uc_ref.sh | 4 |
10 files changed, 72 insertions, 62 deletions
diff --git a/unicorn_mode/README.md b/unicorn_mode/README.md index d2b7d16f..ee4a7b22 100644 --- a/unicorn_mode/README.md +++ b/unicorn_mode/README.md @@ -8,10 +8,11 @@ The CompareCoverage and NeverZero counters features are by Andrea Fioraldi <andr ## 1) Introduction -The code in ./unicorn_mode allows you to build the (Unicorn Engine)[https://github.com/unicorn-engine/unicorn] with AFL support. +The code in ./unicorn_mode allows you to build the +(Unicorn Engine)[https://github.com/unicorn-engine/unicorn] with AFL++ support. This means, you can run anything that can be emulated in unicorn and obtain instrumentation -output for black-box, closed-source binary code snippets. This mechanism -can be then used by afl-fuzz to stress-test targets that couldn't be built +output for black-box, closed-source binary code snippets. This mechanism +can be then used by afl-fuzz to stress-test targets that couldn't be built with afl-cc or used in QEMU mode. There is a significant performance penalty compared to native AFL, @@ -25,7 +26,7 @@ For some pointers for more advanced emulation, take a look at [BaseSAFE](https:/ ### Building AFL++'s Unicorn Mode First, make AFL++ as usual. -Once that completes successfully you need to build and add in the Unicorn Mode +Once that completes successfully you need to build and add in the Unicorn Mode features: ``` @@ -33,10 +34,10 @@ cd unicorn_mode ./build_unicorn_support.sh ``` -NOTE: This script checks out a Unicorn Engine fork as submodule that has been tested -and is stable-ish, based on the unicorn engine `next` branch. +NOTE: This script checks out a Unicorn Engine fork as submodule that has been tested +and is stable-ish, based on the unicorn engine `next` branch. -Building Unicorn will take a little bit (~5-10 minutes). Once it completes +Building Unicorn will take a little bit (~5-10 minutes). Once it completes it automatically compiles a sample application and verifies that it works. ### Fuzzing with Unicorn Mode @@ -46,25 +47,24 @@ To use unicorn-mode effectively you need to prepare the following: * Relevant binary code to be fuzzed * Knowledge of the memory map and good starting state * Folder containing sample inputs to start fuzzing with - + Same ideas as any other AFL inputs - + Quality/speed of results will depend greatly on the quality of starting + + Same ideas as any other AFL++ inputs + + Quality/speed of results will depend greatly on the quality of starting samples + See AFL's guidance on how to create a sample corpus * Unicornafl-based test harness in Rust, C, or Python, which: + Adds memory map regions - + Loads binary code into memory + + Loads binary code into memory + Calls uc.afl_fuzz() / uc.afl_start_forkserver + Loads and verifies data to fuzz from a command-line specified file - + AFL will provide mutated inputs by changing the file passed to + + AFL++ will provide mutated inputs by changing the file passed to the test harness + Presumably the data to be fuzzed is at a fixed buffer address - + If input constraints (size, invalid bytes, etc.) are known they - should be checked in the place_input handler. If a constraint - fails, just return false from the handler. AFL will treat the input as - 'uninteresting' and move on. + + If input constraints (size, invalid bytes, etc.) are known they + should be checked in the place_input handler. If a constraint + fails, just return false from the handler. AFL++ will treat the input as 'uninteresting' and move on. + Sets up registers and memory state for beginning of test + Emulates the interesting code from beginning to end - + If a crash is detected, the test harness must 'crash' by + + If a crash is detected, the test harness must 'crash' by throwing a signal (SIGSEGV, SIGKILL, SIGABORT, etc.), or indicate a crash in the crash validation callback. Once you have all those things ready to go you just need to run afl-fuzz in @@ -77,14 +77,13 @@ afl-fuzz -U -m none -i /path/to/inputs -o /path/to/results -- ./test_harness @@ The normal afl-fuzz command line format applies to everything here. Refer to AFL's main documentation for more info about how to use afl-fuzz effectively. -For a much clearer vision of what all of this looks like, please refer to the -sample provided in the 'unicorn_mode/samples' directory. There is also a blog -post that uses slightly older concepts, but describes the general ideas, at: +For a much clearer vision of what all of this looks like, refer to the sample +provided in the 'unicorn_mode/samples' directory. There is also a blog post that +uses slightly older concepts, but describes the general ideas, at: [https://medium.com/@njvoss299/afl-unicorn-fuzzing-arbitrary-binary-code-563ca28936bf](https://medium.com/@njvoss299/afl-unicorn-fuzzing-arbitrary-binary-code-563ca28936bf) - -The ['helper_scripts'](./helper_scripts) directory also contains several helper scripts that allow you +The ['helper_scripts'](./helper_scripts) directory also contains several helper scripts that allow you to dump context from a running process, load it, and hook heap allocations. For details on how to use this check out the follow-up blog post to the one linked above. @@ -105,8 +104,8 @@ Comparison instructions are currently instrumented only for the x86, x86_64 and ## 4) Gotchas, feedback, bugs -Running the build script builds Unicornafl and its python bindings and installs -them on your system. +Running the build script builds Unicornafl and its python bindings and installs +them on your system. This installation will leave any existing Unicorn installations untouched. If you want to use unicornafl instead of unicorn in a script, replace all `unicorn` imports with `unicornafl` inputs, everything else should "just work". diff --git a/unicorn_mode/UNICORNAFL_VERSION b/unicorn_mode/UNICORNAFL_VERSION index 0db54339..dbe3999f 100644 --- a/unicorn_mode/UNICORNAFL_VERSION +++ b/unicorn_mode/UNICORNAFL_VERSION @@ -1 +1 @@ -c0e03d2c +9df92d6868e8b219886e4b7458e5e134c48ff2c9 diff --git a/unicorn_mode/build_unicorn_support.sh b/unicorn_mode/build_unicorn_support.sh index 6c376f8d..f9c0be7f 100755 --- a/unicorn_mode/build_unicorn_support.sh +++ b/unicorn_mode/build_unicorn_support.sh @@ -20,7 +20,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # This script downloads, patches, and builds a version of Unicorn with # minor tweaks to allow Unicorn-emulated binaries to be run under diff --git a/unicorn_mode/samples/c/COMPILE.md b/unicorn_mode/samples/c/COMPILE.md index 7da140f7..e5265071 100644 --- a/unicorn_mode/samples/c/COMPILE.md +++ b/unicorn_mode/samples/c/COMPILE.md @@ -6,6 +6,7 @@ This shows a simple harness for unicornafl in C The target can be built using the `make` command. Just make sure you have built unicorn support first: + ```bash cd /path/to/afl/unicorn_mode ./build_unicorn_support.sh @@ -19,4 +20,4 @@ was built in case you want to rebuild it or recompile it for any reason. The pre-built binary (persistent_target_x86_64) was built using -g -O0 in gcc. -We then load the binary and execute the main function directly. +Then load the binary and execute the main function directly. diff --git a/unicorn_mode/samples/persistent/COMPILE.md b/unicorn_mode/samples/persistent/COMPILE.md index 111dfc54..5e607aef 100644 --- a/unicorn_mode/samples/persistent/COMPILE.md +++ b/unicorn_mode/samples/persistent/COMPILE.md @@ -1,13 +1,16 @@ # C Sample This shows a simple persistent harness for unicornafl in C. -In contrast to the normal c harness, this harness manually resets the unicorn state on each new input. -Thanks to this, we can rerun the testcase in unicorn multiple times, without the need to fork again. +In contrast to the normal c harness, this harness manually resets the unicorn +state on each new input. +Thanks to this, you can rerun the test case in unicorn multiple times, without +the need to fork again. ## Compiling sample.c The target can be built using the `make` command. Just make sure you have built unicorn support first: + ```bash cd /path/to/afl/unicorn_mode ./build_unicorn_support.sh @@ -19,6 +22,7 @@ You don't need to compile persistent_target.c since a X86_64 binary version is pre-built and shipped in this sample folder. This file documents how the binary was built in case you want to rebuild it or recompile it for any reason. -The pre-built binary (persistent_target_x86_64.bin) was built using -g -O0 in gcc. +The pre-built binary (persistent_target_x86_64.bin) was built using -g -O0 in +gcc. -We then load the binary and we execute the main function directly. +Then load the binary and execute the main function directly. \ No newline at end of file diff --git a/unicorn_mode/samples/speedtest/README.md b/unicorn_mode/samples/speedtest/README.md index 3c1184a2..bd5ba8d0 100644 --- a/unicorn_mode/samples/speedtest/README.md +++ b/unicorn_mode/samples/speedtest/README.md @@ -35,7 +35,6 @@ cd python TODO: add results here. - ## Compiling speedtest_target.c You shouldn't need to compile simple_target.c since a X86_64 binary version is @@ -44,22 +43,30 @@ was built in case you want to rebuild it or recompile it for any reason. The pre-built binary (simple_target_x86_64.bin) was built using -g -O0 in gcc. -We then load the binary and execute the main function directly. +Then load the binary and execute the main function directly. + +## Addresses for the harness -## Addresses for the harness: To find the address (in hex) of main, run: + ```bash objdump -M intel -D target | grep '<main>:' | cut -d" " -f1 ``` + To find all call sites to magicfn, run: + ```bash objdump -M intel -D target | grep '<magicfn>$' | cut -d":" -f1 ``` + For malloc callsites: + ```bash objdump -M intel -D target | grep '<malloc@plt>$' | cut -d":" -f1 ``` + And free callsites: + ```bash objdump -M intel -D target | grep '<free@plt>$' | cut -d":" -f1 -``` +``` \ No newline at end of file diff --git a/unicorn_mode/samples/speedtest/rust/Cargo.toml b/unicorn_mode/samples/speedtest/rust/Cargo.toml index c19ee0a1..9b81be0b 100644 --- a/unicorn_mode/samples/speedtest/rust/Cargo.toml +++ b/unicorn_mode/samples/speedtest/rust/Cargo.toml @@ -11,5 +11,5 @@ panic = "abort" [dependencies] unicornafl = { path = "../../../unicornafl/bindings/rust/", version="1.0.0" } -capstone="0.6.0" -libc="0.2.66" \ No newline at end of file +capstone="0.10.0" +libc="0.2.66" diff --git a/unicorn_mode/samples/speedtest/rust/src/main.rs b/unicorn_mode/samples/speedtest/rust/src/main.rs index 105ba4b4..cded1a3c 100644 --- a/unicorn_mode/samples/speedtest/rust/src/main.rs +++ b/unicorn_mode/samples/speedtest/rust/src/main.rs @@ -11,12 +11,13 @@ use std::{ }; use unicornafl::{ + afl::afl_fuzz, unicorn_const::{uc_error, Arch, Mode, Permission}, - RegisterX86::{self, *}, - Unicorn, UnicornHandle, + RegisterX86::*, + Unicorn, }; -const BINARY: &str = &"../target"; +const BINARY: &str = "../target"; // Memory map for the code to be tested // Arbitrary address where code to test will be loaded @@ -47,7 +48,7 @@ fn read_file(filename: &str) -> Result<Vec<u8>, io::Error> { fn parse_locs(loc_name: &str) -> Result<Vec<u64>, io::Error> { let contents = &read_file(&format!("../target.offsets.{}", loc_name))?; //println!("Read: {:?}", contents); - Ok(str_from_u8_unchecked(&contents) + Ok(str_from_u8_unchecked(contents) .split('\n') .map(|x| { //println!("Trying to convert {}", &x[2..]); @@ -87,8 +88,7 @@ fn main() { } fn fuzz(input_file: &str) -> Result<(), uc_error> { - let mut unicorn = Unicorn::new(Arch::X86, Mode::MODE_64, 0)?; - let mut uc: UnicornHandle<'_, _> = unicorn.borrow(); + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64)?; let binary = read_file(BINARY).unwrap_or_else(|_| panic!("Could not read modem image: {}", BINARY)); @@ -105,7 +105,7 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { // Set the program counter to the start of the code let main_locs = parse_locs("main").unwrap(); //println!("Entry Point: {:x}", main_locs[0]); - uc.reg_write(RegisterX86::RIP as i32, main_locs[0])?; + uc.reg_write(RIP, main_locs[0])?; // Setup the stack. uc.mem_map( @@ -114,14 +114,14 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { Permission::READ | Permission::WRITE, )?; // Setup the stack pointer, but allocate two pointers for the pointers to input. - uc.reg_write(RSP as i32, STACK_ADDRESS + STACK_SIZE - 16)?; + uc.reg_write(RSP, STACK_ADDRESS + STACK_SIZE - 16)?; // Setup our input space, and push the pointer to it in the function params uc.mem_map(INPUT_ADDRESS, INPUT_MAX as usize, Permission::READ)?; // We have argc = 2 - uc.reg_write(RDI as i32, 2)?; + uc.reg_write(RDI, 2)?; // RSI points to our little 2 QWORD space at the beginning of the stack... - uc.reg_write(RSI as i32, STACK_ADDRESS + STACK_SIZE - 16)?; + uc.reg_write(RSI, STACK_ADDRESS + STACK_SIZE - 16)?; // ... which points to the Input. Write the ptr to mem in little endian. uc.mem_write( STACK_ADDRESS + STACK_SIZE - 16, @@ -133,13 +133,13 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { let already_allocated_malloc = already_allocated.clone(); // We use a very simple malloc/free stub here, // that only works for exactly one allocation at a time. - let hook_malloc = move |mut uc: UnicornHandle<'_, _>, addr: u64, size: u32| { + let hook_malloc = move |uc: &mut Unicorn<'_, _>, addr: u64, size: u32| { if already_allocated_malloc.get() { println!("Double malloc, not supported right now!"); abort(); } // read the first param - let malloc_size = uc.reg_read(RDI as i32).unwrap(); + let malloc_size = uc.reg_read(RDI).unwrap(); if malloc_size > HEAP_SIZE_MAX { println!( "Tried to allocate {} bytes, but we may only allocate up to {}", @@ -147,20 +147,20 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { ); abort(); } - uc.reg_write(RAX as i32, HEAP_ADDRESS).unwrap(); - uc.reg_write(RIP as i32, addr + size as u64).unwrap(); + uc.reg_write(RAX, HEAP_ADDRESS).unwrap(); + uc.reg_write(RIP, addr + size as u64).unwrap(); already_allocated_malloc.set(true); }; let already_allocated_free = already_allocated; // No real free, just set the "used"-flag to false. - let hook_free = move |mut uc: UnicornHandle<'_, _>, addr, size| { + let hook_free = move |uc: &mut Unicorn<'_, _>, addr, size| { if already_allocated_free.get() { println!("Double free detected. Real bug?"); abort(); } // read the first param - let free_ptr = uc.reg_read(RDI as i32).unwrap(); + let free_ptr = uc.reg_read(RDI).unwrap(); if free_ptr != HEAP_ADDRESS { println!( "Tried to free wrong mem region {:x} at code loc {:x}", @@ -168,7 +168,7 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { ); abort(); } - uc.reg_write(RIP as i32, addr + size as u64).unwrap(); + uc.reg_write(RIP, addr + size as u64).unwrap(); already_allocated_free.set(false); }; @@ -177,8 +177,8 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { */ // This is a fancy print function that we're just going to skip for fuzzing. - let hook_magicfn = move |mut uc: UnicornHandle<'_, _>, addr, size| { - uc.reg_write(RIP as i32, addr + size as u64).unwrap(); + let hook_magicfn = move |uc: &mut Unicorn<'_, _>, addr, size| { + uc.reg_write(RIP, addr + size as u64).unwrap(); }; for addr in parse_locs("malloc").unwrap() { @@ -195,7 +195,7 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { } let place_input_callback = - |uc: &mut UnicornHandle<'_, _>, afl_input: &mut [u8], _persistent_round| { + |uc: &mut Unicorn<'_, _>, afl_input: &mut [u8], _persistent_round| { // apply constraints to the mutated input if afl_input.len() > INPUT_MAX as usize { //println!("Skipping testcase with leng {}", afl_input.len()); @@ -209,13 +209,12 @@ fn fuzz(input_file: &str) -> Result<(), uc_error> { // return true if the last run should be counted as crash let crash_validation_callback = - |_uc: &mut UnicornHandle<'_, _>, result, _input: &[u8], _persistent_round| { - result != uc_error::OK - }; + |_uc: &mut Unicorn<'_, _>, result, _input: &[u8], _persistent_round| result != uc_error::OK; let end_addrs = parse_locs("main_ends").unwrap(); - let ret = uc.afl_fuzz( + let ret = afl_fuzz( + &mut uc, input_file, place_input_callback, &end_addrs, diff --git a/unicorn_mode/unicornafl b/unicorn_mode/unicornafl -Subproject 019b871539fe9ed3f41d882385a8b02c243d49a +Subproject d4915053d477dd827b3fe4b494173d3fbf9f456 diff --git a/unicorn_mode/update_uc_ref.sh b/unicorn_mode/update_uc_ref.sh index 7c1c7778..6e809a7b 100755 --- a/unicorn_mode/update_uc_ref.sh +++ b/unicorn_mode/update_uc_ref.sh @@ -21,10 +21,10 @@ fi git submodule init && git submodule update unicornafl || exit 1 cd ./unicornafl || exit 1 -git fetch origin dev 1>/dev/null || exit 1 +git fetch origin uc1 1>/dev/null || exit 1 git stash 1>/dev/null 2>/dev/null git stash drop 1>/dev/null 2>/dev/null -git checkout dev +git checkout uc1 if [ -z "$NEW_VERSION" ]; then # No version provided, take HEAD. |