QEMU Synchronous Exit + Syx Snapshot update (#1681)

* Fix: typo in variable name.

* Fix: thread-safe static for emulator initialization.

* Initial support for synchronous exit from QEMU.

* New commands for the sync exit feature.
Supports physical and virtual address requests.
Updated for new SyxSnapshot naming.

* update qemu commit and fix some things

* - Removed lazy_static dependency
- Compiles for usermode
- Format

* Fix warnings

* Fixed sync_exit for missing architectures

---------

Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
This commit is contained in:
Romain Malmain 2023-11-23 17:35:32 +01:00 committed by GitHub
parent 28f34e076e
commit 43c9100f59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 521 additions and 41 deletions

View File

@ -51,6 +51,7 @@ libafl_qemu_sys = { path = "./libafl_qemu_sys", version = "0.11.1" }
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
hashbrown = { version = "0.14", features = ["serde"] } # A faster hashmap, nostd compatible
num-traits = "0.2"
num-derive = "0.4"
num_enum = "0.7"
goblin = "0.7"
libc = "0.2"
@ -64,6 +65,7 @@ rangemap = "1.3"
log = "0.4.20"
addr2line = "0.21"
typed-arena = "2.0"
enum-map = "2.7"
pyo3 = { version = "0.18", optional = true }

View File

@ -76,6 +76,8 @@ const WRAPPER_HEADER: &str = r#"
#include "qemu/plugin-memory.h"
#include "libafl_extras/exit.h"
"#;
pub fn generate(
@ -107,7 +109,11 @@ pub fn generate(
.allowlist_type("qemu_plugin_mem_rw")
.allowlist_type("MemOpIdx")
.allowlist_type("MemOp")
.allowlist_type("device_snapshot_kind_t")
.allowlist_type("DeviceSnapshotKind")
.allowlist_type("libafl_exit_reason")
.allowlist_type("libafl_exit_reason_kind")
.allowlist_type("libafl_exit_reason_sync_backdoor")
.allowlist_type("libafl_exit_reason_breakpoint")
.allowlist_function("qemu_user_init")
.allowlist_function("target_mmap")
.allowlist_function("target_mprotect")
@ -123,10 +129,11 @@ pub fn generate(
.allowlist_function("qemu_plugin_get_hwaddr")
.allowlist_function("qemu_target_page_size")
.allowlist_function("syx_snapshot_init")
.allowlist_function("syx_snapshot_create")
.allowlist_function("syx_snapshot_new")
.allowlist_function("syx_snapshot_root_restore")
.allowlist_function("syx_snapshot_dirty_list_add")
.allowlist_function("device_list_all")
.allowlist_function("libafl_get_exit_reason")
.blocklist_function("main_loop_wait") // bindgen issue #1313
.parse_callbacks(Box::new(bindgen::CargoCallbacks));

View File

@ -8,7 +8,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "b0c827246517e36b480ad501cba5ac6e2c3f26f5";
const QEMU_REVISION: &str = "8db5524416b52c999459f1fe3373846bdcb23ac1";
fn build_dep_check(tools: &[&str]) {
for tool in tools {
@ -43,9 +43,9 @@ pub fn build(
cpu_target += "el";
}
let custum_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string());
let custum_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok();
let custum_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok();
let custom_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string());
let custom_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok();
let custom_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok();
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_DIR");
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_BUILD");
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_CONFIGURE");
@ -63,7 +63,7 @@ pub fn build(
let cc_compiler = cc::Build::new().cpp(false).get_compiler();
let cpp_compiler = cc::Build::new().cpp(true).get_compiler();
let qemu_path = if let Some(qemu_dir) = custum_qemu_dir.as_ref() {
let qemu_path = if let Some(qemu_dir) = custom_qemu_dir.as_ref() {
Path::new(&qemu_dir).to_path_buf()
} else {
let qemu_path = target_dir.join(QEMU_DIRNAME);
@ -128,14 +128,14 @@ pub fn build(
println!("cargo:rerun-if-changed={}", output_lib.to_string_lossy());
if !output_lib.is_file() || (custum_qemu_dir.is_some() && !custum_qemu_no_build) {
if !output_lib.is_file() || (custom_qemu_dir.is_some() && !custom_qemu_no_build) {
/*drop(
Command::new("make")
.current_dir(&qemu_path)
.arg("distclean")
.status(),
);*/
if is_usermode && !custum_qemu_no_configure {
if is_usermode && !custom_qemu_no_configure {
let mut cmd = Command::new("./configure");
cmd.current_dir(&qemu_path)
//.arg("--as-static-lib")
@ -162,7 +162,7 @@ pub fn build(
cmd.arg("--enable-debug");
}
cmd.status().expect("Configure failed");
} else if !custum_qemu_no_configure {
} else if !custom_qemu_no_configure {
let mut cmd = Command::new("./configure");
cmd.current_dir(&qemu_path)
//.arg("--as-static-lib")
@ -290,7 +290,8 @@ pub fn build(
.arg("--disable-xen")
.arg("--disable-xen-pci-passthrough")
.arg("--disable-xkbcommon")
.arg("--disable-zstd");
.arg("--disable-zstd")
.arg("--disable-tests");
if cfg!(feature = "debug_assertions") {
cmd.arg("--enable-debug");
}

View File

@ -1,11 +1,14 @@
use std::sync::OnceLock;
use capstone::arch::BuildsCapstone;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::aarch64::*;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -46,6 +49,23 @@ pub enum Regs {
Pstate = 33,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::X0,
SyncBackdoorArgs::Cmd => Regs::X0,
SyncBackdoorArgs::Arg1 => Regs::X1,
SyncBackdoorArgs::Arg2 => Regs::X2,
SyncBackdoorArgs::Arg3 => Regs::X3,
SyncBackdoorArgs::Arg4 => Regs::X4,
SyncBackdoorArgs::Arg5 => Regs::X5,
SyncBackdoorArgs::Arg6 => Regs::X6,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -1,11 +1,14 @@
use std::sync::OnceLock;
use capstone::arch::BuildsCapstone;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::arm::*;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
/// Registers for the ARM instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -30,6 +33,23 @@ pub enum Regs {
R25 = 25,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::R0,
SyncBackdoorArgs::Cmd => Regs::R0,
SyncBackdoorArgs::Arg1 => Regs::R1,
SyncBackdoorArgs::Arg2 => Regs::R2,
SyncBackdoorArgs::Arg3 => Regs::R3,
SyncBackdoorArgs::Arg4 => Regs::R4,
SyncBackdoorArgs::Arg5 => Regs::R5,
SyncBackdoorArgs::Arg6 => Regs::R6,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -4,7 +4,7 @@ use core::{
convert::Into,
ffi::c_void,
fmt,
mem::MaybeUninit,
mem::{transmute, MaybeUninit},
ptr::{addr_of, copy_nonoverlapping, null},
};
#[cfg(emulation_mode = "usermode")]
@ -14,7 +14,11 @@ use std::{
ffi::{CStr, CString},
ptr::null_mut,
};
use std::{slice::from_raw_parts, str::from_utf8_unchecked};
use std::{
slice::from_raw_parts,
str::from_utf8_unchecked,
sync::{Mutex, OnceLock},
};
#[cfg(emulation_mode = "usermode")]
use libc::c_int;
@ -28,13 +32,28 @@ use crate::{GuestReg, Regs};
pub type GuestAddr = libafl_qemu_sys::target_ulong;
pub type GuestUsize = libafl_qemu_sys::target_ulong;
pub type GuestIsize = libafl_qemu_sys::target_long;
pub type GuestVirtAddr = libafl_qemu_sys::hwaddr;
pub type GuestVirtAddr = libafl_qemu_sys::vaddr;
pub type GuestPhysAddr = libafl_qemu_sys::hwaddr;
pub type GuestHwAddrInfo = libafl_qemu_sys::qemu_plugin_hwaddr;
#[derive(Debug, Clone)]
pub enum GuestAddrKind {
Physical(GuestPhysAddr),
Virtual(GuestVirtAddr),
}
impl fmt::Display for GuestAddrKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GuestAddrKind::Physical(phys_addr) => write!(f, "hwaddr 0x{:x}", phys_addr),
GuestAddrKind::Virtual(virt_addr) => write!(f, "vaddr 0x{:x}", virt_addr),
}
}
}
#[cfg(emulation_mode = "systemmode")]
pub type FastSnapshot = *mut libafl_qemu_sys::syx_snapshot_t;
pub type FastSnapshot = *mut libafl_qemu_sys::SyxSnapshot;
#[cfg(emulation_mode = "systemmode")]
pub enum DeviceSnapshotFilter {
@ -45,16 +64,14 @@ pub enum DeviceSnapshotFilter {
#[cfg(emulation_mode = "systemmode")]
impl DeviceSnapshotFilter {
fn enum_id(&self) -> libafl_qemu_sys::device_snapshot_kind_t {
fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind {
match self {
DeviceSnapshotFilter::All => {
libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALL
}
DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
DeviceSnapshotFilter::AllowList(_) => {
libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALLOWLIST
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST
}
DeviceSnapshotFilter::DenyList(_) => {
libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_DENYLIST
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST
}
}
}
@ -136,9 +153,13 @@ pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
pub use libafl_qemu_sys::{CPUArchState, CPUState};
use crate::sync_backdoor::{SyncBackdoor, SyncBackdoorError};
pub type CPUStatePtr = *mut libafl_qemu_sys::CPUState;
pub type CPUArchStatePtr = *mut libafl_qemu_sys::CPUArchState;
pub type ExitReasonPtr = *mut libafl_qemu_sys::libafl_exit_reason;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)]
#[repr(i32)]
pub enum MmapPerms {
@ -360,6 +381,9 @@ extern "C" {
// CPUState* libafl_qemu_current_cpu(void);
fn libafl_qemu_current_cpu() -> CPUStatePtr;
// struct libafl_exit_reason* libafl_get_exit_reason(void);
fn libafl_get_exit_reason() -> ExitReasonPtr;
fn libafl_qemu_cpu_index(cpu: CPUStatePtr) -> i32;
fn libafl_qemu_write_reg(cpu: CPUStatePtr, reg: i32, val: *const u8) -> i32;
@ -821,7 +845,7 @@ impl CPU {
}
}
static mut EMULATOR_IS_INITIALIZED: bool = false;
static EMULATOR_IS_INITIALIZED: OnceLock<Mutex<bool>> = OnceLock::new();
#[derive(Clone, Debug)]
pub struct Emulator {
@ -835,6 +859,60 @@ pub enum EmuError {
TooManyArgs(usize),
}
#[derive(Debug, Clone)]
pub enum EmuExitReason {
End, // QEMU ended for some reason.
Breakpoint(GuestVirtAddr), // Breakpoint triggered. Contains the virtual address of the trigger.
SyncBackdoor(SyncBackdoor), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
}
impl fmt::Display for EmuExitReason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
EmuExitReason::End => write!(f, "End"),
EmuExitReason::Breakpoint(vaddr) => write!(f, "Breakpoint @vaddr 0x{:x}", vaddr),
EmuExitReason::SyncBackdoor(sync_backdoor) => {
write!(f, "Sync backdoor exit: {}", sync_backdoor)
}
}
}
}
#[derive(Debug, Clone)]
pub enum EmuExitReasonError {
UnknownKind(),
UnexpectedExit,
SyncBackdoorError(SyncBackdoorError),
}
impl From<SyncBackdoorError> for EmuExitReasonError {
fn from(sync_backdoor_error: SyncBackdoorError) -> Self {
EmuExitReasonError::SyncBackdoorError(sync_backdoor_error)
}
}
impl TryFrom<&Emulator> for EmuExitReason {
type Error = EmuExitReasonError;
fn try_from(emu: &Emulator) -> Result<Self, Self::Error> {
let exit_reason = unsafe { libafl_get_exit_reason() };
if exit_reason.is_null() {
Err(EmuExitReasonError::UnexpectedExit)
} else {
let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason =
unsafe { transmute(exit_reason) };
Ok(match exit_reason.kind {
libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe {
EmuExitReason::Breakpoint(exit_reason.data.breakpoint.addr.into())
},
libafl_qemu_sys::libafl_exit_reason_kind_SYNC_BACKDOOR => {
EmuExitReason::SyncBackdoor(emu.try_into()?)
}
_ => return Err(EmuExitReasonError::UnknownKind()),
})
}
}
}
impl std::error::Error for EmuError {}
impl fmt::Display for EmuError {
@ -866,11 +944,15 @@ impl From<EmuError> for libafl::Error {
impl Emulator {
#[allow(clippy::must_use_candidate, clippy::similar_names)]
pub fn new(args: &[String], env: &[(String, String)]) -> Result<Emulator, EmuError> {
unsafe {
if EMULATOR_IS_INITIALIZED {
return Err(EmuError::MultipleInstances);
}
let mut is_initialized = EMULATOR_IS_INITIALIZED
.get_or_init(|| Mutex::new(false))
.lock()
.unwrap();
if *is_initialized {
return Err(EmuError::MultipleInstances);
}
if args.is_empty() {
return Err(EmuError::EmptyArgs);
}
@ -903,7 +985,7 @@ impl Emulator {
libc::atexit(qemu_cleanup_atexit);
libafl_qemu_sys::syx_snapshot_init();
}
EMULATOR_IS_INITIALIZED = true;
*is_initialized = true;
}
Ok(Emulator { _private: () })
}
@ -1047,7 +1129,8 @@ impl Emulator {
pub fn entry_break(&self, addr: GuestAddr) {
self.set_breakpoint(addr);
unsafe {
self.run();
// TODO: decide what to do with sync exit here: ignore or check for bp exit?
let _ = self.run();
}
self.remove_breakpoint(addr);
}
@ -1079,7 +1162,7 @@ impl Emulator {
///
/// Should, in general, be safe to call.
/// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system.
pub unsafe fn run(&self) {
pub unsafe fn run(&self) -> Result<EmuExitReason, EmuExitReasonError> {
#[cfg(emulation_mode = "usermode")]
libafl_qemu_run();
#[cfg(emulation_mode = "systemmode")]
@ -1087,6 +1170,7 @@ impl Emulator {
vm_start();
qemu_main_loop();
}
EmuExitReason::try_from(self)
}
#[cfg(emulation_mode = "usermode")]
@ -1283,9 +1367,9 @@ impl Emulator {
#[must_use]
pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshot {
unsafe {
libafl_qemu_sys::syx_snapshot_create(
libafl_qemu_sys::syx_snapshot_new(
track,
libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALL,
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
null_mut(),
)
}
@ -1300,7 +1384,7 @@ impl Emulator {
) -> FastSnapshot {
let mut v = vec![];
unsafe {
libafl_qemu_sys::syx_snapshot_create(
libafl_qemu_sys::syx_snapshot_new(
track,
device_filter.enum_id(),
device_filter.devices(&mut v),

View File

@ -1,9 +1,12 @@
use std::sync::OnceLock;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -61,6 +64,23 @@ pub enum Regs {
Pktcnthi = 51,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::R0,
SyncBackdoorArgs::Cmd => Regs::R0,
SyncBackdoorArgs::Arg1 => Regs::R1,
SyncBackdoorArgs::Arg2 => Regs::R2,
SyncBackdoorArgs::Arg3 => Regs::R3,
SyncBackdoorArgs::Arg4 => Regs::R4,
SyncBackdoorArgs::Arg5 => Regs::R5,
SyncBackdoorArgs::Arg6 => Regs::R6,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -1,13 +1,14 @@
use std::mem::size_of;
use std::{mem::size_of, sync::OnceLock};
use capstone::arch::BuildsCapstone;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86::*;
use crate::{CallingConvention, GuestAddr};
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention, GuestAddr};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -24,6 +25,23 @@ pub enum Regs {
Eflags = 9,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::Eax,
SyncBackdoorArgs::Cmd => Regs::Eax,
SyncBackdoorArgs::Arg1 => Regs::Edi,
SyncBackdoorArgs::Arg2 => Regs::Esi,
SyncBackdoorArgs::Arg3 => Regs::Edx,
SyncBackdoorArgs::Arg4 => Regs::Ebx,
SyncBackdoorArgs::Arg5 => Regs::Ecx,
SyncBackdoorArgs::Arg6 => Regs::Ebp,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -99,6 +99,8 @@ pub use executor::QemuForkExecutor;
pub mod emu;
pub use emu::*;
pub mod sync_backdoor;
#[must_use]
pub fn filter_qemu_args() -> Vec<String> {
let mut args = vec![env::args().next().unwrap()];

View File

@ -1,10 +1,13 @@
use std::sync::OnceLock;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::mips::*;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -46,6 +49,23 @@ pub enum Regs {
Pc = 37,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::V0,
SyncBackdoorArgs::Cmd => Regs::V0,
SyncBackdoorArgs::Arg1 => Regs::A0,
SyncBackdoorArgs::Arg2 => Regs::A1,
SyncBackdoorArgs::Arg3 => Regs::A2,
SyncBackdoorArgs::Arg4 => Regs::A3,
SyncBackdoorArgs::Arg5 => Regs::T0,
SyncBackdoorArgs::Arg6 => Regs::T1,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -1,10 +1,13 @@
use std::sync::OnceLock;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::powerpc::*;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -85,6 +88,23 @@ pub enum Regs {
Fpscr = 70,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::R3,
SyncBackdoorArgs::Cmd => Regs::R0,
SyncBackdoorArgs::Arg1 => Regs::R3,
SyncBackdoorArgs::Arg2 => Regs::R4,
SyncBackdoorArgs::Arg3 => Regs::R5,
SyncBackdoorArgs::Arg4 => Regs::R6,
SyncBackdoorArgs::Arg5 => Regs::R7,
SyncBackdoorArgs::Arg6 => Regs::R8,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {

View File

@ -0,0 +1,248 @@
use std::{
fmt::{Display, Formatter},
sync::OnceLock,
};
use enum_map::{enum_map, Enum, EnumMap};
use libafl::executors::ExitKind;
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
use crate::{
get_sync_backdoor_arch_regs, Emulator, GuestAddrKind, GuestPhysAddr, GuestReg, GuestVirtAddr,
Regs,
};
#[derive(Debug, Clone)]
pub enum SyncBackdoorError {
UnknownCommand(GuestReg),
RegError(String),
}
impl From<String> for SyncBackdoorError {
fn from(error_string: String) -> Self {
SyncBackdoorError::RegError(error_string)
}
}
#[derive(Debug, Clone, Enum)]
pub enum SyncBackdoorArgs {
Ret,
Cmd,
Arg1,
Arg2,
Arg3,
Arg4,
Arg5,
Arg6,
}
// TODO: Move in a separate header file to have a central definition of native definitions,
// reusable in targets directly.
#[derive(Debug, Clone, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeSyncBackdoorCommand {
Save = 0, // Save the VM
Load = 1, // Reload the target without ending the run?
InputVirt = 2, // The address is a virtual address using the paging currently running in the VM.
InputPhys = 3, // The address is a physical address
End = 4, // Implies reloading of the target. The first argument gives the exit status.
StartVirt = 5, // Shortcut for Save + InputVirt
StartPhys = 6, // Shortcut for Save + InputPhys
}
#[derive(Debug, Clone, Enum, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeExitKind {
Unknown = 0, // Should not be used
Ok = 1, // Normal exit
Crash = 2, // Crash reported in the VM
}
static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
impl From<TryFromPrimitiveError<NativeSyncBackdoorCommand>> for SyncBackdoorError {
fn from(error: TryFromPrimitiveError<NativeSyncBackdoorCommand>) -> Self {
SyncBackdoorError::UnknownCommand(error.number.try_into().unwrap())
}
}
#[derive(Debug, Clone)]
pub struct CommandInput {
addr: GuestAddrKind,
max_input_size: GuestReg,
}
impl CommandInput {
pub fn exec(&self, emu: &Emulator, backdoor: &SyncBackdoor, input: &[u8]) {
match self.addr {
GuestAddrKind::Physical(hwaddr) => unsafe {
#[cfg(emulation_mode = "usermode")]
{
// For now the default behaviour is to fall back to virtual addresses
emu.write_mem(hwaddr.try_into().unwrap(), input)
}
#[cfg(emulation_mode = "systemmode")]
{
emu.write_phys_mem(hwaddr, input)
}
},
GuestAddrKind::Virtual(vaddr) => unsafe {
emu.write_mem(vaddr.try_into().unwrap(), input)
},
};
backdoor.ret(&emu, input.len().try_into().unwrap()).unwrap()
}
}
impl Display for CommandInput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({:x} max nb bytes)", self.addr, self.max_input_size)
}
}
#[derive(Debug, Clone)]
pub enum Command {
Save,
Load,
Input(CommandInput),
Start(CommandInput),
Exit(Option<ExitKind>),
}
impl Display for Command {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Command::Save => write!(f, "Save VM"),
Command::Load => write!(f, "Reload VM"),
Command::Input(command_input) => write!(f, "Set fuzzing input @{}", command_input),
Command::Start(command_input) => {
write!(f, "Start fuzzing with input @{}", command_input)
}
Command::Exit(exit_kind) => write!(f, "Exit of kind {:?}", exit_kind),
}
}
}
#[derive(Debug, Clone)]
pub struct SyncBackdoor {
command: Command,
arch_regs_map: &'static EnumMap<SyncBackdoorArgs, Regs>,
}
impl SyncBackdoor {
pub fn command(&self) -> &Command {
&self.command
}
pub fn ret(&self, emu: &Emulator, value: GuestReg) -> Result<(), SyncBackdoorError> {
Ok(emu.write_reg(self.arch_regs_map[SyncBackdoorArgs::Ret], value)?)
}
}
impl Display for SyncBackdoor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.command)
}
}
impl TryFrom<&Emulator> for SyncBackdoor {
type Error = SyncBackdoorError;
fn try_from(emu: &Emulator) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<SyncBackdoorArgs, Regs> = get_sync_backdoor_arch_regs();
let cmd_id: GuestReg =
emu.read_reg::<Regs, GuestReg>(arch_regs_map[SyncBackdoorArgs::Cmd])?;
Ok(match u64::from(cmd_id).try_into()? {
NativeSyncBackdoorCommand::Save => SyncBackdoor {
command: Command::Save,
arch_regs_map,
},
NativeSyncBackdoorCommand::Load => SyncBackdoor {
command: Command::Load,
arch_regs_map,
},
NativeSyncBackdoorCommand::InputVirt => {
let virt_addr: GuestVirtAddr =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::Input(CommandInput {
addr: GuestAddrKind::Virtual(virt_addr),
max_input_size,
}),
arch_regs_map,
}
}
NativeSyncBackdoorCommand::InputPhys => {
let phys_addr: GuestPhysAddr =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::Input(CommandInput {
addr: GuestAddrKind::Physical(phys_addr),
max_input_size,
}),
arch_regs_map,
}
}
NativeSyncBackdoorCommand::End => {
let native_exit_kind: GuestReg =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?;
let native_exit_kind: Result<NativeExitKind, _> =
u64::from(native_exit_kind).try_into();
let exit_kind = native_exit_kind
.ok()
.map(|k| {
EMU_EXIT_KIND_MAP.get_or_init(|| {
enum_map! {
NativeExitKind::Unknown => None,
NativeExitKind::Ok => Some(ExitKind::Ok),
NativeExitKind::Crash => Some(ExitKind::Crash)
}
})[k]
})
.flatten();
SyncBackdoor {
command: Command::Exit(exit_kind),
arch_regs_map,
}
}
NativeSyncBackdoorCommand::StartPhys => {
let input_phys_addr: GuestPhysAddr =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::Start(CommandInput {
addr: GuestAddrKind::Physical(input_phys_addr),
max_input_size,
}),
arch_regs_map,
}
}
NativeSyncBackdoorCommand::StartVirt => {
let input_virt_addr: GuestVirtAddr =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::Start(CommandInput {
addr: GuestAddrKind::Virtual(input_virt_addr),
max_input_size,
}),
arch_regs_map,
}
}
})
}
}

View File

@ -1,13 +1,14 @@
use std::mem::size_of;
use std::{mem::size_of, sync::OnceLock};
use capstone::arch::BuildsCapstone;
use enum_map::{enum_map, EnumMap};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86_64::*;
use crate::CallingConvention;
use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -32,6 +33,23 @@ pub enum Regs {
Rflags = 17,
}
static SYNC_BACKDOOR_ARCH_REGS: OnceLock<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
enum_map! {
SyncBackdoorArgs::Ret => Regs::Rax,
SyncBackdoorArgs::Cmd => Regs::Rax,
SyncBackdoorArgs::Arg1 => Regs::Rdi,
SyncBackdoorArgs::Arg2 => Regs::Rsi,
SyncBackdoorArgs::Arg3 => Regs::Rdx,
SyncBackdoorArgs::Arg4 => Regs::R10,
SyncBackdoorArgs::Arg5 => Regs::R8,
SyncBackdoorArgs::Arg6 => Regs::R9,
}
})
}
/// alias registers
#[allow(non_upper_case_globals)]
impl Regs {