#![warn(missing_docs)]
use std::{
ffi::c_void,
ptr::null_mut,
collections::HashMap,
mem::{size_of, zeroed},
alloc::{alloc_zeroed, dealloc, Layout}
};
use winapi::shared::{
ntdef::NT_SUCCESS,
minwindef::{LPVOID, ULONG}
};
use crate::{
error::SpfError,
superfetch::{superfetch, SUPERFETCH_INFORMATION_CLASS},
types::{
PF_MEMORY_RANGE_INFO_V1,
PF_MEMORY_RANGE_INFO_V2,
PF_PHYSICAL_MEMORY_RANGE,
PF_PFN_PRIO_REQUEST,
MMPFN_IDENTITY,
STATUS_BUFFER_TOO_SMALL
}
};
pub mod error;
pub mod types;
pub mod superfetch;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryRange {
pub pfn: u64,
pub page_count: usize,
}
pub struct MemoryMap {
memory_ranges: Vec<MemoryRange>,
translations: HashMap<LPVOID, u64>,
}
impl MemoryMap {
pub unsafe fn snapshot() -> Result<Self, SpfError> {
privilege::raise()?;
let memory_ranges = unsafe { Self::query_ranges()? };
let mut translations = HashMap::new();
for range in &memory_ranges {
let base_pfn = range.pfn as usize;
let page_count = range.page_count;
let buffer_length = size_of::<PF_PFN_PRIO_REQUEST>() + size_of::<MMPFN_IDENTITY>() * page_count;
let layout = Layout::from_size_align(buffer_length, std::mem::align_of::<PF_PFN_PRIO_REQUEST>())
.map_err(|_| SpfError::Layout)?;
let buf_ptr = unsafe { alloc_zeroed(layout) };
if buf_ptr.is_null() {
return Err(SpfError::Allocation);
}
let request = buf_ptr as *mut PF_PFN_PRIO_REQUEST;
unsafe {
(*request).Version = 1;
(*request).RequestFlags = 1;
(*request).PfnCount = page_count;
let page_data_ptr = (buf_ptr as *mut u8)
.add(size_of::<PF_PFN_PRIO_REQUEST>()) as *mut MMPFN_IDENTITY;
for i in 0..page_count {
(*page_data_ptr.add(i)).PageFrameIndex = (base_pfn + i) as usize;
}
}
let status = unsafe {
superfetch(
SUPERFETCH_INFORMATION_CLASS::SuperfetchPfnQuery,
request as *mut c_void,
buffer_length as ULONG,
null_mut(),
)
};
if NT_SUCCESS(status) {
let page_data_ptr = unsafe {
(buf_ptr as *mut u8)
.add(size_of::<PF_PFN_PRIO_REQUEST>()) as *mut MMPFN_IDENTITY
};
for i in 0..page_count {
unsafe {
let page_data = &(*page_data_ptr.add(i));
if !page_data.u2_VirtualAddress.is_null() {
let virt_addr = page_data.u2_VirtualAddress as LPVOID;
let phys_addr = ((base_pfn + i) << 12) as u64;
translations.insert(virt_addr, phys_addr);
}
}
}
}
unsafe { dealloc(buf_ptr, layout); }
}
Ok(Self {
memory_ranges,
translations
})
}
pub fn ranges(&self) -> Vec<MemoryRange> {
return self.memory_ranges.clone();
}
pub fn translations(&self) -> HashMap<LPVOID, u64> {
return self.translations.clone();
}
pub fn translate(&self, address: LPVOID) -> Result<u64, SpfError> {
let aligned = (address as u64) & !0xFFFu64;
let aligned_ptr = aligned as LPVOID;
match self.translations.get(&aligned_ptr) {
Some(&phys_base) => {
let offset = (address as u64) & 0xFFFu64;
Ok(phys_base + offset)
}
None => Err(SpfError::Translate),
}
}
unsafe fn query_ranges() -> Result<Vec<MemoryRange>, SpfError> {
let mut buffer_length: ULONG = 0;
let mut probe_v1: PF_MEMORY_RANGE_INFO_V1 = unsafe { zeroed() };
probe_v1.Version = 1;
let status = unsafe {
superfetch(
SUPERFETCH_INFORMATION_CLASS::SuperfetchMemoryRangesQuery,
&mut probe_v1 as *mut _ as *mut c_void,
size_of::<PF_MEMORY_RANGE_INFO_V1>() as ULONG,
&mut buffer_length as *mut ULONG,
)
};
if status == STATUS_BUFFER_TOO_SMALL {
if buffer_length == 0 {
return Err(SpfError::Layout);
}
let size = buffer_length as usize;
let align = std::mem::align_of::<PF_PHYSICAL_MEMORY_RANGE>();
let layout = Layout::from_size_align(size, align).map_err(|_| SpfError::Layout)?;
let buf_ptr = unsafe { alloc_zeroed(layout) };
if buf_ptr.is_null() {
return Err(SpfError::Allocation);
}
let info_ptr = buf_ptr as *mut PF_MEMORY_RANGE_INFO_V1;
unsafe { (*info_ptr).Version = 1; }
let call_status = unsafe {
superfetch(
SUPERFETCH_INFORMATION_CLASS::SuperfetchMemoryRangesQuery,
info_ptr as *mut c_void,
buffer_length,
null_mut(),
)
};
if call_status >= 0 {
let range_count = unsafe { (*info_ptr).RangeCount as usize };
let header_size = size_of::<PF_MEMORY_RANGE_INFO_V1>();
let ranges_ptr = unsafe { buf_ptr.add(header_size - size_of::<PF_PHYSICAL_MEMORY_RANGE>()) as *const PF_PHYSICAL_MEMORY_RANGE };
let mut ranges = Vec::with_capacity(range_count);
for i in 0..range_count {
let r = unsafe { *ranges_ptr.add(i) };
ranges.push(MemoryRange { pfn: r.BasePfn as u64, page_count: r.PageCount as usize });
}
unsafe { dealloc(buf_ptr, layout) };
return Ok(ranges);
}
unsafe { dealloc(buf_ptr, layout) };
}
buffer_length = 0;
let mut probe_v2: PF_MEMORY_RANGE_INFO_V2 = unsafe { zeroed() };
probe_v2.Version = 2;
let status2 = unsafe {
superfetch(
SUPERFETCH_INFORMATION_CLASS::SuperfetchMemoryRangesQuery,
&mut probe_v2 as *mut _ as *mut c_void,
size_of::<PF_MEMORY_RANGE_INFO_V2>() as ULONG,
&mut buffer_length as *mut ULONG,
)
};
if status2 == STATUS_BUFFER_TOO_SMALL {
if buffer_length == 0 {
return Err(SpfError::Layout);
}
let size = buffer_length as usize;
let align = std::mem::align_of::<PF_PHYSICAL_MEMORY_RANGE>();
let layout = Layout::from_size_align(size, align).map_err(|_| SpfError::Layout)?;
let buf_ptr = unsafe { alloc_zeroed(layout) };
if buf_ptr.is_null() {
return Err(SpfError::Allocation);
}
let info_ptr = buf_ptr as *mut PF_MEMORY_RANGE_INFO_V2;
unsafe { (*info_ptr).Version = 2; }
let call_status = unsafe {
superfetch(
SUPERFETCH_INFORMATION_CLASS::SuperfetchMemoryRangesQuery,
info_ptr as *mut c_void,
buffer_length,
null_mut(),
)
};
if call_status >= 0 {
let range_count = unsafe { (*info_ptr).RangeCount as usize };
let header_size = size_of::<PF_MEMORY_RANGE_INFO_V2>();
let ranges_ptr = unsafe {
buf_ptr.add(header_size - size_of::<PF_PHYSICAL_MEMORY_RANGE>()) as *const PF_PHYSICAL_MEMORY_RANGE
};
let mut ranges = Vec::with_capacity(range_count);
for i in 0..range_count {
let r = unsafe { *ranges_ptr.add(i) };
ranges.push(MemoryRange { pfn: r.BasePfn as u64, page_count: r.PageCount as usize });
}
unsafe { dealloc(buf_ptr, layout) };
return Ok(ranges);
}
unsafe { dealloc(buf_ptr, layout) };
return Err(SpfError::QueryRanges(call_status));
} else {
return Err(SpfError::QueryRanges(status2));
}
}
}
mod privilege {
use ntapi::{ntrtl::RtlAdjustPrivilege, ntseapi::{SE_DEBUG_PRIVILEGE, SE_PROF_SINGLE_PROCESS_PRIVILEGE}};
use winapi::shared::ntdef::{BOOLEAN, NT_SUCCESS};
use crate::error::SpfError;
pub fn raise() -> Result<(), SpfError> {
unsafe {
let mut old: BOOLEAN = 0;
let status1 = RtlAdjustPrivilege(SE_PROF_SINGLE_PROCESS_PRIVILEGE as u32, 1, 0, &mut old);
if !NT_SUCCESS(status1) {
return Err(SpfError::RaisePrivilege(status1));
}
let mut old2: BOOLEAN = 0;
let status2 = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE as u32, 1, 0, &mut old2);
if !NT_SUCCESS(status2) {
return Err(SpfError::RaisePrivilege(status2));
}
return Ok(());
}
}
}