use std::{
ffi::OsString,
io,
mem::{self, MaybeUninit},
num::NonZeroU32,
os::windows::prelude::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle},
path::{Path, PathBuf},
ptr,
time::Duration,
};
use winapi::{
shared::{
minwindef::{DWORD, FALSE},
winerror::{ERROR_CALL_NOT_IMPLEMENTED, ERROR_INSUFFICIENT_BUFFER},
},
um::{
minwinbase::STILL_ACTIVE,
processthreadsapi::{
CreateRemoteThread, GetCurrentProcess, GetExitCodeProcess, GetExitCodeThread,
GetProcessId, TerminateProcess,
},
synchapi::WaitForSingleObject,
winbase::{QueryFullProcessImageNameW, INFINITE, WAIT_FAILED},
winnt::{
PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION,
PROCESS_VM_READ, PROCESS_VM_WRITE,
},
wow64apiset::{GetSystemWow64DirectoryA, IsWow64Process},
},
};
use crate::{
process::{BorrowedProcess, ProcessModule},
utils::{win_fill_path_buf_helper, FillPathBufResult},
};
pub type ProcessHandle = std::os::windows::raw::HANDLE;
pub const PROCESS_INJECTION_ACCESS: DWORD = PROCESS_CREATE_THREAD
| PROCESS_QUERY_INFORMATION
| PROCESS_VM_OPERATION
| PROCESS_VM_READ
| PROCESS_VM_WRITE;
pub trait Process: AsHandle + AsRawHandle {
type Handle;
fn borrowed(&self) -> BorrowedProcess<'_>;
fn try_clone(&self) -> Result<Self, io::Error>
where
Self: Sized;
#[must_use]
fn into_handle(self) -> Self::Handle;
#[must_use]
unsafe fn from_handle_unchecked(handle: Self::Handle) -> Self;
#[must_use]
fn raw_current_handle() -> ProcessHandle {
unsafe { GetCurrentProcess() }
}
#[must_use]
fn current_handle() -> Self::Handle;
#[must_use]
fn current() -> Self
where
Self: Sized,
{
unsafe { Self::from_handle_unchecked(Self::current_handle()) }
}
#[must_use]
fn is_current(&self) -> bool {
self.borrowed() == BorrowedProcess::current()
}
#[must_use]
fn is_alive(&self) -> bool {
if self.is_current() {
return true;
}
let mut exit_code = MaybeUninit::uninit();
let result = unsafe { GetExitCodeProcess(self.as_raw_handle(), exit_code.as_mut_ptr()) };
result != FALSE && unsafe { exit_code.assume_init() } == STILL_ACTIVE
}
fn pid(&self) -> Result<NonZeroU32, io::Error> {
let result = unsafe { GetProcessId(self.as_raw_handle()) };
NonZeroU32::new(result).ok_or_else(io::Error::last_os_error)
}
fn runs_under_wow64(&self) -> Result<bool, io::Error> {
let mut is_wow64 = MaybeUninit::uninit();
let result = unsafe { IsWow64Process(self.as_raw_handle(), is_wow64.as_mut_ptr()) };
if result == 0 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { is_wow64.assume_init() } != FALSE)
}
fn is_x64(&self) -> Result<bool, io::Error> {
Ok(is_x64_windows()? && !self.runs_under_wow64()?)
}
fn is_x86(&self) -> Result<bool, io::Error> {
Ok(is_x32_windows()? || is_x64_windows()? && self.runs_under_wow64()?)
}
fn path(&self) -> Result<PathBuf, io::Error> {
win_fill_path_buf_helper(|buf_ptr, buf_size| {
let mut buf_size = buf_size as u32;
let result = unsafe {
QueryFullProcessImageNameW(self.as_raw_handle(), 0, buf_ptr, &mut buf_size)
};
if result == 0 {
let err = io::Error::last_os_error();
if err.raw_os_error().unwrap() == ERROR_INSUFFICIENT_BUFFER as i32 {
FillPathBufResult::BufTooSmall {
size_hint: Some(buf_size as usize),
}
} else {
FillPathBufResult::Error(err)
}
} else {
FillPathBufResult::Success {
actual_len: buf_size as usize,
}
}
})
}
fn base_name(&self) -> Result<OsString, io::Error> {
self.path()
.map(|path| path.file_name().unwrap().to_os_string())
}
fn kill(&self) -> Result<(), io::Error> {
self.kill_with_exit_code(1)
}
fn kill_with_exit_code(&self, exit_code: u32) -> Result<(), io::Error> {
let result = unsafe { TerminateProcess(self.as_raw_handle(), exit_code) };
if result == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn run_remote_thread<T>(
&self,
remote_fn: extern "system" fn(*mut T) -> u32,
parameter: *mut T,
) -> Result<u32, io::Error> {
let thread_handle = self.start_remote_thread(remote_fn, parameter)?;
let reason = unsafe { WaitForSingleObject(thread_handle.as_raw_handle(), INFINITE) };
if reason == WAIT_FAILED {
return Err(io::Error::last_os_error());
}
let mut exit_code = MaybeUninit::uninit();
let result =
unsafe { GetExitCodeThread(thread_handle.as_raw_handle(), exit_code.as_mut_ptr()) };
if result == 0 {
return Err(io::Error::last_os_error());
}
debug_assert_ne!(
result as u32, STILL_ACTIVE,
"GetExitCodeThread returned STILL_ACTIVE after WaitForSingleObject"
);
Ok(unsafe { exit_code.assume_init() })
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] fn start_remote_thread<T>(
&self,
remote_fn: unsafe extern "system" fn(*mut T) -> u32,
parameter: *mut T,
) -> Result<OwnedHandle, io::Error> {
let thread_handle = unsafe {
CreateRemoteThread(
self.as_raw_handle(),
ptr::null_mut(),
0,
Some(mem::transmute(remote_fn)),
parameter.cast(),
0, ptr::null_mut(),
)
};
if thread_handle.is_null() {
return Err(io::Error::last_os_error());
}
Ok(unsafe { OwnedHandle::from_raw_handle(thread_handle) })
}
fn find_module_by_name(
&self,
module_name: impl AsRef<Path>,
) -> Result<Option<ProcessModule<Self>>, io::Error>
where
Self: Sized;
fn find_module_by_path(
&self,
module_path: impl AsRef<Path>,
) -> Result<Option<ProcessModule<Self>>, io::Error>
where
Self: Sized;
fn wait_for_module_by_name(
&self,
module_name: impl AsRef<Path>,
timeout: Duration,
) -> Result<Option<ProcessModule<Self>>, io::Error>
where
Self: Sized;
fn wait_for_module_by_path(
&self,
module_path: impl AsRef<Path>,
timeout: Duration,
) -> Result<Option<ProcessModule<Self>>, io::Error>
where
Self: Sized;
fn modules(&self) -> Result<Vec<ProcessModule<Self>>, io::Error>
where
Self: Sized,
{
let module_handles = self.borrowed().module_handles()?;
let mut modules = Vec::with_capacity(module_handles.len());
for module_handle in module_handles {
modules.push(unsafe { ProcessModule::new_unchecked(module_handle, self.try_clone()?) });
}
Ok(modules)
}
}
fn is_x32_windows() -> Result<bool, io::Error> {
let result = unsafe { GetSystemWow64DirectoryA(ptr::null_mut(), 0) };
if result == 0 {
let err = io::Error::last_os_error();
if err.raw_os_error().unwrap() == ERROR_CALL_NOT_IMPLEMENTED as _ {
Ok(true)
} else {
Err(err)
}
} else {
Ok(false)
}
}
fn is_x64_windows() -> Result<bool, io::Error> {
if cfg!(target_arch = "x86_64") {
Ok(true)
} else {
Ok(!is_x32_windows()?)
}
}