diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bb538ba816..1b79bb67f9 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -343,6 +343,27 @@ jobs: - 'libafl_targets/**' - 'libafl_qemu/**' - 'fuzzers/**/*qemu*/**' + fuzzer-unicorn: + runs-on: ubuntu-24.04 + needs: + - fuzzers-preflight + strategy: + fail-fast: false + matrix: + os: [ ubuntu-24.04 ] + fuzzer: + - ./fuzzers/full_system/unicorn + steps: + - uses: actions/checkout@v4 + - uses: ./.github/workflows/fuzzer-tester-prepare + - name: "Install dependencies" + if: runner.os == 'Linux' + shell: bash + run: sudo apt-get update && sudo apt-get install gcc gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu + - name: Build and run example fuzzers (Linux) + if: runner.os == 'Linux' + shell: bash + run: RUN_ON_CI=1 LLVM_CONFIG=llvm-config-${{env.MAIN_LLVM_VERSION}} ./scripts/test_fuzzer.sh ${{ matrix.fuzzer }} fuzzers-qemu: needs: diff --git a/Cargo.toml b/Cargo.toml index 475edf6db4..7e4ba83dd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "libafl_intelpt", "libafl_libfuzzer", "libafl_nyx", + "libafl_unicorn", "libafl_targets", "libafl_tinyinst", "libafl_qemu", @@ -88,6 +89,7 @@ bindgen = "0.71.1" # 2024-12-16: bitbybit 1.3.3 is leading CI to fail due to missing docs. # fixme: Change this to 1.3.3 when the issue https://github.com/danlehmann/bitfield/issues/66 is resolved. bitbybit = "=1.3.2" # bitfields, use this for bit fields and bit enums +capstone = "0.13.0" # Disassembler used in libafl_unicorn to provide disassembly on crash clap = "4.5.18" cc = "1.1.21" cmake = "0.1.51" @@ -121,6 +123,7 @@ strum_macros = "0.27.0" toml = "0.8.19" # For parsing the injections toml file typed-builder = "0.20.0" # Implement the builder pattern at compiletime typeid = "1.0.0" # Safe type_eq that doesn't rely on std specialization +unicorn-engine = "2.0.1" # Used in libafl_unicorn uuid = { version = "1.10.0", features = ["serde", "v4"] } which = "6.0.3" windows = "0.59.0" diff --git a/Dockerfile b/Dockerfile index 1807afcc78..0e4e80bc16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,9 +14,9 @@ ENV SCCACHE_DIR=$HOME/.cache/sccache ENV RUSTC_WRAPPER="/usr/local/cargo/bin/sccache" ENV IS_DOCKER="1" RUN sh -c 'echo set encoding=utf-8 > /root/.vimrc' \ - echo "export PS1='"'[LibAFL \h] \w$(__git_ps1) \$ '"'" >> ~/.bashrc && \ - mkdir ~/.cargo && \ - echo "[build]\nrustc-wrapper = \"${RUSTC_WRAPPER}\"" >> ~/.cargo/config + echo "export PS1='"'[LibAFL \h] \w$(__git_ps1) \$ '"'" >> ~/.bashrc && \ + mkdir ~/.cargo && \ + echo "[build]\nrustc-wrapper = \"${RUSTC_WRAPPER}\"" >> ~/.cargo/config RUN rustup default nightly RUN rustup component add rustfmt clippy @@ -25,9 +25,9 @@ RUN rustup component add rustfmt clippy ENV LLVM_VERSION=18 RUN apt update && apt install -y build-essential gdb git wget python3-venv ninja-build lsb-release software-properties-common gnupg cmake RUN set -ex &&\ - wget https://apt.llvm.org/llvm.sh &&\ - chmod +x llvm.sh &&\ - ./llvm.sh ${LLVM_VERSION} + wget https://apt.llvm.org/llvm.sh &&\ + chmod +x llvm.sh &&\ + ./llvm.sh ${LLVM_VERSION} # Copy a dummy.rs and Cargo.toml first, so that dependencies are cached @@ -55,6 +55,9 @@ COPY libafl_frida/src/gettls.c libafl_frida/src/gettls.c COPY libafl_intelpt/Cargo.toml libafl_intelpt/README.md libafl_intelpt/ COPY scripts/dummy.rs libafl_intelpt/src/lib.rs +COPY libafl_unicorn/Cargo.toml libafl_unicorn/ +COPY scripts/dummy.rs libafl_unicorn/src/lib.rs + COPY libafl_qemu/Cargo.toml libafl_qemu/build.rs libafl_qemu/build_linux.rs libafl_qemu/ COPY scripts/dummy.rs libafl_qemu/src/lib.rs @@ -149,6 +152,8 @@ COPY libafl_libfuzzer/build.rs libafl_libfuzzer/build.rs RUN touch libafl_libfuzzer/src/lib.rs COPY libafl_intelpt/src libafl_intelpt/src RUN touch libafl_intelpt/src/lib.rs +COPY libafl_unicorn/src libafl_unicorn/src +RUN touch libafl_unicorn/src/lib.rs RUN cargo build && cargo build --release # Copy fuzzers over diff --git a/fuzzers/full_system/unicorn/Cargo.toml b/fuzzers/full_system/unicorn/Cargo.toml new file mode 100644 index 0000000000..efad8b5071 --- /dev/null +++ b/fuzzers/full_system/unicorn/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "unicorn" +version = "0.1.0" +edition = "2021" + +[features] +# Enable a code hook which log the program counter at each executed instruction +default = [] +code_hook = [] +mem_hook = [] + +[dependencies] +libafl = { path = "../../../libafl/" } +libafl_bolts = { path = "../../../libafl_bolts/" } +libafl_targets = { path = "../../../libafl_targets" } +libafl_unicorn = { path = "../../../libafl_unicorn/" } + +unicorn-engine = "2.1.2" +log = "0.4.25" +env_logger = "0.11.6" diff --git a/fuzzers/full_system/unicorn/Justfile b/fuzzers/full_system/unicorn/Justfile new file mode 100644 index 0000000000..cb4f80af4e --- /dev/null +++ b/fuzzers/full_system/unicorn/Justfile @@ -0,0 +1,43 @@ +FUZZER_NAME := 'unicorn' +PROJECT_DIR := absolute_path(".") +PROFILE := 'release' +PROFILE_DIR := 'release' +CARGO_TARGET_DIR := env("CARGO_TARGET_DIR", "target") +FUZZER := CARGO_TARGET_DIR / PROFILE_DIR / FUZZER_NAME + + +alias build := fuzzer + +fuzzer: + cargo build --profile={{PROFILE}} + +run: fuzzer + RUST_LOG="debug" {{FUZZER}} arm + +build_bin: + cd bin && just all + + +[linux] +[macos] +test: fuzzer build_bin (test_single "arm") (test_single "arm64") (test_single "x86") + echo "Done" + +test_single arch="arm": + #!/bin/bash + echo "Testing {{arch}}" + + RUST_LOG="debug" timeout 10s {{FUZZER}} {{arch}} 2>&1 | tee fuzz_stdout.log || true + if grep -qa "objectives: 1" fuzz_stdout.log; then + echo "Fuzzer is working" + else + echo "Fuzzer does not generate any testcases or any crashes" + exit 1 + fi + +[windows] +test: fuzzer + echo "Unsupported on this platform" + +clean: + cargo clean diff --git a/fuzzers/full_system/unicorn/bin/Justfile b/fuzzers/full_system/unicorn/bin/Justfile new file mode 100644 index 0000000000..119661f145 --- /dev/null +++ b/fuzzers/full_system/unicorn/bin/Justfile @@ -0,0 +1,36 @@ +arm64 := "aarch64-linux-gnu" +arm := "arm-linux-gnueabihf" +x64 := "x86_64-linux-gnu" + +assembly_arm64: + {{arm64}}-gcc -O2 -S -c foo.c -o foo_arm64.s + +binary_arm64: + {{arm64}}-as foo_arm64.s -o foo_arm64.elf + {{arm64}}-objcopy -O binary -j .text.startup foo_arm64.elf foo_arm64.bin + +assembly_arm: + {{arm}}-gcc -O2 -S -c foo.c -o foo_arm.s + +binary_arm: + {{arm}}-as foo_arm.s -o foo_arm.elf + {{arm}}-objcopy -O binary foo_arm.elf foo_arm.bin + +assembly_x86: + {{x64}}-gcc -O2 -S -c foo.c -o foo_x86.s + +binary_x86: + {{x64}}-as foo_x86.s -o foo_x86.elf + # Extract the .text.startup section + {{x64}}-objcopy -O binary -j .text.startup foo_x86.elf foo_x86.bin + +build_arm: assembly_arm binary_arm +build_arm64: assembly_arm64 binary_arm64 +build_x86: assembly_x86 binary_x86 + +clean: + rm foo_* + + +all: build_arm build_arm64 build_x86 +# sudo apt install gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu diff --git a/fuzzers/full_system/unicorn/bin/foo.c b/fuzzers/full_system/unicorn/bin/foo.c new file mode 100644 index 0000000000..ff85822d5e --- /dev/null +++ b/fuzzers/full_system/unicorn/bin/foo.c @@ -0,0 +1,22 @@ +int main() { + char *data = (char *)0x8000; + // Extract the input from the memory at 0x8000 + unsigned char a = data[0]; + unsigned char b = data[1]; + + if (a > b) { + if (a < 0x30) return 0x2; + if (a > 0x80) return 0x3; + if (a > 0x60) return 0x4; + if (a != 0x50) return 0x5; + + if (b < 0x20) return 0x7; + if (b > 0x60) return 0x8; + if (b > 0x30) return 0x9; + if (b == 0x24) return 0x6; // Success + + return 0x5; + } + + return 0x1; +} diff --git a/fuzzers/full_system/unicorn/src/main.rs b/fuzzers/full_system/unicorn/src/main.rs new file mode 100644 index 0000000000..e01fd39192 --- /dev/null +++ b/fuzzers/full_system/unicorn/src/main.rs @@ -0,0 +1,356 @@ +use std::{env, fs::File, io::Read, path::PathBuf, ptr::NonNull}; + +use libafl::{ + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{ExitKind, InProcessExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + generators::RandBytesGenerator, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::{havoc_mutations, scheduled::StdScheduledMutator}, + nonzero, + observers::{ConstMapObserver, HitcountsMapObserver, TimeObserver}, + schedulers::QueueScheduler, + stages::mutational::StdMutationalStage, + state::StdState, +}; +use libafl_bolts::{ + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + AsSlice, AsSliceMut, +}; +use libafl_targets::EDGES_MAP_DEFAULT_SIZE; +pub use libafl_targets::EDGES_MAP_PTR; +use libafl_unicorn::{ + emu::{debug_print, memory_dump}, + helper::get_stack_pointer, + hooks::set_coverage_hook, +}; +use unicorn_engine::{ + unicorn_const::{Arch, MemType, SECOND_SCALE}, + HookType, Mode, Permission, RegisterARM, RegisterARM64, RegisterX86, Unicorn, +}; + +pub const CODE_ADDRESS: u64 = 0x9000; +pub const CODE_SIZE: u64 = 0x1000; +pub const RETURN_ADDRESS: u64 = CODE_ADDRESS + CODE_SIZE - 0x8; // Such as that it works in 32 and 64 + // bit + +pub const DATA_ADDRESS: u64 = 0x8000; +pub const DATA_SIZE: u64 = 0x1000; +pub const MAX_INPUT_SIZE: usize = 0x100; //1048576; // 1MB + +pub const STACK_ADDRESS: u64 = 0x7000; +pub const STACK_SIZE: u64 = 0x1000; + +fn main() { + env_logger::init(); + + let args: Vec<_> = env::args().collect(); + let mut emu = false; + if args.len() < 2 { + log::debug!("Please specify the arcghitecture"); + return; + } + + let arch = match args[1].as_str() { + "arm" => Arch::ARM, + "arm64" => Arch::ARM64, + "x86" => Arch::X86, + _ => { + panic!("This arcghitecture is not supported") + } + }; + + if args.len() >= 3 && args[2] == "emu" { + emu = true; + } + fuzzer(emu, arch); +} + +pub fn init_registers(emu: &mut Unicorn<()>, sp: u64) { + match emu.get_arch() { + Arch::ARM => { + emu.reg_write(RegisterARM::SP, sp) + .expect("Could not setup register"); + } + Arch::ARM64 => { + emu.reg_write(RegisterARM64::SP, sp) + .expect("Could not setup register"); + } + Arch::X86 => { + emu.reg_write(RegisterX86::ESP, sp) + .expect("Could not setup register"); + } + _ => {} + } +} + +// emulating +fn fuzzer(should_emulate: bool, arch: Arch) { + let mode = match arch { + Arch::ARM => Mode::ARM, + Arch::ARM64 => Mode::ARM926, + Arch::X86 => Mode::MODE_64, + _ => Mode::MODE_64, + }; + + let mut emu = Unicorn::new(arch, mode).unwrap(); + + unicorn_map_and_load_code( + &mut emu, + CODE_ADDRESS, + CODE_SIZE as usize, + match arch { + Arch::ARM => "bin/foo_arm.bin", + Arch::ARM64 => "bin/foo_arm64.bin", + Arch::X86 => "bin/foo_x86.bin", + _ => "", + }, + ); + + // Map the data section in memory + emu.mem_map( + DATA_ADDRESS, + DATA_SIZE as usize, + Permission::WRITE | Permission::READ, + ) + .unwrap(); + + emu.mem_map( + STACK_ADDRESS, + STACK_SIZE as usize, + Permission::WRITE | Permission::READ, + ) + .unwrap(); + + #[cfg(feature = "code_hook")] + add_code_hook(&mut emu); + + #[cfg(feature = "mem_hook")] + emu.add_mem_hook( + HookType::MEM_READ + | HookType::MEM_READ_INVALID + | HookType::MEM_WRITE + | HookType::MEM_WRITE_UNMAPPED, + 0x0, + !0x0_u64, + mem_callback, + ) + .unwrap(); + + let mut shmem = StdShMemProvider::new() + .unwrap() + .new_shmem(EDGES_MAP_DEFAULT_SIZE) + .unwrap(); + + let shmem_buf = shmem.as_slice_mut(); + unsafe { + EDGES_MAP_PTR = shmem_buf.as_mut_ptr(); + } + + let edges_observer = unsafe { + HitcountsMapObserver::new(ConstMapObserver::<_, EDGES_MAP_DEFAULT_SIZE>::from_mut_ptr( + "edges", + NonNull::new(shmem_buf.as_mut_ptr()) + .expect("The edge map pointer is null.") + .cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(), + )) + }; + + // Add the coverage hook + set_coverage_hook(&mut emu); + + // Save context + let context = emu.context_init().unwrap(); + + let mut harness = |input: &BytesInput| { + emu.context_restore(&context).unwrap(); + + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let len = buf.len(); + if len > MAX_INPUT_SIZE { + buf = &buf[0..MAX_INPUT_SIZE]; + } + + // Load data in memory + emu.mem_write(DATA_ADDRESS, buf).unwrap(); + + init_registers(&mut emu, STACK_ADDRESS + STACK_SIZE - 0x8); + + // Store the return address + match arch { + Arch::ARM => emu.reg_write(RegisterARM::LR, RETURN_ADDRESS).unwrap(), + Arch::ARM64 => emu.reg_write(RegisterARM64::LR, RETURN_ADDRESS).unwrap(), + Arch::X86 => { + let bytes = u64::to_le_bytes(RETURN_ADDRESS); + + // Store the return value in the stack + emu.mem_write(STACK_SIZE + STACK_ADDRESS - 0x8, &bytes) + .unwrap(); + } + _ => {} + } + + let mut address = CODE_ADDRESS; + if arch == Arch::ARM { + address += 0x1; // We use thumb mode + } + + let result = emu.emu_start(address, RETURN_ADDRESS, SECOND_SCALE, 0x10000); + + match result { + Ok(_) => { + let result_value = match arch { + Arch::ARM => emu.reg_read(RegisterARM::R0).unwrap(), + Arch::ARM64 => emu.reg_read(RegisterARM64::W0).unwrap(), + Arch::X86 => emu.reg_read(RegisterX86::EAX).unwrap(), + _ => 0, + }; + if result_value == 0x6 { + log::debug!("Result found: 0x{result_value:x}"); + + return ExitKind::Crash; + } + } + Err(err) => { + log::error!("Error: {:?}", err); + + memory_dump(&emu, 2); + debug_print(&emu); + } + } + + ExitKind::Ok + }; + + if should_emulate { + log::info!("Starting emulation:"); + let mem_data: Vec = vec![0x50, 0x24, 0x36, 0x0]; + harness(&BytesInput::from(mem_data)); + log::info!("Done"); + return; + } + + let monitor = MultiMonitor::new(|s| log::info!("{s}")); + // The event manager handle the various events generated during the fuzzing loop + // such as the notification of the addition of a new item to the corpus + let mut mgr = SimpleEventManager::new(monitor); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interest of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + MaxMapFeedback::new(&edges_observer), + // MaxMapFeedback::new(&signal_observer), + TimeFeedback::new(&time_observer), + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap(); + + // A minimization+queue policy to get test cases from the corpus + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut executor = InProcessExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + ) + .expect("Failed to create the executor"); + + // Generator of printable bytearrays of max size 32 + let mut generator = RandBytesGenerator::new(nonzero!(4)); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"); +} + +fn unicorn_map_and_load_code(emu: &mut Unicorn<()>, address: u64, size: usize, path: &str) -> u64 { + let mut f = File::open(path).expect("Could not open file"); + let mut buffer = Vec::new(); + + // read the whole file + f.read_to_end(&mut buffer).expect("Could not read file"); + + // Define memory regions + emu.mem_map(address, address as usize + size, Permission::EXEC) + .expect("failed to map code page"); + + // Write memory + emu.mem_write(address, &buffer) + .expect("failed to write instructions"); + buffer.len() as u64 +} + +fn add_code_hook(emu: &mut Unicorn<()>) { + emu.add_code_hook(0x0, !0x0_u64, |emu, pc, _| { + let sp = get_stack_pointer(emu); + log::debug!("[PC: 0x{pc:x}] Hook: SP 0x:{sp:x}"); + }) + .unwrap(); +} +fn mem_callback( + emu: &mut unicorn_engine::Unicorn<()>, + mem: MemType, + address: u64, + size: usize, + value: i64, +) -> bool { + match mem { + MemType::WRITE => log::debug!( + "[PC: 0x{:x}] Memory is being WRITTEN at adress: {address:x} size: {size:} value: {value:}", + emu.pc_read().unwrap() + ), + MemType::READ => log::debug!( + "[PC: 0x{:x}] Memory is being READ at adress: {address:x} size: {size:}", + emu.pc_read().unwrap() + ), + _ => log::debug!( + "[PC: 0x{:x}] Memory access type: {mem:?} adress: {address:x} size: {size:} value: {value:}", + emu.pc_read().unwrap() + ), + } + + true +} diff --git a/libafl_unicorn/Cargo.toml b/libafl_unicorn/Cargo.toml new file mode 100644 index 0000000000..3f9cab69a6 --- /dev/null +++ b/libafl_unicorn/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "libafl_unicorn" +version.workspace = true +authors = [""] +description = "Unicorn backend library for LibAFL" +documentation = "https://docs.rs/" +repository = "https://github.com/AFLplusplus/" +readme = "../README.md" +license = "MIT OR Apache-2.0" +keywords = ["fuzzing", "unicorn"] +edition = "2021" +categories = [ + "development-tools::testing", + "emulators", + "embedded", + "os", + "no-std", +] + +[dependencies] +libafl = { path = "../libafl", default-features = false, features = [ + "std", + "derive", + "llmp_compression", +] } +libafl_targets = { path = "../libafl_targets" } + +# External dependencies +capstone = { workspace = true } +log = { workspace = true } +unicorn-engine = { workspace = true } + + +[lib] +name = "libafl_unicorn" +crate-type = ["cdylib", "rlib"] diff --git a/libafl_unicorn/src/emu.rs b/libafl_unicorn/src/emu.rs new file mode 100644 index 0000000000..687d53972f --- /dev/null +++ b/libafl_unicorn/src/emu.rs @@ -0,0 +1,122 @@ +use capstone::{ + arch::{self, BuildsCapstone, BuildsCapstoneSyntax}, + Capstone, +}; +pub use libafl_targets::{EDGES_MAP, EDGES_MAP_PTR}; +use unicorn_engine::{ + unicorn_const::{Arch, Permission}, + RegisterARM, RegisterARM64, RegisterX86, Unicorn, +}; + +use crate::helper::get_stack_pointer; +// TODO: For some reason, the compiled program start by substracting 0x10 to SP + +pub fn memory_dump(emu: &Unicorn<()>, len: u64) { + let sp = get_stack_pointer(emu); + for i in 0..len { + let pos = sp + i * 4 - len * 4; + + let data = emu.mem_read_as_vec(pos, 4).unwrap(); + + log::debug!( + "{:X}:\t {:02X} {:02X} {:02X} {:02X} {:08b} {:08b} {:08b} {:08b}", + pos, + data[0], + data[1], + data[2], + data[3], + data[0], + data[1], + data[2], + data[3] + ); + } +} + +pub fn debug_print(emu: &Unicorn<()>) { + log::debug!("Status when crash happened:"); + + let pc = emu.pc_read().unwrap(); + + log::debug!("PC: {:X}", pc); + let arch = emu.get_arch(); + match arch { + Arch::ARM => { + log::debug!("SP: {:X}", emu.reg_read(RegisterARM::SP).unwrap()); + log::debug!("R0: {:X}", emu.reg_read(RegisterARM::R0).unwrap()); + log::debug!("R1: {:X}", emu.reg_read(RegisterARM::R1).unwrap()); + log::debug!("R2: {:X}", emu.reg_read(RegisterARM::R2).unwrap()); + log::debug!("R3: {:X}", emu.reg_read(RegisterARM::R3).unwrap()); + } + Arch::ARM64 => { + log::debug!("SP: {:X}", emu.reg_read(RegisterARM64::SP).unwrap()); + log::debug!("X0: {:X}", emu.reg_read(RegisterARM64::X0).unwrap()); + log::debug!("X1: {:X}", emu.reg_read(RegisterARM64::X1).unwrap()); + log::debug!("X2: {:X}", emu.reg_read(RegisterARM64::X2).unwrap()); + log::debug!("X3: {:X}", emu.reg_read(RegisterARM64::X3).unwrap()); + } + Arch::X86 => { + log::debug!("ESP: {:X}", emu.reg_read(RegisterX86::ESP).unwrap()); + log::debug!("RAX: {:X}", emu.reg_read(RegisterX86::RAX).unwrap()); + log::debug!("RCX: {:X}", emu.reg_read(RegisterX86::RCX).unwrap()); + log::debug!("RDX: {:X}", emu.reg_read(RegisterX86::RDX).unwrap()); + log::debug!("RPB: {:X}", emu.reg_read(RegisterX86::RBP).unwrap()); + log::debug!("RSP: {:X}", emu.reg_read(RegisterX86::RSP).unwrap()); + } + _ => {} + } + + // Provide disassembly at instant of crash + let regions = emu.mem_regions().expect("Could not get memory regions"); + for i in 0..regions.len() { + if regions[i].perms.contains(Permission::EXEC) { + if pc >= regions[i].begin && pc <= regions[i].end { + let mut begin = pc - 32; + let mut end = pc + 32; + if begin < regions[i].begin { + begin = regions[i].begin; + } + if end > regions[i].end { + end = regions[i].end; + } + + let bytes = emu + .mem_read_as_vec(begin, (end - begin) as usize) + .expect("Could not get program code"); + let cs = match emu.get_arch() { + Arch::ARM => Capstone::new() + .arm() + .mode(arch::arm::ArchMode::Thumb) + .detail(true) + .build() + .expect("Failed to create Capstone object"), + Arch::ARM64 => Capstone::new() + .arm64() + .mode(arch::arm64::ArchMode::Arm) + .detail(true) + .build() + .expect("Failed to create Capstone object"), + + _ => Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode64) + .syntax(arch::x86::ArchSyntax::Intel) + .detail(true) + .build() + .expect("Failed to create Capstone object"), + }; + let insns = cs.disasm_all(&bytes, begin).expect("Failed to disassemble"); + + if !insns.is_empty() { + log::debug!("Code dump: [0x{begin:x} -> 0x{end:x}]"); + } else { + log::debug!("No disassembly available at PC: 0x{pc:x}"); + } + + for i in insns.as_ref() { + log::debug!("{}", i); + } + } + } + } +} diff --git a/libafl_unicorn/src/helper.rs b/libafl_unicorn/src/helper.rs new file mode 100644 index 0000000000..d365b33ebb --- /dev/null +++ b/libafl_unicorn/src/helper.rs @@ -0,0 +1,11 @@ +use unicorn_engine::{unicorn_const::Arch, RegisterARM, RegisterARM64, RegisterX86}; + +pub fn get_stack_pointer(emu: &unicorn_engine::Unicorn<()>) -> u64 { + let sp = match emu.get_arch() { + Arch::ARM => emu.reg_read(RegisterARM::SP).unwrap(), + Arch::ARM64 => emu.reg_read(RegisterARM64::SP).unwrap(), + Arch::X86 => emu.reg_read(RegisterX86::ESP).unwrap(), + _ => 0, + }; + sp +} diff --git a/libafl_unicorn/src/hooks.rs b/libafl_unicorn/src/hooks.rs new file mode 100644 index 0000000000..46bb72133a --- /dev/null +++ b/libafl_unicorn/src/hooks.rs @@ -0,0 +1,16 @@ +use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR}; +use unicorn_engine::Unicorn; + +fn coverage_hook(_emu: &mut unicorn_engine::Unicorn<()>, pc: u64, _: u32) { + unsafe { + let id = pc % EDGES_MAP_DEFAULT_SIZE as u64; + + let ptr = EDGES_MAP_PTR.add(id as usize); + let val = ptr.read().wrapping_add(1); + ptr.write(val); + } +} + +pub fn set_coverage_hook(emu: &mut Unicorn<()>) { + emu.add_block_hook(0x0, !0x0_u64, coverage_hook).unwrap(); +} diff --git a/libafl_unicorn/src/lib.rs b/libafl_unicorn/src/lib.rs new file mode 100644 index 0000000000..f6cef3da8f --- /dev/null +++ b/libafl_unicorn/src/lib.rs @@ -0,0 +1,3 @@ +pub mod emu; +pub mod helper; +pub mod hooks;