Add support for Unicorn engine (#1054)
* feat: unicorn support * feat: update * fix: remove unused files * fix: clean * fix: remove undeeded parameters * fix: typo * moved to justfile * use log::debug! * fix cargo and created justfile * feat: add CI * add runs on * fix: CI * fix: CI * fix: don't use fork executor * not needed anymore * fix: CI * fix: CI * remove extra space
This commit is contained in:
parent
b3fe744e57
commit
0aba2c4520
21
.github/workflows/build_and_test.yml
vendored
21
.github/workflows/build_and_test.yml
vendored
@ -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:
|
||||
|
@ -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"
|
||||
|
17
Dockerfile
17
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
|
||||
|
20
fuzzers/full_system/unicorn/Cargo.toml
Normal file
20
fuzzers/full_system/unicorn/Cargo.toml
Normal file
@ -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"
|
43
fuzzers/full_system/unicorn/Justfile
Normal file
43
fuzzers/full_system/unicorn/Justfile
Normal file
@ -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
|
36
fuzzers/full_system/unicorn/bin/Justfile
Normal file
36
fuzzers/full_system/unicorn/bin/Justfile
Normal file
@ -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
|
22
fuzzers/full_system/unicorn/bin/foo.c
Normal file
22
fuzzers/full_system/unicorn/bin/foo.c
Normal file
@ -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;
|
||||
}
|
356
fuzzers/full_system/unicorn/src/main.rs
Normal file
356
fuzzers/full_system/unicorn/src/main.rs
Normal file
@ -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<u8> = 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
|
||||
}
|
36
libafl_unicorn/Cargo.toml
Normal file
36
libafl_unicorn/Cargo.toml
Normal file
@ -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"]
|
122
libafl_unicorn/src/emu.rs
Normal file
122
libafl_unicorn/src/emu.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
libafl_unicorn/src/helper.rs
Normal file
11
libafl_unicorn/src/helper.rs
Normal file
@ -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
|
||||
}
|
16
libafl_unicorn/src/hooks.rs
Normal file
16
libafl_unicorn/src/hooks.rs
Normal file
@ -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();
|
||||
}
|
3
libafl_unicorn/src/lib.rs
Normal file
3
libafl_unicorn/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod emu;
|
||||
pub mod helper;
|
||||
pub mod hooks;
|
Loading…
x
Reference in New Issue
Block a user