diff --git a/src/vmm/cpuid.rs b/src/vmm/cpuid.rs index 1d1584c..abc5445 100644 --- a/src/vmm/cpuid.rs +++ b/src/vmm/cpuid.rs @@ -6,7 +6,12 @@ use super::{vcpu::VCpu, vmcs::VmxLeaf}; pub fn handle_cpuid_exit(vcpu: &mut VCpu) { let regs = &mut vcpu.guest_registers; - let vendor: &[u8; 12] = b"miHypervisor"; + let vendor: &[u8; 12] = if vcpu.emulate_amd { + b"AuthenticAMD" + } else { + b"miHypervisor" + }; + let brand_string: &[u8; 48] = b"mii Hypervisor CPU on Intel VT-x \0"; let vendor = unsafe { core::mem::transmute::<&[u8; 12], &[u32; 3]>(vendor) }; diff --git a/src/vmm/emulation/mod.rs b/src/vmm/emulation/mod.rs new file mode 100644 index 0000000..ba577aa --- /dev/null +++ b/src/vmm/emulation/mod.rs @@ -0,0 +1 @@ +pub mod opcode; diff --git a/src/vmm/emulation/opcode.rs b/src/vmm/emulation/opcode.rs new file mode 100644 index 0000000..6c4c6be --- /dev/null +++ b/src/vmm/emulation/opcode.rs @@ -0,0 +1,174 @@ +use crate::{ + info, + vmm::{ + vcpu::VCpu, + vmcs::{DescriptorType, EntryControls, Granularity, SegmentRights}, + }, +}; +use x86::current::vmx::{vmread, vmwrite}; +use x86::vmx::vmcs; + +const OPCODE_TWO_BYTE_ESCAPE: u8 = 0x0F; +const OPCODE_GROUP_7: u8 = 0x01; +const OPCODE_CLAC: u8 = 0xCA; +const OPCODE_STAC: u8 = 0xCB; +const OPCODE_SYSCALL: u8 = 0x05; + +const RFLAGS_AC_BIT: u64 = 1 << 18; + +pub struct OpcodeEmulator { + pub original_opcode: Option<[u8; 16]>, + pub replaced_address: Option, +} + +impl OpcodeEmulator { + pub fn new() -> Self { + OpcodeEmulator { + original_opcode: None, + replaced_address: None, + } + } +} + +pub fn emulate_opcode(vcpu: &mut VCpu, instruction_bytes: [u8; 16], valid_bytes: u64) -> bool { + if instruction_bytes[0] != OPCODE_TWO_BYTE_ESCAPE || valid_bytes < 2 { + return false; + } + + match instruction_bytes[1] { + OPCODE_SYSCALL => { + return emulate_syscall(vcpu); + } + _ => {} + } + + match (instruction_bytes[1], instruction_bytes[2]) { + (OPCODE_GROUP_7, OPCODE_CLAC) => emulate_clac(vcpu), + (OPCODE_GROUP_7, OPCODE_STAC) => emulate_stac(vcpu), + _ => false, + } +} + +fn emulate_syscall(vcpu: &mut VCpu) -> bool { + if !vcpu.emulate_amd { + return false; + } + + let current_rip = unsafe { vmread(vmcs::guest::RIP).unwrap() }; + let return_address = current_rip + 2; + + let rflags = unsafe { vmread(vmcs::guest::RFLAGS).unwrap() }; + + vcpu.guest_registers.rcx = return_address; + vcpu.guest_registers.r11 = rflags; + + let lstar = vcpu.guest_msr.find(0xc0000082).unwrap().data; + let star = vcpu.guest_msr.find(0xc0000081).unwrap().data; + let sfmask = vcpu.guest_msr.find(0xc0000084).unwrap().data; + + let cs_selector = (star >> 32) as u16; + let ss_selector = cs_selector + 8; + + let cs_rights = { + let mut rights = SegmentRights::default(); + rights.set_rw(true); + rights.set_dc(false); + rights.set_executable(true); + rights.set_desc_type_raw(DescriptorType::Code as u8); + rights.set_dpl(0); + rights.set_granularity_raw(Granularity::KByte as u8); + rights.set_long(true); + rights.set_db(false); + rights + }; + + let ss_rights = { + let mut rights = SegmentRights::default(); + rights.set_rw(true); + rights.set_dc(false); + rights.set_executable(false); + rights.set_desc_type_raw(DescriptorType::Code as u8); + rights.set_dpl(0); + rights.set_granularity_raw(Granularity::KByte as u8); + rights.set_long(false); + rights.set_db(true); + rights + }; + + info!("Setting RIP:{:x} to {:x}", current_rip, lstar); + unsafe { + // Set segment registers for kernel mode + vmwrite(vmcs::guest::RIP, lstar).unwrap(); + vmwrite(vmcs::guest::CS_SELECTOR, cs_selector as u64).unwrap(); + vmwrite(vmcs::guest::SS_SELECTOR, ss_selector as u64).unwrap(); + vmwrite(vmcs::guest::CS_BASE, 0).unwrap(); + vmwrite(vmcs::guest::SS_BASE, 0).unwrap(); + vmwrite(vmcs::guest::CS_LIMIT, 0xFFFFFFFF).unwrap(); + vmwrite(vmcs::guest::SS_LIMIT, 0xFFFFFFFF).unwrap(); + vmwrite(vmcs::guest::CS_ACCESS_RIGHTS, cs_rights.0 as u64).unwrap(); + vmwrite(vmcs::guest::SS_ACCESS_RIGHTS, ss_rights.0 as u64).unwrap(); + + // Set CR0 and CR4 for Long Mode + let mut cr0 = vmread(vmcs::guest::CR0).unwrap(); + cr0 |= (1 << 31) | (1 << 0); // Set PG (Paging) and PE (Protection Enable) + vmwrite(vmcs::guest::CR0, cr0).unwrap(); + + let mut cr4 = vmread(vmcs::guest::CR4).unwrap(); + cr4 |= 1 << 5; // Set PAE (Physical Address Extension) + vmwrite(vmcs::guest::CR4, cr4).unwrap(); + + // Set EFER for Long Mode + let mut efer = vmread(vmcs::guest::IA32_EFER_FULL).unwrap(); + efer |= (1 << 8) | (1 << 10); // Set LME (Long Mode Enable) and LMA (Long Mode Active) + vmwrite(vmcs::guest::IA32_EFER_FULL, efer).unwrap(); + + // Set VM-Entry controls for Long Mode + let mut entry_ctrls = EntryControls::read(); + entry_ctrls.set_ia32e_mode_guest(true); + entry_ctrls.write(); + } + + let new_rflags = rflags & !sfmask; + unsafe { + vmwrite(vmcs::guest::RFLAGS, new_rflags).unwrap(); + } + + return true; +} + +fn emulate_clac(vcpu: &mut VCpu) -> bool { + if let Err(_) = modify_rflags_ac(false) { + return false; + } + + if let Err(_) = vcpu.step_next_inst() { + return false; + } + + true +} + +fn emulate_stac(vcpu: &mut VCpu) -> bool { + if let Err(_) = modify_rflags_ac(true) { + return false; + } + + if let Err(_) = vcpu.step_next_inst() { + return false; + } + + true +} + +fn modify_rflags_ac(set: bool) -> x86::vmx::Result<()> { + unsafe { + let rflags = vmread(vmcs::guest::RFLAGS)?; + let new_rflags = if set { + rflags | RFLAGS_AC_BIT + } else { + rflags & !RFLAGS_AC_BIT + }; + vmwrite(vmcs::guest::RFLAGS, new_rflags)?; + } + Ok(()) +} diff --git a/src/vmm/mod.rs b/src/vmm/mod.rs index 780a419..f999bf6 100644 --- a/src/vmm/mod.rs +++ b/src/vmm/mod.rs @@ -1,6 +1,7 @@ pub mod asm; pub mod cpuid; pub mod cr; +pub mod emulation; pub mod ept; pub mod error; pub mod fpu; diff --git a/src/vmm/msr.rs b/src/vmm/msr.rs index 0216139..79be6db 100644 --- a/src/vmm/msr.rs +++ b/src/vmm/msr.rs @@ -133,6 +133,13 @@ impl ShadowMsr { } x86::msr::IA32_KERNEL_GSBASE => Self::shadow_read(vcpu, msr_kind), 0x1b => Self::shadow_read(vcpu, msr_kind), + 0x8b => Self::set_ret_val(vcpu, 0x8701021), + 0xc0011029 => Self::set_ret_val(vcpu, 0x3000310e08202), + 0xc0010000 => Self::set_ret_val(vcpu, 0x130076), + 0xc0010001 => Self::set_ret_val(vcpu, 0), + 0xc0010002 => Self::set_ret_val(vcpu, 0), + 0xc0010003 => Self::set_ret_val(vcpu, 0), + 0xc0010007 => Self::set_ret_val(vcpu, 0), _ => { panic!("Unhandled RDMSR: {:#x}", msr_kind); } @@ -172,6 +179,7 @@ impl ShadowMsr { x86::msr::IA32_FS_BASE => unsafe { vmwrite(vmcs::guest::FS_BASE, value).unwrap() }, x86::msr::IA32_GS_BASE => unsafe { vmwrite(vmcs::guest::GS_BASE, value).unwrap() }, 0x1b => Self::shadow_write(vcpu, msr_kind), + 0xc0010007 => Self::shadow_write(vcpu, msr_kind), _ => { panic!("Unhandled WRMSR: {:#x}", msr_kind); diff --git a/src/vmm/vcpu.rs b/src/vmm/vcpu.rs index aae4626..4ab523b 100644 --- a/src/vmm/vcpu.rs +++ b/src/vmm/vcpu.rs @@ -26,7 +26,9 @@ use crate::{ memory::BootInfoFrameAllocator, subscribe_with_context, vmm::{ - cpuid, cr, fpu, + cpuid, cr, + emulation::opcode::{emulate_opcode, OpcodeEmulator}, + fpu, io::{self, InitPhase, Serial, PIC}, msr, qual::{QualCr, QualIo}, @@ -49,8 +51,10 @@ use super::{ }; const SIZE_2MIB: u64 = 2 * 1024 * 1024; +const GUEST_MEMORY_SIZE: u64 = 2 * 1024 * 1024 * 1024; -static EPT_FRAME_ALLOCATOR: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); +static EPT_FRAME_ALLOCATOR: AtomicPtr = + AtomicPtr::new(core::ptr::null_mut()); #[repr(C)] pub struct VCpu { @@ -71,6 +75,8 @@ pub struct VCpu { pub io_bitmap_b: x86_64::structures::paging::PhysFrame, pub pic: PIC, pub pending_irq: u16, + pub opcode_emulator: OpcodeEmulator, + pub emulate_amd: bool, } const TEMP_STACK_SIZE: usize = 4096; @@ -184,6 +190,8 @@ impl VCpu { io_bitmap_b, pic: PIC::new(), pending_irq: 0, + opcode_emulator: OpcodeEmulator::new(), + emulate_amd: false, } } @@ -193,7 +201,7 @@ impl VCpu { mapper: &OffsetPageTable<'static>, ) { EPT_FRAME_ALLOCATOR.store(frame_allocator as *mut _, Ordering::Release); - + self.vmxon.activate_vmxon().unwrap(); let revision_id = unsafe { rdmsr(x86::msr::IA32_VMX_BASIC) } as u32; @@ -241,7 +249,7 @@ impl VCpu { let cmdline_start = linux::LAYOUT_CMDLINE as u64; let cmdline_end = cmdline_start + cmdline_max_size as u64; - + let cmdline_bytes = b"console=ttyS0 earlyprintk=serial nokaslr\0"; self.load_image(cmdline_bytes, cmdline_start as usize); @@ -272,21 +280,23 @@ impl VCpu { pub fn load_image(&mut self, image: &[u8], addr: usize) { info!("Loading image at {:#x}, size: {} bytes", addr, image.len()); - + let start_page = addr & !0xFFF; let end_page = ((addr + image.len() - 1) & !0xFFF) + 0x1000; - + unsafe { let frame_allocator_ptr = EPT_FRAME_ALLOCATOR.load(Ordering::Acquire); if !frame_allocator_ptr.is_null() { let frame_allocator = &mut *(frame_allocator_ptr as *mut BootInfoFrameAllocator); - + let mut current_page = start_page; while current_page < end_page { if self.ept.get_phys_addr(current_page as u64).is_none() { if let Some(frame) = frame_allocator.allocate_frame() { let hpa = frame.start_address().as_u64(); - self.ept.map_4k(current_page as u64, hpa, frame_allocator).unwrap(); + self.ept + .map_4k(current_page as u64, hpa, frame_allocator) + .unwrap(); } else { panic!("Failed to allocate frame for image at {:#x}", current_page); } @@ -295,7 +305,7 @@ impl VCpu { } } } - + for (i, &byte) in image.iter().enumerate() { let gpa = addr + i; self.ept.set(gpa as u64, byte).unwrap(); @@ -303,17 +313,17 @@ impl VCpu { } pub fn setup_guest_memory(&mut self, frame_allocator: &mut BootInfoFrameAllocator) -> u64 { - let guest_memory_size = 2 * 1024 * 1024 * 1024; + info!( + "Setting up guest memory with on-demand allocation (reported size: {}MB)", + GUEST_MEMORY_SIZE / (1024 * 1024) + ); - info!("Setting up guest memory with on-demand allocation (reported size: {}MB)", - guest_memory_size / (1024 * 1024)); - - self.load_kernel(linux::BZIMAGE, guest_memory_size); + self.load_kernel(linux::BZIMAGE, GUEST_MEMORY_SIZE); let eptp = EPTP::new(&self.ept.root_table); unsafe { vmwrite(vmcs::control::EPTP_FULL, eptp.0).unwrap() }; - guest_memory_size + GUEST_MEMORY_SIZE } pub fn register_msrs(&mut self, mapper: &OffsetPageTable<'static>) { @@ -354,6 +364,7 @@ impl VCpu { .set(x86::msr::MSR_C5_PMON_BOX_CTRL, 0) .unwrap(); self.guest_msr.set(0x1b, 0).unwrap(); + self.guest_msr.set(0xc0010007, 0).unwrap(); vmwrite( vmcs::control::VMEXIT_MSR_LOAD_ADDR_FULL, @@ -969,7 +980,7 @@ impl VCpu { vmwrite(vmcs::host::RSP, rsp).unwrap(); } - fn step_next_inst(&mut self) -> Result<(), VmFail> { + pub fn step_next_inst(&mut self) -> Result<(), VmFail> { unsafe { let rip = vmread(vmcs::guest::RIP)?; vmwrite( @@ -980,8 +991,11 @@ impl VCpu { } fn handle_ept_violation(&mut self, gpa: u64) { - if gpa >= 2 * 1024 * 1024 * 1024 { - panic!("EPT Violation: Guest tried to access memory beyond 2GB at {:#x}", gpa); + if gpa >= GUEST_MEMORY_SIZE { + panic!( + "EPT Violation: Guest tried to access memory beyond 2GB at {:#x}", + gpa + ); } unsafe { @@ -989,19 +1003,22 @@ impl VCpu { if frame_allocator_ptr.is_null() { panic!("EPT Violation: Frame allocator not initialized!"); } - + let frame_allocator = &mut *(frame_allocator_ptr as *mut BootInfoFrameAllocator); - + match frame_allocator.allocate_frame() { Some(frame) => { let hpa = frame.start_address().as_u64(); - + if let Err(e) = self.ept.map_4k(gpa, hpa, frame_allocator) { panic!("Failed to map page at GPA {:#x}: {}", gpa, e); } } None => { - panic!("EPT Violation: Out of memory! Cannot allocate frame for GPA {:#x}", gpa); + panic!( + "EPT Violation: Out of memory! Cannot allocate frame for GPA {:#x}", + gpa + ); } } } @@ -1146,38 +1163,12 @@ impl VCpu { } if valid_bytes > 0 { - match instruction_bytes[0] { - 0x0F => { - if valid_bytes > 1 { - match instruction_bytes[1] { - 0x01 => match instruction_bytes[2] { - 0xCA => { - unsafe { - let rflags = vmread(vmcs::guest::RFLAGS).unwrap(); - vmwrite(vmcs::guest::RFLAGS, rflags & !(1 << 18)).unwrap(); - } - self.step_next_inst().unwrap(); - } - 0xCB => { - unsafe { - let rflags = vmread(vmcs::guest::RFLAGS).unwrap(); - vmwrite(vmcs::guest::RFLAGS, rflags | (1 << 18)).unwrap(); - } - self.step_next_inst().unwrap(); - } - _ => { - self.inject_exception(vector, error_code).unwrap(); - } - }, - _ => { - self.inject_exception(vector, error_code).unwrap(); - } - } - } - } - _ => { - self.inject_exception(vector, error_code).unwrap(); - } + if !emulate_opcode(self, instruction_bytes, valid_bytes) { + info!( + "VMExit: Exception {} at RIP {:#x} with instruction bytes: {:?}", + vector, rip, instruction_bytes + ); + self.inject_exception(vector, error_code).unwrap(); } } } @@ -1215,7 +1206,7 @@ impl VCpu { let translation_valid = (exit_qualification & 0x100) != 0; let page_addr = guest_address & !0xFFF; - + self.handle_ept_violation(page_addr); } _ => {