use vmi_arch_amd64::Registers;
use vmi_core::{Va, VmiError, VmiState, driver::VmiRead};
use zerocopy::{FromBytes, IntoBytes};
use super::{Frame, Unwinder, Unwound};
use crate::{
ArchAdapter, WindowsOs,
pe::{ImageRuntimeFunctionEntry, PeImage},
};
pub const UNWIND_CHAIN_LIMIT: u32 = 32;
pub const UWOP_PUSH_NONVOL: u8 = 0;
pub const UWOP_ALLOC_LARGE: u8 = 1;
pub const UWOP_ALLOC_SMALL: u8 = 2;
pub const UWOP_SET_FPREG: u8 = 3;
pub const UWOP_SAVE_NONVOL: u8 = 4;
pub const UWOP_SAVE_NONVOL_FAR: u8 = 5;
pub const UWOP_EPILOG: u8 = 6;
pub const UWOP_SPARE_CODE: u8 = 7;
pub const UWOP_SAVE_XMM128: u8 = 8;
pub const UWOP_SAVE_XMM128_FAR: u8 = 9;
pub const UWOP_PUSH_MACHFRAME: u8 = 10;
pub const UNW_FLAG_NHANDLER: u8 = 0x0;
pub const UNW_FLAG_EHANDLER: u8 = 0x1;
pub const UNW_FLAG_UHANDLER: u8 = 0x2;
pub const UNW_FLAG_CHAININFO: u8 = 0x4;
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes)]
pub struct UNWIND_INFO {
pub VersionAndFlags: u8,
pub SizeOfProlog: u8,
pub CountOfCodes: u8,
pub FrameRegisterAndOffset: u8,
}
impl UNWIND_INFO {
pub fn version(&self) -> u8 {
self.VersionAndFlags & 0x07
}
pub fn flags(&self) -> u8 {
(self.VersionAndFlags >> 3) & 0x1f
}
pub fn size_of_prolog(&self) -> u8 {
self.SizeOfProlog
}
pub fn count_of_codes(&self) -> u8 {
self.CountOfCodes
}
pub fn frame_register(&self) -> u8 {
self.FrameRegisterAndOffset & 0x0f
}
pub fn frame_offset(&self) -> u8 {
(self.FrameRegisterAndOffset >> 4) & 0x0f
}
pub fn codes_end_offset(&self) -> u32 {
let aligned = self.CountOfCodes as u32 + (self.CountOfCodes as u32 & 1);
4 + aligned * 2
}
}
pub const REG_RAX: u8 = 0;
pub const REG_RCX: u8 = 1;
pub const REG_RDX: u8 = 2;
pub const REG_RBX: u8 = 3;
pub const REG_RSP: u8 = 4;
pub const REG_RBP: u8 = 5;
pub const REG_RSI: u8 = 6;
pub const REG_RDI: u8 = 7;
pub const REG_R8: u8 = 8;
pub const REG_R9: u8 = 9;
pub const REG_R10: u8 = 10;
pub const REG_R11: u8 = 11;
pub const REG_R12: u8 = 12;
pub const REG_R13: u8 = 13;
pub const REG_R14: u8 = 14;
pub const REG_R15: u8 = 15;
#[derive(Debug, Clone)]
pub struct UnwindContextAmd64 {
pub rip: u64,
pub rsp: u64,
pub rbp: u64,
pub rbx: u64,
pub rsi: u64,
pub rdi: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
}
impl From<&Registers> for UnwindContextAmd64 {
fn from(value: &Registers) -> Self {
Self {
rip: value.rip,
rsp: value.rsp,
rbp: value.rbp,
rbx: value.rbx,
rsi: value.rsi,
rdi: value.rdi,
r12: value.r12,
r13: value.r13,
r14: value.r14,
r15: value.r15,
}
}
}
impl UnwindContextAmd64 {
pub fn get_register(&self, reg: u8) -> u64 {
match reg {
REG_RBX => self.rbx,
REG_RBP => self.rbp,
REG_RSI => self.rsi,
REG_RDI => self.rdi,
REG_R12 => self.r12,
REG_R13 => self.r13,
REG_R14 => self.r14,
REG_R15 => self.r15,
_ => 0,
}
}
pub fn set_register(&mut self, reg: u8, value: u64) {
match reg {
REG_RBX => self.rbx = value,
REG_RBP => self.rbp = value,
REG_RSI => self.rsi = value,
REG_RDI => self.rdi = value,
REG_R12 => self.r12 = value,
REG_R13 => self.r13 = value,
REG_R14 => self.r14 = value,
REG_R15 => self.r15 = value,
_ => {} }
}
}
pub struct UnwinderAmd64;
impl<Driver> Unwinder<Driver> for UnwinderAmd64
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
type Context = UnwindContextAmd64;
fn unwind(
&self,
vmi: &VmiState<WindowsOs<Driver>>,
image_base: Va,
image: &impl PeImage,
context: &mut UnwindContextAmd64,
) -> Result<Unwound, VmiError> {
let rva = context.rip.saturating_sub(image_base.0) as u32;
let exception_dir = image.exception_directory()?;
let runtime_function = exception_dir.as_ref().and_then(|dir| dir.find(rva));
let entry = match runtime_function {
Some(entry) => entry,
None => {
return unwind_leaf(vmi, context);
}
};
let begin_address = entry.begin_address;
let unwind_data_rva = entry.unwind_info_address_or_data;
let mut unwind_rva = unwind_data_rva;
let rip_offset = rva.saturating_sub(begin_address);
let mut is_first = true;
let mut machine_frame = false;
let mut chain_count = 0u32;
loop {
let header = image.read_struct_at_rva::<UNWIND_INFO>(unwind_rva)?;
let flags = header.flags();
let size_of_prolog = header.size_of_prolog();
let count_of_codes = header.count_of_codes() as usize;
let frame_register = header.frame_register();
let frame_offset = header.frame_offset();
let codes_size = count_of_codes * 2;
let mut codes_data = vec![0u8; codes_size];
if codes_size > 0 {
image.read_at_rva(unwind_rva + 4, &mut codes_data)?;
}
let frame_base = if frame_register != 0 {
context
.get_register(frame_register)
.wrapping_sub((frame_offset as u64) * 16)
}
else {
context.rsp
};
if frame_register != 0 && (!is_first || rip_offset >= size_of_prolog as u32) {
context.rsp = frame_base;
}
let mut slot = 0;
while slot < count_of_codes {
let code_offset = codes_data[slot * 2];
let op_info = codes_data[slot * 2 + 1];
let op = op_info & 0x0f;
let info = (op_info >> 4) & 0x0f;
if is_first
&& (rip_offset < size_of_prolog as u32)
&& (code_offset as u32 > rip_offset)
{
slot += slots_for_op(op, info);
continue;
}
match op {
UWOP_PUSH_NONVOL => {
let value = vmi.read_u64(Va(context.rsp))?;
context.set_register(info, value);
context.rsp += 8;
slot += 1;
}
UWOP_ALLOC_LARGE => {
if info == 0 {
let alloc = read_u16_from_codes(&codes_data, slot + 1) as u64 * 8;
context.rsp += alloc;
slot += 2;
}
else {
let alloc = read_u32_from_codes(&codes_data, slot + 1) as u64;
context.rsp += alloc;
slot += 3;
}
}
UWOP_ALLOC_SMALL => {
context.rsp += info as u64 * 8 + 8;
slot += 1;
}
UWOP_SET_FPREG => {
context.rsp = frame_base;
slot += 1;
}
UWOP_SAVE_NONVOL => {
let offset = read_u16_from_codes(&codes_data, slot + 1) as u64 * 8;
let value = vmi.read_u64(Va(frame_base.wrapping_add(offset)))?;
context.set_register(info, value);
slot += 2;
}
UWOP_SAVE_NONVOL_FAR => {
let offset = read_u32_from_codes(&codes_data, slot + 1) as u64;
let value = vmi.read_u64(Va(frame_base.wrapping_add(offset)))?;
context.set_register(info, value);
slot += 3;
}
UWOP_EPILOG => {
slot += 2;
}
UWOP_SPARE_CODE => {
slot += 3;
}
UWOP_SAVE_XMM128 => {
slot += 2;
}
UWOP_SAVE_XMM128_FAR => {
slot += 3;
}
UWOP_PUSH_MACHFRAME => {
if info == 1 {
context.rsp += 8; }
let new_rip = vmi.read_u64(Va(context.rsp))?;
let new_rsp = vmi.read_u64(Va(context.rsp + 24))?;
context.rip = new_rip;
context.rsp = new_rsp;
machine_frame = true;
slot += 1;
}
_ => {
tracing::warn!(op, info, "unknown unwind opcode");
slot += 1;
}
}
}
if flags & UNW_FLAG_CHAININFO == 0 {
break;
}
let chain_rva = unwind_rva + header.codes_end_offset();
let mut chain_buf = [0u8; 12];
image.read_at_rva(chain_rva, &mut chain_buf)?;
unwind_rva = u32::from_le_bytes(chain_buf[8..12].try_into().unwrap());
is_first = false;
chain_count += 1;
if chain_count > UNWIND_CHAIN_LIMIT {
tracing::warn!("unwind chain limit exceeded");
break;
}
}
if machine_frame {
if context.rip == 0 {
return Ok(Unwound::MachineEnd);
}
let params = read_params(vmi, Va(context.rsp));
return Ok(Unwound::Frame(Frame {
instruction_pointer: Va(context.rip),
stack_pointer: Va(context.rsp),
params,
machine_frame: true,
}));
}
let return_addr = vmi.read_u64(Va(context.rsp))?;
context.rsp += 8;
if return_addr == 0 {
return Ok(Unwound::End);
}
context.rip = return_addr;
let params = read_params(vmi, Va(context.rsp));
Ok(Unwound::Frame(Frame {
instruction_pointer: Va(return_addr),
stack_pointer: Va(context.rsp),
params,
machine_frame: false,
}))
}
}
pub fn read_params<Driver>(vmi: &VmiState<WindowsOs<Driver>>, rsp: Va) -> [u64; 4]
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
[
vmi.read_u64(rsp).unwrap_or(0),
vmi.read_u64(rsp + 8).unwrap_or(0),
vmi.read_u64(rsp + 16).unwrap_or(0),
vmi.read_u64(rsp + 24).unwrap_or(0),
]
}
pub fn unwind_leaf<Driver>(
vmi: &VmiState<WindowsOs<Driver>>,
context: &mut UnwindContextAmd64,
) -> Result<Unwound, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
let return_addr = vmi.read_u64(Va(context.rsp))?;
context.rsp += 8;
if return_addr == 0 {
return Ok(Unwound::End);
}
context.rip = return_addr;
let params = read_params(vmi, Va(context.rsp));
Ok(Unwound::Frame(Frame {
instruction_pointer: Va(return_addr),
stack_pointer: Va(context.rsp),
params,
machine_frame: false,
}))
}
pub fn resolve_primary_function(image: &impl PeImage, rva: u32) -> Result<Option<u32>, VmiError> {
let exception_dir = match image.exception_directory()? {
Some(exception_dir) => exception_dir,
None => return Ok(None),
};
let entry = match exception_dir.find(rva) {
Some(entry) => entry,
None => return Ok(None),
};
let mut begin_address = entry.begin_address;
let mut unwind_rva = entry.unwind_info_address_or_data;
for _ in 0..UNWIND_CHAIN_LIMIT {
let header = image.read_struct_at_rva::<UNWIND_INFO>(unwind_rva)?;
if header.flags() & UNW_FLAG_CHAININFO == 0 {
break;
}
let entry = image.read_struct_at_rva::<ImageRuntimeFunctionEntry>(
unwind_rva + header.codes_end_offset(),
)?;
begin_address = entry.begin_address;
unwind_rva = entry.unwind_info_address_or_data;
}
Ok(Some(begin_address))
}
fn slots_for_op(op: u8, info: u8) -> usize {
match op {
UWOP_PUSH_NONVOL => 1,
UWOP_ALLOC_LARGE => {
if info == 0 {
2
}
else {
3
}
}
UWOP_ALLOC_SMALL => 1,
UWOP_SET_FPREG => 1,
UWOP_SAVE_NONVOL => 2,
UWOP_SAVE_NONVOL_FAR => 3,
UWOP_SPARE_CODE => 3,
UWOP_SAVE_XMM128 => 2,
UWOP_SAVE_XMM128_FAR => 3,
UWOP_PUSH_MACHFRAME => 1,
_ => 1,
}
}
fn read_u16_from_codes(data: &[u8], slot: usize) -> u16 {
let offset = slot * 2;
if offset + 2 <= data.len() {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
else {
0
}
}
fn read_u32_from_codes(data: &[u8], slot: usize) -> u32 {
let offset = slot * 2;
if offset + 4 <= data.len() {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
else {
0
}
}