need to get to main pc since wifi is trash

This commit is contained in:
lily 2026-04-10 22:34:31 -04:00
parent aed7231b15
commit 2649fd48f4
3 changed files with 267 additions and 0 deletions

148
src/asm.rs Normal file
View file

@ -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),
);
}
}

View file

@ -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),
}
}

104
src/paging.rs Normal file
View file

@ -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<u64, WalkError> {
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<u64, WalkError> {
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<u32, WalkError> {
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<u64, WalkError> {
mem.read_u64(table_gpa + idx * 8).map_err(WalkError::MemRead)
}