use std::ffi::c_void;
use std::mem::{size_of, MaybeUninit};
use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle};
use windows_sys::Win32::Foundation::{ERROR_INVALID_PARAMETER, HANDLE, LUID};
use windows_sys::Win32::Security::{
AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_DEBUG_NAME,
SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES,
};
use windows_sys::Win32::System::Diagnostics::Debug::ReadProcessMemory;
use windows_sys::Win32::System::Memory::{
VirtualQueryEx, MEMORY_BASIC_INFORMATION, MEM_COMMIT, PAGE_NOACCESS,
};
use windows_sys::Win32::System::Threading::{
GetCurrentProcess, OpenProcess, OpenProcessToken, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
};
use crate::memory::{FragmentedMemory, MemoryParams, Region, RegionDescription};
use crate::scanner::ScanError;
pub fn process_memory(pid: u32) -> Result<Box<dyn FragmentedMemory>, ScanError> {
if enable_se_debug_privilege().is_err() {
}
let res = unsafe {
OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
0,
pid,
)
};
if res.is_null() {
let err = std::io::Error::last_os_error();
return Err(
#[allow(clippy::cast_possible_wrap)]
if err.raw_os_error() == Some(ERROR_INVALID_PARAMETER as _) {
ScanError::UnknownProcess
} else {
ScanError::CannotListProcessRegions(err)
},
);
}
let handle = unsafe { OwnedHandle::from_raw_handle(res.cast()) };
Ok(Box::new(WindowsProcessMemory {
handle,
buffer: Vec::new(),
current_region: None,
}))
}
fn enable_se_debug_privilege() -> Result<(), std::io::Error> {
let mut self_token = std::ptr::null_mut();
let self_handle = unsafe { GetCurrentProcess() };
let res = unsafe { OpenProcessToken(self_handle, TOKEN_ADJUST_PRIVILEGES, &mut self_token) };
if res == 0 {
return Err(std::io::Error::last_os_error());
}
let mut debug_privilege_luid = LUID {
LowPart: 0,
HighPart: 0,
};
let res = unsafe {
LookupPrivilegeValueW(
std::ptr::null_mut(),
SE_DEBUG_NAME,
&mut debug_privilege_luid,
)
};
if res == 0 {
return Err(std::io::Error::last_os_error());
}
let cfg = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: debug_privilege_luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
let res = unsafe {
AdjustTokenPrivileges(
self_token,
0,
&cfg,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
if res == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[derive(Debug)]
struct WindowsProcessMemory {
handle: OwnedHandle,
buffer: Vec<u8>,
current_region: Option<RegionDescription>,
}
impl WindowsProcessMemory {
fn next_region(&self, params: &MemoryParams) -> Option<RegionDescription> {
let next_addr = match self.current_region {
Some(desc) => {
if let Some(chunk_size) = params.memory_chunk_size {
if chunk_size < desc.length {
return Some(RegionDescription {
start: desc.start.saturating_add(chunk_size),
length: desc.length.saturating_sub(chunk_size),
});
}
}
desc.start.checked_add(desc.length)?
}
None => 0,
};
query_next_region(self.handle.as_handle(), next_addr)
}
}
fn query_next_region(handle: BorrowedHandle, mut next_addr: usize) -> Option<RegionDescription> {
loop {
let mut info = MaybeUninit::uninit();
let res = unsafe {
VirtualQueryEx(
handle_to_windows_handle(handle.as_handle()),
next_addr as *const c_void,
info.as_mut_ptr(),
size_of::<MEMORY_BASIC_INFORMATION>(),
)
};
if res == 0 {
return None;
}
let info = unsafe { info.assume_init() };
next_addr = (info.BaseAddress as usize).checked_add(info.RegionSize)?;
if info.State == MEM_COMMIT && info.Protect != PAGE_NOACCESS {
return Some(RegionDescription {
start: info.BaseAddress as usize,
length: info.RegionSize,
});
}
}
}
impl FragmentedMemory for WindowsProcessMemory {
fn reset(&mut self) {
self.current_region = None;
}
fn next(&mut self, params: &MemoryParams) -> Option<RegionDescription> {
self.current_region = self.next_region(params);
self.current_region
.map(|region| get_chunked_region(region, params))
}
fn fetch(&mut self, params: &MemoryParams) -> Option<Region<'_>> {
let desc = get_chunked_region(self.current_region?, params);
self.buffer.resize(
std::cmp::min(desc.length, params.max_fetched_region_size),
0,
);
let mut nb_bytes_read = 0;
let res = unsafe {
ReadProcessMemory(
handle_to_windows_handle(self.handle.as_handle()),
desc.start as _,
self.buffer.as_mut_ptr().cast(),
self.buffer.len(),
&mut nb_bytes_read,
)
};
if res == 0 {
return None;
}
self.buffer.truncate(nb_bytes_read);
Some(Region {
start: desc.start,
mem: &self.buffer,
})
}
}
fn get_chunked_region(desc: RegionDescription, params: &MemoryParams) -> RegionDescription {
match params.memory_chunk_size {
Some(chunk_size) => RegionDescription {
start: desc.start,
length: std::cmp::min(chunk_size, desc.length),
},
None => desc,
}
}
fn handle_to_windows_handle(handle: BorrowedHandle) -> HANDLE {
handle.as_raw_handle() as HANDLE
}
#[cfg(test)]
mod tests {
use crate::test_helpers::test_type_traits_non_clonable;
use super::*;
#[test]
fn test_types_traits() {
let memory = process_memory(std::process::id()).unwrap();
test_type_traits_non_clonable(memory);
}
}