dyncvoke-spoof 0.1.1

Call stack spoofing primitives for Dyncvoke (synthetic and desync)
Documentation
#![allow(non_snake_case, non_camel_case_types)]

use core::ffi::c_void;

/// Indicates the presence of an exception handler in the function.
pub const UNW_FLAG_EHANDLER: u8 = 0x1;

/// Indicates chained unwind information is present.
pub const UNW_FLAG_CHAININFO: u8 = 0x4;

/// IMAGE_DIRECTORY_ENTRY_EXCEPTION — index into the optional header's
/// DataDirectory[] where the .pdata RUNTIME_FUNCTION table lives.
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3;

/// IMAGE_RUNTIME_FUNCTION_ENTRY — one entry of the .pdata exception table.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ImageRuntimeFunction {
    pub BeginAddress: u32,
    pub EndAddress: u32,
    pub UnwindData: u32,
}

/// IMAGE_DATA_DIRECTORY — VA + size pair used by the optional header.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ImageDataDirectory {
    pub VirtualAddress: u32,
    pub Size: u32,
}

/// Config struct read by the asm trampoline. Layout must match
/// `src/asm/{msvc,gnu}/synthetic.asm` and `desync.asm` byte-for-byte.
/// Field reordering will corrupt argument passing — do not touch.
#[repr(C)]
#[derive(Debug)]
pub struct Config {
    pub rtl_user_addr: *const c_void,
    pub rtl_user_thread_size: u64,
    pub base_thread_addr: *const c_void,
    pub base_thread_size: u64,
    pub first_frame_fp: *const c_void,
    pub second_frame_fp: *const c_void,
    pub jmp_rbx_gadget: *const c_void,
    pub add_rsp_gadget: *const c_void,
    pub first_frame_size: u64,
    pub second_frame_size: u64,
    pub jmp_rbx_frame_size: u64,
    pub add_rsp_frame_size: u64,
    pub rbp_stack_offset: u64,
    pub spoof_function: *const c_void,
    pub return_address: *const c_void,
    pub is_syscall: u32,
    pub ssn: u32,
    pub number_args: u64,
    pub arg01: *const c_void,
    pub arg02: *const c_void,
    pub arg03: *const c_void,
    pub arg04: *const c_void,
    pub arg05: *const c_void,
    pub arg06: *const c_void,
    pub arg07: *const c_void,
    pub arg08: *const c_void,
    pub arg09: *const c_void,
    pub arg10: *const c_void,
    pub arg11: *const c_void,
}

impl Default for Config {
    fn default() -> Self {
        unsafe { core::mem::zeroed() }
    }
}

/// x86_64 general-purpose register encoding used by unwind codes.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
#[allow(dead_code)]
pub enum Registers {
    Rax = 0,
    Rcx,
    Rdx,
    Rbx,
    Rsp,
    Rbp,
    Rsi,
    Rdi,
    R8,
    R9,
    R10,
    R11,
    R12,
    R13,
    R14,
    R15,
}

impl PartialEq<usize> for Registers {
    fn eq(&self, other: &usize) -> bool {
        *self as usize == *other
    }
}

/// One unwind code slot. The full union: either a packed 16-bit slot
/// (CodeOffset/UnwindOp/OpInfo) or a raw FrameOffset for following slots.
#[repr(C)]
pub union UnwindCode {
    pub FrameOffset: u16,
    pub Anonymous: UnwindCode0,
}

bitfield::bitfield! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug)]
    pub struct UnwindCode0(u16);
    pub u8, CodeOffset, SetCodeOffset: 7, 0;
    pub u8, UnwindOp, SetUnwindOp: 11, 8;
    pub u8, OpInfo, SetOpInfo: 15, 12;
}

/// Optional Exception/ChainInfo slot in UNWIND_INFO.
#[repr(C)]
pub union UnwindInfo0 {
    pub ExceptionHandler: u32,
    pub FunctionEntry: u32,
}

bitfield::bitfield! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug)]
    pub struct UnwindVersionFlags(u8);
    pub u8, Version, SetVersion: 2, 0;
    pub u8, Flags, SetFlags: 7, 3;
}

bitfield::bitfield! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug)]
    pub struct UnwindFrameInfo(u8);
    pub u8, FrameRegister, SetFrameRegister: 3, 0;
    pub u8, FrameOffset, SetFrameOffset: 7, 4;
}

/// UNWIND_INFO header at the front of every function's unwind record.
#[repr(C)]
pub struct UnwindInfo {
    pub VersionFlags: UnwindVersionFlags,
    pub SizeOfProlog: u8,
    pub CountOfCodes: u8,
    pub FrameInfo: UnwindFrameInfo,
    pub UnwindCode: UnwindCode,
    pub Anonymous: UnwindInfo0,
    pub ExceptionData: u32,
}

/// x64 unwind operation codes (subset we care about for stack-size
/// calculation). MS docs: learn.microsoft.com/cpp/build/exception-handling-x64
#[repr(u8)]
#[allow(dead_code)]
pub enum UnwindOpCode {
    UWOP_PUSH_NONVOL = 0,
    UWOP_ALLOC_LARGE = 1,
    UWOP_ALLOC_SMALL = 2,
    UWOP_SET_FPREG = 3,
    UWOP_SAVE_NONVOL = 4,
    UWOP_SAVE_NONVOL_BIG = 5,
    UWOP_EPILOG = 6,
    UWOP_SPARE_CODE = 7,
    UWOP_SAVE_XMM128 = 8,
    UWOP_SAVE_XMM128BIG = 9,
    UWOP_PUSH_MACH_FRAME = 10,
}

impl TryFrom<u8> for UnwindOpCode {
    type Error = ();

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0..=10 => Ok(unsafe { core::mem::transmute::<u8, UnwindOpCode>(value) }),
            _ => Err(()),
        }
    }
}