libnyx_fuzz_runner 0.1.3

libnyx fork only for publishing
Documentation

use nix::sys::mman::*;
use std::fs::File;
use std::os::unix::io::IntoRawFd;
use std::fmt;
//use std::sync::atomic::compiler_fence;
//use std::sync::atomic::Ordering;

use crate::nyx::mem_barrier::mem_barrier;


use derivative::Derivative;

/* various Nyx exec codes (aux_buffer.result.exec_result_code) */
pub const NYX_SUCCESS: u8       = 0;
pub const NYX_CRASH: u8         = 1;
pub const NYX_HPRINTF: u8       = 2;
pub const NYX_TIMEOUT: u8       = 3;
pub const NYX_INPUT_WRITE: u8   = 4;
pub const NYX_ABORT: u8         = 5;


pub const AUX_BUFFER_SIZE: usize = 4096;

const AUX_MAGIC: u64 = 0x54502d554d4551_u64;
const QEMU_PT_VERSION: u16 = 3; /* let's start at 1 for the initial version using the aux buffer */
const QEMU_PT_HASH: u16 = 84;

const HEADER_SIZE: usize = 128;
const CAP_SIZE: usize = 256;
const CONFIG_SIZE: usize = 512;
const STATE_SIZE: usize = 512;
//const MISC_SIZE: usize = 4096 - (HEADER_SIZE + CAP_SIZE + CONFIG_SIZE + STATE_SIZE);

const HEADER_OFFSET: usize = 0;
const CAP_OFFSET: usize = HEADER_OFFSET + HEADER_SIZE;
const CONFIG_OFFSET: usize = CAP_OFFSET + CAP_SIZE;
const STATE_OFFSET: usize = CONFIG_OFFSET + CONFIG_SIZE;
const MISC_OFFSET: usize = STATE_OFFSET + STATE_SIZE;
const MISC_SIZE: usize = AUX_BUFFER_SIZE - MISC_OFFSET;

#[derive(Debug)]
pub struct AuxBuffer {
    pub header: &'static mut auxilary_buffer_header_s,
    pub cap: &'static mut auxilary_buffer_cap_s,
    pub config: &'static mut auxilary_buffer_config_s,
    pub result: &'static mut auxilary_buffer_result_s,
    pub misc: &'static mut auxilary_buffer_misc_s,
    size: usize, /* total size of the aux buffer */
}

impl AuxBuffer {

    pub fn new_readonly(file: File, read_only: bool, size: usize) -> Self {

        let mut prot = ProtFlags::PROT_READ;
        if !read_only{
            prot |=  ProtFlags::PROT_WRITE;
        }

        let flags = MapFlags::MAP_SHARED;
        let null_addr = std::num::NonZeroUsize::new(0);
        let aux_buffer_alloc_size = std::num::NonZeroUsize::new(size).unwrap();
        unsafe {
            let ptr = mmap(null_addr, aux_buffer_alloc_size, prot, flags, file.into_raw_fd(), 0).unwrap();
            let header = (ptr.add(HEADER_OFFSET) as *mut auxilary_buffer_header_s)
                .as_mut()
                .unwrap();
            let cap = (ptr.add(CAP_OFFSET) as *mut auxilary_buffer_cap_s)
                .as_mut()
                .unwrap();
            let config = (ptr.add(CONFIG_OFFSET) as *mut auxilary_buffer_config_s)
                .as_mut()
                .unwrap();
            let result = (ptr.add(STATE_OFFSET) as *mut auxilary_buffer_result_s)
                .as_mut()
                .unwrap();
            let misc = (ptr.add(MISC_OFFSET) as *mut auxilary_buffer_misc_s)
                .as_mut()
                .unwrap();
            return Self {
                header,
                cap,
                config,
                result,
                misc,
                size,
            };
        }
    }

    pub fn new(file: File, size: usize) -> Self {
        return AuxBuffer::new_readonly(file, false, size);
    }

    pub fn size(&self) -> usize {
        return self.size;
    }

    /* This is a somewhat hacky way of returning a slice of the total misc area */
    pub fn misc_slice(&self) -> &[u8] {
        return unsafe { std::slice::from_raw_parts(self.misc as *const auxilary_buffer_misc_s as *const u8, self.size - MISC_OFFSET) };
    }

