vminer 0.1.0

Virtual Machine Introspection library
Documentation
use bytemuck::Zeroable;
use std::{
    fs,
    io::{self, Read, Seek, Write},
    path::Path,
};
use vmc::{
    arch::{self, aarch64, x86_64, HasVcpus},
    Architecture, Memory, PhysicalAddress, VmResult,
};

#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Header {
    magic: u32,
    arch: u32,
    n_mappings: u32,
    n_vcpus: u32,
}

const MAGIC: u32 = u32::from_le_bytes(*b"\xaabox");

#[derive(Debug)]
enum Vcpus {
    X86_64(Vec<arch::x86_64::Vcpu>),
    Aarch64(Vec<arch::aarch64::Vcpu>),
}

impl Vcpus {
    fn read_x86_64<R: Read>(mut reader: R, n_vcpus: usize) -> io::Result<Self> {
        let mut vcpus = Vec::with_capacity(n_vcpus);

        for _ in 0..n_vcpus {
            let mut vcpu = x86_64::Vcpu::zeroed();

            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.registers))?;
            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.special_registers))?;
            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.other_registers))?;

            vcpus.push(vcpu);
        }

        Ok(Self::X86_64(vcpus))
    }

    fn read_aarch64<R: Read>(mut reader: R, n_vcpus: usize) -> io::Result<Self> {
        let mut vcpus = Vec::with_capacity(n_vcpus);

        for _ in 0..n_vcpus {
            let mut vcpu = aarch64::Vcpu::zeroed();

            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.registers))?;
            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.special_registers))?;
            reader.read_exact(bytemuck::bytes_of_mut(&mut vcpu.other_registers))?;

            vcpus.push(vcpu);
        }

        Ok(Self::Aarch64(vcpus))
    }
}

#[derive(Debug)]
pub struct DumbDump<Mem> {
    vcpus: Vcpus,
    mem: vmc::mem::MemRemap<Mem>,
}

impl DumbDump<vmc::mem::File> {
    pub fn read<P: AsRef<Path>>(path: P) -> VmResult<Self> {
        let mut file = io::BufReader::new(fs::File::open(path)?);

        let mut header = Header::zeroed();
        file.read_exact(bytemuck::bytes_of_mut(&mut header))?;

        if header.magic != MAGIC {
            return Err(vmc::VmError::new("invalid file magic"));
        }

        let mut mappings = vec![vmc::mem::MemoryMap::zeroed(); header.n_mappings as _];
        file.read_exact(bytemuck::cast_slice_mut(&mut mappings))?;

        let mut remap_addr = PhysicalAddress(0);
        let remap_at = mappings
            .iter()
            .map(|mapping| {
                let offset = remap_addr;
                remap_addr += mapping.end - mapping.start;
                offset
            })
            .collect();

        let vcpus = match header.arch {
            0 => Vcpus::read_x86_64(&mut file, header.n_vcpus as _)?,
            1 => Vcpus::read_aarch64(&mut file, header.n_vcpus as _)?,
            _ => return Err(vmc::VmError::new("unsupported architecture")),
        };

        let start = file.stream_position()?;
        let mut file = file.into_inner();
        let end = file.seek(io::SeekFrom::End(0))?;

        let mem_size: i64 = mappings.iter().map(|map| map.end - map.start).sum();
        if end - start != mem_size as u64 {
            println!(
                "memsize 0x{mem_size:x} ; 0x{end:x} - 0x{start:x} = 0x{:x}",
                end - start
            );
            return Err(vmc::VmError::new("invalid file size"));
        }

        let mem =
            vmc::mem::MemRemap::new(vmc::mem::File::new(file, start, end), mappings, remap_at);

        Ok(DumbDump { vcpus, mem })
    }
}

impl<Mem: vmc::Memory> DumbDump<Mem> {
    pub fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        let mut out = io::BufWriter::new(fs::File::create(path)?);

        let mappings = self.mem.memory_mappings();

        let mut header = Header {
            magic: MAGIC,
            n_mappings: mappings.len() as u32,
            arch: 0,
            n_vcpus: 0,
        };

        match &self.vcpus {
            Vcpus::X86_64(vcpus) => {
                header.arch = 0;
                header.n_vcpus = vcpus.len() as u32;
            }
            Vcpus::Aarch64(vcpus) => {
                header.arch = 1;
                header.n_vcpus = vcpus.len() as u32;
            }
        }

        out.write_all(bytemuck::bytes_of(&header))?;
        out.write_all(bytemuck::cast_slice(mappings))?;

        match &self.vcpus {
            Vcpus::X86_64(vcpus) => {
                for vcpu in vcpus {
                    out.write_all(bytemuck::bytes_of(&vcpu.registers))?;
                    out.write_all(bytemuck::bytes_of(&vcpu.special_registers))?;
                    out.write_all(bytemuck::bytes_of(&vcpu.other_registers))?;
                }
            }
            Vcpus::Aarch64(vcpus) => {
                for vcpu in vcpus {
                    out.write_all(bytemuck::bytes_of(&vcpu.registers))?;
                    out.write_all(bytemuck::bytes_of(&vcpu.special_registers))?;
                    out.write_all(bytemuck::bytes_of(&vcpu.other_registers))?;
                }
            }
        }

        self.mem.dump(&mut out)?;

        Ok(())
    }
}

