Struct hyperpom::memory::VirtMemAllocator
source · pub struct VirtMemAllocator { /* private fields */ }
Expand description
Virtual memory allocator.
Role of the Virtual Memory Allocator in the Fuzzer
PhysMemAllocator
and PageTableManager
provides the necessary building blocks to create
multiple independant virtual address spaces over a shared physical one.
The role of this allocator is to provide an abstraction over PageTableManager
, to easily
allocate and access virtual memory inside a guest VM, but also to initialize the different
ARM system registers used for memory management (e.g. TTBR0_EL1/TTBR1_EL1
, SCTRL_EL1
,
MAIR_EL1
, etc.). It also provides fuzzing specific function, such as the ability to restore
a virtual address space from a snapshot.
Each fuzzing Executor
has at least one instance of this allocator to
manage its virtual memory ranges.
Example
use applevisor as av;
use hyperpom::memory::{PhysMemAllocator, VirtMemAllocator};
// First we create an hypervisor virtual machine instance to allow memory management in the
// guest (there's only one per process).
let vm = applevisor::VirtualMachine::new().unwrap();
// We create a new physical memory allocator over an address range of size 0x1000_0000.
let mut pma = PhysMemAllocator::new(0x1000_0000).unwrap();
// We create a new virtual memory allocator using `pma` as the physical page provider.
let mut vma = VirtMemAllocator::new(pma).unwrap();
// We can now map a virtual memory range starting at address `0x1234_0000`.
vma.map(0x1234_0000, 0x1000, av::MemPerms::RWX).unwrap();
// We can clone the virtual address space.
let vma_snapshot = vma.clone();
// We can write to it.
vma.write_qword(0x0000_0000_1000_0000, 0xdead_beef_dead_beef).unwrap();
// We can read from it.
assert_eq!(vma.read_qword(0x0000_0000_1000_0000), Ok(0xdead_beef_dead_beef));
// We can restore the virtual address space from a snapshot.
vma.restore_from_snapshot(&vma_snapshot).unwrap();
Now it’s also possible to map code and make the cpu execute arbitrary programs.
use applevisor as av;
use keystone as ks;
use hyperpom::memory::{PhysMemAllocator, VirtMemAllocator};
// Creates a new hypervisor virtual machine for this process.
let vm = av::VirtualMachine::new().unwrap();
// Creates an address space of size 0x1000_0000.
let pma = PhysMemAllocator::new(0x1000_0000).unwrap();
// Creates a virtual memory allocator.
let mut vma = VirtMemAllocator::new(pma.clone()).unwrap();
// Creates a new Vcpu.
let mut vcpu = av::Vcpu::new().unwrap();
// Initializes Vcpu system registers.
vma.init(&mut vcpu, true).unwrap();
// Maps an executable page at address 0x10_0000.
vma.map(0x10_0000, 0x1000, av::MemPerms::RX).unwrap();
// We compile a small program using the keystone engine.
let ks = ks::Keystone::new(keystone::Arch::ARM64, keystone::Mode::LITTLE_ENDIAN)
.expect("Could not initialize Keystone engine");
let asm = String::from(
"mov x0, #0x0000
movk x0, #0x20, lsl #16
blr x0
brk #0",
);
let entry_func = ks.asm(asm, 0).expect("could not assemble");
// We write the function at address 0x10_0000.
vma.write(0x10_0000, &entry_func.bytes).unwrap();
// We create a mapping and write a second function (that will be called by the first one) at
// address 0x20_0000.
vma.map(0x20_0000, 0x1000, av::MemPerms::RX).unwrap();
let asm = String::from(
"mov x0, #0x42
ret",
);
let func = ks.asm(asm, 0).expect("could not assemble");
vma.write(0x200000, &func.bytes).unwrap();
// Sets PC to the entry point address.
vcpu.set_reg(av::Reg::PC, 0x10_0000).unwrap();
// Runs the program
vcpu.run().unwrap();
// Checks that the value stored after the program execution is 0x42.
assert_eq!(vcpu.get_reg(av::Reg::X0), Ok(0x42));
// Checks that the Vcpu stopped its execution after an exception was raised when hitting
// the breakpoint.
let exit = vcpu.get_exit_info();
assert_eq!(exit.reason, av::ExitReason::EXCEPTION);
assert_eq!(exit.exception.syndrome, 0xf2000000);
Implementations
sourceimpl VirtMemAllocator
impl VirtMemAllocator
sourcepub fn new(pma: PhysMemAllocator) -> Result<Self>
pub fn new(pma: PhysMemAllocator) -> Result<Self>
Creates a new virtual memory allocator over the physical memory allocator pma
.
sourcepub fn init(&mut self, vcpu: &mut Vcpu, map_exceptions: bool) -> Result<()>
pub fn init(&mut self, vcpu: &mut Vcpu, map_exceptions: bool) -> Result<()>
Modifies different system registers to:
- set the page memory attributes;
- set the granule size;
- set the size of the upper and lower virtual address ranges;
- set the page table address of the upper and lower virtual address ranges;
- enable caches and the MMU;
- disable SIMD and FP registers access trapping;
- set the current exception level to EL0;
- unmask interrupts;
- initialize the
Exceptions
vector table; - enable debug features for the hypervisor.
The map_exceptions
argument determines if we need to remap the exception vector table
in the current address space. This argument should be set to false
if the function
is called after restoring from a snapshot.
sourcepub fn set_trans_table_base_registers(&self, vcpu: &Vcpu) -> Result<()>
pub fn set_trans_table_base_registers(&self, vcpu: &Vcpu) -> Result<()>
Sets TTBR0_EL1 and TTBR1_EL1 to the addresses of the current virtual address space page tables.
sourcepub fn map(&mut self, addr: u64, size: usize, perms: MemPerms) -> Result<()>
pub fn map(&mut self, addr: u64, size: usize, perms: MemPerms) -> Result<()>
Maps a non-privileged virtual address range of size size
, starting at address addr
and
with permissions perms
.
sourcepub fn map_privileged(
&mut self,
addr: u64,
size: usize,
perms: MemPerms
) -> Result<()>
pub fn map_privileged(
&mut self,
addr: u64,
size: usize,
perms: MemPerms
) -> Result<()>
Maps a privileged virtual address range of size size
, starting at address addr
and
with permissions perms
.
This function exists mainly because PAN is enabled by default on Apple Silicon. Therefore, all code that runs at EL1 (cache maintenance, exception handling, etc.) should be mapped using this function, otherwise it will trigger an exception.
sourcepub fn unmap(&mut self, addr: u64, size: usize) -> Result<()>
pub fn unmap(&mut self, addr: u64, size: usize) -> Result<()>
Unmaps a virtual address range of size size
and starting at address addr
.
sourcepub fn page_fault_dirty_state_handler(&mut self, far: u64) -> Result<bool>
pub fn page_fault_dirty_state_handler(&mut self, far: u64) -> Result<bool>
Checks if the page fault was due to dirty state detection, and handles it accordingly, or if it’s a legitimate data abort exception that needs to be propagated.
sourcepub fn restore_from_snapshot(
&mut self,
snapshot: &VirtMemAllocator
) -> Result<()>
pub fn restore_from_snapshot(
&mut self,
snapshot: &VirtMemAllocator
) -> Result<()>
Restores the current virtual address space from a snapshot.
sourcepub fn read(&self, addr: u64, buf: &mut [u8]) -> Result<usize>
pub fn read(&self, addr: u64, buf: &mut [u8]) -> Result<usize>
Reads from virtual address addr
into the slice buf
. The number of bytes read is the
size of buf
.
sourcepub fn read_dword(&self, addr: u64) -> Result<u32>
pub fn read_dword(&self, addr: u64) -> Result<u32>
Reads one dword at virtual address addr
.
sourcepub fn read_qword(&self, addr: u64) -> Result<u64>
pub fn read_qword(&self, addr: u64) -> Result<u64>
Reads one qword at virtual address addr
.
sourcepub fn read_cstring(&self, addr: u64) -> Result<String>
pub fn read_cstring(&self, addr: u64) -> Result<String>
Reads a C-string at virtual address addr
.
sourcepub fn write(&mut self, addr: u64, buf: &[u8]) -> Result<usize>
pub fn write(&mut self, addr: u64, buf: &[u8]) -> Result<usize>
Writes to virtual address addr
from the slice buf
. The number of bytes written is the
size of buf
.
sourcepub fn write_byte(&mut self, addr: u64, data: u8) -> Result<usize>
pub fn write_byte(&mut self, addr: u64, data: u8) -> Result<usize>
Writes one byte at virtual address addr
.
sourcepub fn write_word(&mut self, addr: u64, data: u16) -> Result<usize>
pub fn write_word(&mut self, addr: u64, data: u16) -> Result<usize>
Writes one word at virtual address addr
.
sourcepub fn write_dword(&mut self, addr: u64, data: u32) -> Result<usize>
pub fn write_dword(&mut self, addr: u64, data: u32) -> Result<usize>
Writes one dword at virtual address addr
.
sourcepub fn write_qword(&mut self, addr: u64, data: u64) -> Result<usize>
pub fn write_qword(&mut self, addr: u64, data: u64) -> Result<usize>
Writes one qword at virtual address addr
.
sourcepub fn write_cstring(&mut self, addr: u64, s: &str) -> Result<usize>
pub fn write_cstring(&mut self, addr: u64, s: &str) -> Result<usize>
Writes a C-string at virtual address addr
.
sourcepub fn write_dirty(&mut self, addr: u64, buf: &[u8]) -> Result<usize>
pub fn write_dirty(&mut self, addr: u64, buf: &[u8]) -> Result<usize>
Writes to virtual address addr
from the slice buf
. The number of bytes written is the
size of buf
. Sets the dirty bit.
sourcepub fn write_byte_dirty(&mut self, addr: u64, data: u8) -> Result<usize>
pub fn write_byte_dirty(&mut self, addr: u64, data: u8) -> Result<usize>
Writes one byte at virtual address addr
. Sets the dirty bit.
sourcepub fn write_word_dirty(&mut self, addr: u64, data: u16) -> Result<usize>
pub fn write_word_dirty(&mut self, addr: u64, data: u16) -> Result<usize>
Writes one word at virtual address addr
. Sets the dirty bit.
sourcepub fn write_dword_dirty(&mut self, addr: u64, data: u32) -> Result<usize>
pub fn write_dword_dirty(&mut self, addr: u64, data: u32) -> Result<usize>
Writes one dword at virtual address addr
. Sets the dirty bit.
sourcepub fn write_qword_dirty(&mut self, addr: u64, data: u64) -> Result<usize>
pub fn write_qword_dirty(&mut self, addr: u64, data: u64) -> Result<usize>
Writes one qword at virtual address addr
. Sets the dirty bit.