    /* same... */
    pub fn misc_data_slice(&self) -> &[u8] {
        return unsafe { std::slice::from_raw_parts((self.misc as *const auxilary_buffer_misc_s as *const u8).offset(std::mem::size_of::<u16>() as isize), self.size - MISC_OFFSET - std::mem::size_of::<u16>()) };
    }

    pub fn validate_header(&self) -> Result<(), String> {
        mem_barrier();
        let mgc = self.header.magic;
        if mgc != AUX_MAGIC {
            return Err(format!("aux buffer magic mismatch {} != {}...\n[!] Probably the AUX buffer is corrupted?!", AUX_MAGIC, mgc));
        }
        let version = self.header.version;
        if version != QEMU_PT_VERSION {
            return Err(format!("aux buffer version mismatch {} != {}...\n[!] You are probably using either an outdated version of libnyx or QEMU-Nyx...", QEMU_PT_VERSION, version));
        }
        let hash = self.header.hash;
        if hash != QEMU_PT_HASH {
            return Err(format!("aux buffer hash mismatch {} != {}...\n[!] You are probably using either an outdated version of libnyx or QEMU-Nyx...", QEMU_PT_HASH, hash));
        }
        Ok(())
    }
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_header_s {
    pub magic: u64, /* 0x54502d554d4551 */
    pub version: u16,
    pub hash: u16,
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_cap_s {
    pub redqueen: u8,
    pub agent_timeout_detection: u8,    /* agent implements own timeout detection; host timeout detection is still in used, but treshold is increased by x2; */
    pub agent_trace_bitmap: u8,         /* agent implements own tracing mechanism; PT tracing is disabled */
    pub agent_ijon_trace_bitmap: u8,    /* agent uses the ijon shm buffer */

    pub agent_input_buffer_size: u32,    /* agent requests a custom input buffer size (if the size is 0, the minimum buffer size is used) */
    pub agent_coverage_bitmap_size: u32,    /* agent requests a custom coverage bitmap size (if the size is 0, the minimum buffer size is used) */

}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_config_s {
    pub changed: u8, /* set this byte to kick in a rescan of this buffer */

    pub timeout_sec: u8,
    pub timeout_usec: u32,

    /* trigger to enable / disable different QEMU-PT modes */
    pub redqueen_mode: u8,
    pub trace_mode: u8,
    pub reload_mode: u8,
    pub verbose_level: u8,
    pub page_dump_mode: u8,
    pub page_addr: u64,
    /*  0 -> disabled
        1 -> decoding
        2 -> decoding + full disassembling
    */
    //uint8_t pt_processing_mode;
    pub protect_payload_buffer: u8,
      /* snapshot extension */
    pub discard_tmp_snapshot: u8,
}

#[derive(Derivative)]
#[derivative(Debug)]
#[derive(Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_result_s {
    /*  0 -> booting,
        1 -> loader level 1,
        2 -> loader level 2,
        3 -> ready to fuzz
    */
    pub state: u8,
    pub exec_done: u8,
    pub exec_result_code: u8,
    pub reloaded: u8,

    pub pt_overflow: u8,
    pub page_not_found: u8,
    pub tmp_snapshot_created: u8,
    #[derivative(Debug="ignore")]
    pub padding_3: u8,

    pub page_not_found_addr: u64,
    pub dirty_pages: u32,
    pub pt_trace_size: u32,
    pub bb_coverage: u32,
    pub runtime_usec: u32,
    pub runtime_sec: u32,

}

#[repr(C, packed(1))]
pub struct auxilary_buffer_misc_s {
    pub len: u16,
    pub data: [u8;MISC_SIZE-2],
}

fn inspect_bytes(bs: &[u8]) -> String {
    use std::ascii::escape_default;
    use std::str;

    let mut visible = String::new();
    for &b in bs {
        let part: Vec<u8> = escape_default(b).collect();
        visible.push_str(str::from_utf8(&part).unwrap());
    }
    visible
}

impl auxilary_buffer_misc_s{
    pub fn as_slice(&self) -> &[u8]{
        assert!(self.len as usize <= self.data.len());
        return &self.data[0..self.len as usize];
    }
    pub fn as_string(&self) -> String{
        inspect_bytes(self.as_slice())
    }
}

/* FIXME: will fail if the aux_buffer is > 4KB in size */
impl fmt::Debug for auxilary_buffer_misc_s {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("auxilary_buffer_misc_s")
         .field("data", &inspect_bytes(self.as_slice()))
         .finish()
    }
}