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:
henri2h 2025-02-15 04:15:16 +01:00 committed by GitHub
parent b3fe744e57
commit 0aba2c4520
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 700 additions and 6 deletions

View File

@ -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:

View File

@ -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"

View File

@ -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

View 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"

View 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

View 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

View 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;
}

View 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
View 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
View 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);
}
}
}
}
}

View 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
}

View 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();
}

View File

@ -0,0 +1,3 @@
pub mod emu;
pub mod helper;
pub mod hooks;