impl DumbDump<vmc::mem::RawMemory<Vec<u8>>> {
    pub fn dump_vm<B: vmc::Backend>(backend: &B) -> VmResult<Self> {
        let mappings = backend.memory_mappings().to_owned();

        let mut remap_addr = PhysicalAddress(0);
        let remap_at = mappings
            .iter()
            .map(|mapping| {
                let offset = remap_addr;
                remap_addr += mapping.end - mapping.start;
                offset
            })
            .collect();

        let mut mem = vec![0; remap_addr.0 as usize];
        let mut offset = 0;
        for mapping in &mappings {
            let next_offset = offset + (mapping.end - mapping.start) as usize;
            backend.read_physical(mapping.start, &mut mem[offset..next_offset])?;
            offset = next_offset;
        }

        let vcpus = match backend.arch().into_runtime() {
            arch::RuntimeArchitecture::X86_64(_) => {
                let backend = vmc::arch::AssumeX86_64(backend);
                let mut vcpus = Vec::with_capacity(backend.vcpus_count());
                for vcpu in backend.iter_vcpus() {
                    vcpus.push(vmc::arch::x86_64::Vcpu {
                        registers: backend.registers(vcpu)?,
                        special_registers: backend.special_registers(vcpu)?,
                        other_registers: backend.other_registers(vcpu)?,
                    })
                }
                Vcpus::X86_64(vcpus)
            }
            arch::RuntimeArchitecture::Aarch64(_) => {
                let backend = vmc::arch::AssumeAarch64(backend);
                let mut vcpus = Vec::with_capacity(backend.vcpus_count());
                for vcpu in backend.iter_vcpus() {
                    vcpus.push(vmc::arch::aarch64::Vcpu {
                        registers: backend.registers(vcpu)?,
                        special_registers: backend.special_registers(vcpu)?,
                        other_registers: backend.other_registers(vcpu)?,
                    })
                }
                Vcpus::Aarch64(vcpus)
            }
        };

        let mem = vmc::mem::RawMemory::new(mem);
        let mem = vmc::mem::MemRemap::new(mem, mappings, remap_at);

        let dump = DumbDump { vcpus, mem };
        Ok(dump)
    }
}

impl<Mem: vmc::Memory> vmc::Memory for DumbDump<Mem> {
    #[inline]
    fn memory_mappings(&self) -> &[vmc::mem::MemoryMap] {
        self.mem.memory_mappings()
    }

    #[inline]
    fn is_valid(&self, addr: PhysicalAddress, size: usize) -> bool {
        self.mem.is_valid(addr, size)
    }

    #[inline]
    fn read_physical(&self, addr: PhysicalAddress, buf: &mut [u8]) -> vmc::MemoryAccessResult<()> {
        self.mem.read_physical(addr, buf)
    }

    #[inline]
    fn search(
        &self,
        addr: PhysicalAddress,
        page_size: u64,
        finder: &memchr::memmem::Finder,
        buf: &mut [u8],
    ) -> vmc::MemoryAccessResult<Option<u64>> {
        self.mem.search(addr, page_size, finder, buf)
    }
}

impl<Mem> vmc::HasVcpus for DumbDump<Mem> {
    type Arch = vmc::arch::RuntimeArchitecture;

    fn arch(&self) -> Self::Arch {
        match &self.vcpus {
            Vcpus::X86_64(_) => vmc::arch::RuntimeArchitecture::X86_64(vmc::arch::X86_64),
            Vcpus::Aarch64(_) => vmc::arch::RuntimeArchitecture::Aarch64(vmc::arch::Aarch64),
        }
    }

    fn vcpus_count(&self) -> usize {
        match &self.vcpus {
            Vcpus::X86_64(vcpus) => vcpus.len(),
            Vcpus::Aarch64(vcpus) => vcpus.len(),
        }
    }

    fn registers(
        &self,
        vcpu: vmc::VcpuId,
    ) -> vmc::VcpuResult<<Self::Arch as vmc::Architecture>::Registers> {
        Ok(match &self.vcpus {
            Vcpus::X86_64(vcpus) => vmc::arch::runtime::Registers::X86_64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .registers,
            ),
            Vcpus::Aarch64(vcpus) => vmc::arch::runtime::Registers::Aarch64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .registers,
            ),
        })
    }

    fn special_registers(
        &self,
        vcpu: vmc::VcpuId,
    ) -> vmc::VcpuResult<<Self::Arch as vmc::Architecture>::SpecialRegisters> {
        Ok(match &self.vcpus {
            Vcpus::X86_64(vcpus) => vmc::arch::runtime::SpecialRegisters::X86_64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .special_registers,
            ),
            Vcpus::Aarch64(vcpus) => vmc::arch::runtime::SpecialRegisters::Aarch64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .special_registers,
            ),
        })
    }

    fn other_registers(
        &self,
        vcpu: vmc::VcpuId,
    ) -> vmc::VcpuResult<<Self::Arch as vmc::Architecture>::OtherRegisters> {
        Ok(match &self.vcpus {
            Vcpus::X86_64(vcpus) => vmc::arch::runtime::OtherRegisters::X86_64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .other_registers,
            ),
            Vcpus::Aarch64(vcpus) => vmc::arch::runtime::OtherRegisters::Aarch64(
                vcpus
                    .get(vcpu.0)
                    .ok_or(vmc::VcpuError::InvalidId)?
                    .other_registers,
            ),
        })
    }
}

impl<Mem: vmc::Memory> vmc::Backend for DumbDump<Mem> {}