need to get to main pc since wifi is trash
This commit is contained in:
parent
aed7231b15
commit
2649fd48f4
3 changed files with 267 additions and 0 deletions
148
src/asm.rs
Normal file
148
src/asm.rs
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/main.rs
15
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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
104
src/paging.rs
Normal file
104
src/paging.rs
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue