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 kvm;
|
||||||
mod mem;
|
mod mem;
|
||||||
|
mod paging;
|
||||||
mod qemu;
|
mod qemu;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -12,9 +14,22 @@ fn main() {
|
||||||
let regs = kvm::get_regs(vm.vcpu_fds[0]).expect("KVM_GET_REGS failed");
|
let regs = kvm::get_regs(vm.vcpu_fds[0]).expect("KVM_GET_REGS failed");
|
||||||
println!("[+] vcpu0 RIP={:#018x} RSP={:#018x}", regs.rip, regs.rsp);
|
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");
|
let guest = mem::GuestMem::attach(vm.pid).expect("failed to map guest RAM");
|
||||||
for r in &guest.regions {
|
for r in &guest.regions {
|
||||||
println!("[+] mem region GPA {:#010x}..{:#010x} (HVA {:#010x})",
|
println!("[+] mem region GPA {:#010x}..{:#010x} (HVA {:#010x})",
|
||||||
r.gpa_base, r.gpa_base + r.size, r.hva_base);
|
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