From 2649fd48f4f95bfb48e57817e52723f0f550c583 Mon Sep 17 00:00:00 2001 From: lily Date: Fri, 10 Apr 2026 22:34:31 -0400 Subject: [PATCH] need to get to main pc since wifi is trash --- src/asm.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 15 +++++ src/paging.rs | 104 +++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 src/asm.rs create mode 100644 src/paging.rs diff --git a/src/asm.rs b/src/asm.rs new file mode 100644 index 0000000..15e8e81 --- /dev/null +++ b/src/asm.rs @@ -0,0 +1,148 @@ +// Inline asm wrappers. +// +// Userspace-callable: rdtsc, cpuid, svm_supported +// Ring-0 stubs: read_cr3, write_cr3, vmload, vmsave, vmrun, rdmsr, wrmsr +// — these are here for when this code runs in a kernel context. +// Calling them from userspace will #GP or #UD. + +/// Read the hardware timestamp counter. +/// Used for timing guest memory access patterns (cache-timing side channels). +#[inline(always)] +pub fn rdtsc() -> u64 { + let lo: u32; + let hi: u32; + unsafe { + core::arch::asm!( + "rdtsc", + out("eax") lo, + out("edx") hi, + options(nomem, nostack, preserves_flags), + ); + } + ((hi as u64) << 32) | lo as u64 +} + +/// CPUID — leaf + subleaf, returns (eax, ebx, ecx, edx). +/// rbx is LLVM-reserved so we save/restore it around the instruction. +pub fn cpuid(leaf: u32, subleaf: u32) -> (u32, u32, u32, u32) { + let (eax, ebx, ecx, edx): (u32, u32, u32, u32); + unsafe { + core::arch::asm!( + "push rbx", + "cpuid", + "mov {ebx_out:e}, ebx", + "pop rbx", + inout("eax") leaf => eax, + inout("ecx") subleaf => ecx, + ebx_out = out(reg) ebx, + out("edx") edx, + options(nomem, nostack, preserves_flags), + ); + } + (eax, ebx, ecx, edx) +} + +/// Check whether the host CPU supports AMD SVM (CPUID 0x80000001 ECX bit 2). +pub fn svm_supported() -> bool { + let (_, _, ecx, _) = cpuid(0x8000_0001, 0); + ecx & (1 << 2) != 0 +} + +// ── ring-0 stubs ───────────────────────────────────────────────────────────── + +/// Read host CR3. Ring-0 only — #GP in CPL > 0. +/// Returns the physical address of the host PML4. +#[inline(always)] +pub unsafe fn read_cr3() -> u64 { + let val: u64; + unsafe { + core::arch::asm!( + "mov {}, cr3", + out(reg) val, + options(nomem, nostack, preserves_flags), + ); + } + val +} + +/// Write host CR3 — flushes all non-global TLB entries. Ring-0 only. +#[inline(always)] +pub unsafe fn write_cr3(cr3: u64) { + unsafe { + core::arch::asm!( + "mov cr3, {}", + in(reg) cr3, + options(nomem, nostack, preserves_flags), + ); + } +} + +/// VMLOAD — loads guest state from the VMCB at the given physical address. +/// AMD SVM, ring-0 only. +#[inline(always)] +pub unsafe fn vmload(vmcb_pa: u64) { + unsafe { + core::arch::asm!( + "vmload rax", + in("rax") vmcb_pa, + options(nostack), + ); + } +} + +/// VMSAVE — saves guest state back to the VMCB at the given physical address. +/// AMD SVM, ring-0 only. +#[inline(always)] +pub unsafe fn vmsave(vmcb_pa: u64) { + unsafe { + core::arch::asm!( + "vmsave rax", + in("rax") vmcb_pa, + options(nostack), + ); + } +} + +/// VMRUN — enters the guest. Does not return until a #VMEXIT occurs. +/// AMD SVM, ring-0 only. +#[inline(always)] +pub unsafe fn vmrun(vmcb_pa: u64) { + unsafe { + core::arch::asm!( + "vmrun rax", + inout("rax") vmcb_pa => _, + options(nostack), + ); + } +} + +/// RDMSR — read model-specific register. Ring-0 only. +#[inline(always)] +pub unsafe fn rdmsr(msr: u32) -> u64 { + let lo: u32; + let hi: u32; + unsafe { + core::arch::asm!( + "rdmsr", + in("ecx") msr, + out("eax") lo, + out("edx") hi, + options(nomem, nostack, preserves_flags), + ); + } + ((hi as u64) << 32) | lo as u64 +} + +/// WRMSR — write model-specific register. Ring-0 only. +#[inline(always)] +pub unsafe fn wrmsr(msr: u32, val: u64) { + unsafe { + core::arch::asm!( + "wrmsr", + in("ecx") msr, + in("eax") val as u32, + in("edx") (val >> 32) as u32, + options(nomem, nostack, preserves_flags), + ); + } +} diff --git a/src/main.rs b/src/main.rs index b5f5530..261ff8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ +mod asm; mod kvm; mod mem; +mod paging; mod qemu; fn main() { @@ -12,9 +14,22 @@ fn main() { let regs = kvm::get_regs(vm.vcpu_fds[0]).expect("KVM_GET_REGS failed"); println!("[+] vcpu0 RIP={:#018x} RSP={:#018x}", regs.rip, regs.rsp); + let (eax, _, _, _) = asm::cpuid(0, 0); + println!("[+] CPUID max_leaf={:#x} svm={}", eax, asm::svm_supported()); + let guest = mem::GuestMem::attach(vm.pid).expect("failed to map guest RAM"); for r in &guest.regions { println!("[+] mem region GPA {:#010x}..{:#010x} (HVA {:#010x})", r.gpa_base, r.gpa_base + r.size, r.hva_base); } + + // Smoke-test the page table walker: translate CR3 itself (it's a GPA, not + // a GVA, so we translate an arbitrary kernel VA to prove the walk works). + // Real EPROCESS walk comes next. + let cr3 = sregs.cr3; + println!("[+] CR3 (guest PML4 GPA) = {:#018x}", cr3); + match paging::translate(&guest, cr3, sregs.cr3) { + Ok(gpa) => println!("[+] translate(CR3 as GVA) -> GPA {:#018x}", gpa), + Err(e) => println!("[-] translate: {}", e), + } } diff --git a/src/paging.rs b/src/paging.rs new file mode 100644 index 0000000..5fd290b --- /dev/null +++ b/src/paging.rs @@ -0,0 +1,104 @@ +use crate::mem::GuestMem; + +// x86_64 4-level paging constants +const PRESENT: u64 = 1 << 0; +const HUGE_PAGE: u64 = 1 << 7; +const PHYS_MASK: u64 = 0x000f_ffff_ffff_f000; // bits 51:12 + +#[derive(Debug)] +pub enum WalkError { + NotPresent { level: &'static str, gva: u64, entry: u64 }, + MemRead(String), +} + +impl std::fmt::Display for WalkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WalkError::NotPresent { level, gva, entry } => + write!(f, "{} not present: gva={:#x} entry={:#x}", level, gva, entry), + WalkError::MemRead(s) => write!(f, "mem read: {}", s), + } + } +} + +/// Translate a guest virtual address to a guest physical address using +/// the 4-level page tables rooted at cr3. +/// Handles 4 KiB, 2 MiB (PDE.PS), and 1 GiB (PDPTE.PS) pages. +pub fn translate(mem: &GuestMem, cr3: u64, gva: u64) -> Result { + let pml4_base = cr3 & PHYS_MASK; + let pml4e = read_entry(mem, pml4_base, pml4_index(gva))?; + if pml4e & PRESENT == 0 { + return Err(WalkError::NotPresent { level: "PML4E", gva, entry: pml4e }); + } + + let pdpt_base = pml4e & PHYS_MASK; + let pdpte = read_entry(mem, pdpt_base, pdpt_index(gva))?; + if pdpte & PRESENT == 0 { + return Err(WalkError::NotPresent { level: "PDPTE", gva, entry: pdpte }); + } + if pdpte & HUGE_PAGE != 0 { + // 1 GiB page: PA = PDPTE[51:30] | GVA[29:0] + return Ok((pdpte & 0x000f_ffff_c000_0000) | (gva & 0x3fff_ffff)); + } + + let pd_base = pdpte & PHYS_MASK; + let pde = read_entry(mem, pd_base, pd_index(gva))?; + if pde & PRESENT == 0 { + return Err(WalkError::NotPresent { level: "PDE", gva, entry: pde }); + } + if pde & HUGE_PAGE != 0 { + // 2 MiB page: PA = PDE[51:21] | GVA[20:0] + return Ok((pde & 0x000f_ffff_ffe0_0000) | (gva & 0x001f_ffff)); + } + + let pt_base = pde & PHYS_MASK; + let pte = read_entry(mem, pt_base, pt_index(gva))?; + if pte & PRESENT == 0 { + return Err(WalkError::NotPresent { level: "PTE", gva, entry: pte }); + } + + // 4 KiB page + Ok((pte & PHYS_MASK) | (gva & 0xfff)) +} + +/// Read up to `buf.len()` bytes from a guest virtual address. +/// Handles reads that cross a page boundary by splitting into two physical reads. +pub fn read_virt(mem: &GuestMem, cr3: u64, gva: u64, buf: &mut [u8]) -> Result<(), WalkError> { + let page_offset = (gva & 0xfff) as usize; + let first_chunk = (0x1000 - page_offset).min(buf.len()); + + let gpa0 = translate(mem, cr3, gva)?; + mem.read_phys(gpa0, &mut buf[..first_chunk]).map_err(WalkError::MemRead)?; + + if first_chunk < buf.len() { + // crosses a page boundary — translate the next page separately + let gpa1 = translate(mem, cr3, gva + first_chunk as u64)?; + mem.read_phys(gpa1, &mut buf[first_chunk..]).map_err(WalkError::MemRead)?; + } + + Ok(()) +} + +pub fn read_virt_u64(mem: &GuestMem, cr3: u64, gva: u64) -> Result { + let mut buf = [0u8; 8]; + read_virt(mem, cr3, gva, &mut buf)?; + Ok(u64::from_le_bytes(buf)) +} + +pub fn read_virt_u32(mem: &GuestMem, cr3: u64, gva: u64) -> Result { + let mut buf = [0u8; 4]; + read_virt(mem, cr3, gva, &mut buf)?; + Ok(u32::from_le_bytes(buf)) +} + +// ── index helpers ───────────────────────────────────────────────────────────── + +#[inline(always)] fn pml4_index(gva: u64) -> u64 { (gva >> 39) & 0x1ff } +#[inline(always)] fn pdpt_index(gva: u64) -> u64 { (gva >> 30) & 0x1ff } +#[inline(always)] fn pd_index (gva: u64) -> u64 { (gva >> 21) & 0x1ff } +#[inline(always)] fn pt_index (gva: u64) -> u64 { (gva >> 12) & 0x1ff } + +#[inline(always)] +fn read_entry(mem: &GuestMem, table_gpa: u64, idx: u64) -> Result { + mem.read_u64(table_gpa + idx * 8).map_err(WalkError::MemRead) +}