use core::ffi::c_void;
use core::ptr::NonNull;
use alloc::vec::Vec;
use super::error::{status, KmError, KmResult, NtStatus};
use super::memory::{AccessMode, Mdl, LockOperation};
pub struct Eprocess {
raw: NonNull<c_void>,
}
impl Eprocess {
pub fn lookup(process_id: u32) -> KmResult<Self> {
let mut eprocess: *mut c_void = core::ptr::null_mut();
let status = unsafe {
PsLookupProcessByProcessId(process_id as *mut c_void, &mut eprocess)
};
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: process_id,
reason: "PsLookupProcessByProcessId failed",
});
}
NonNull::new(eprocess)
.map(|raw| Self { raw })
.ok_or(KmError::ProcessOperationFailed {
pid: process_id,
reason: "process not found",
})
}
pub fn as_raw(&self) -> *mut c_void {
self.raw.as_ptr()
}
pub fn dereference(&self) {
unsafe {
ObDereferenceObject(self.raw.as_ptr());
}
}
pub fn process_id(&self) -> u32 {
unsafe { PsGetProcessId(self.raw.as_ptr()) as u32 }
}
pub fn cr3(&self) -> u64 {
unsafe { PsGetProcessCr3(self.raw.as_ptr()) }
}
pub fn image_file_name(&self) -> [u8; 15] {
let mut name = [0u8; 15];
let ptr = unsafe { PsGetProcessImageFileName(self.raw.as_ptr()) };
if !ptr.is_null() {
unsafe {
for (i, byte) in name.iter_mut().enumerate() {
let b = *ptr.add(i);
if b == 0 {
break;
}
*byte = b;
}
}
}
name
}
pub fn is_terminating(&self) -> bool {
unsafe { PsGetProcessExitStatus(self.raw.as_ptr()) != 0x103 } }
pub fn peb(&self) -> *mut c_void {
unsafe { PsGetProcessPeb(self.raw.as_ptr()) }
}
pub fn wow64_peb(&self) -> *mut c_void {
unsafe { PsGetProcessWow64Process(self.raw.as_ptr()) }
}
}
impl Drop for Eprocess {
fn drop(&mut self) {
self.dereference();
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessAccess {
Terminate = 0x0001,
CreateThread = 0x0002,
VmOperation = 0x0008,
VmRead = 0x0010,
VmWrite = 0x0020,
DupHandle = 0x0040,
QueryInformation = 0x0400,
SetInformation = 0x0200,
All = 0x001F0FFF,
}
pub struct KmProcess {
eprocess: Eprocess,
apc_state: ApcState,
attached: bool,
}
#[repr(C)]
struct ApcState {
apc_list_head: [[*mut c_void; 2]; 2],
process: *mut c_void,
kernel_apc_in_progress: u8,
kernel_apc_pending: u8,
user_apc_pending: u8,
}
impl Default for ApcState {
fn default() -> Self {
Self {
apc_list_head: [[core::ptr::null_mut(); 2]; 2],
process: core::ptr::null_mut(),
kernel_apc_in_progress: 0,
kernel_apc_pending: 0,
user_apc_pending: 0,
}
}
}
impl KmProcess {
pub fn open(process_id: u32) -> KmResult<Self> {
let eprocess = Eprocess::lookup(process_id)?;
Ok(Self {
eprocess,
apc_state: ApcState::default(),
attached: false,
})
}
pub fn eprocess(&self) -> &Eprocess {
&self.eprocess
}
pub fn id(&self) -> u32 {
self.eprocess.process_id()
}
pub fn attach(&mut self) -> KmResult<()> {
if self.attached {
return Ok(());
}
unsafe {
KeStackAttachProcess(self.eprocess.as_raw(), &mut self.apc_state as *mut _ as *mut _);
}
self.attached = true;
Ok(())
}
pub fn detach(&mut self) {
if self.attached {
unsafe {
KeUnstackDetachProcess(&mut self.apc_state as *mut _ as *mut _);
}
self.attached = false;
}
}
pub fn read<T: Copy>(&mut self, address: u64) -> KmResult<T> {
let mut value = core::mem::MaybeUninit::<T>::uninit();
self.read_bytes(
address,
unsafe { core::slice::from_raw_parts_mut(value.as_mut_ptr() as *mut u8, core::mem::size_of::<T>()) },
)?;
Ok(unsafe { value.assume_init() })
}
pub fn read_bytes(&mut self, address: u64, buffer: &mut [u8]) -> KmResult<usize> {
if buffer.is_empty() {
return Ok(0);
}
self.attach()?;
let mut bytes_read = 0usize;
let status = unsafe {
MmCopyVirtualMemory(
self.eprocess.as_raw(),
address as *const c_void,
PsGetCurrentProcess(),
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
AccessMode::KernelMode as u8,
&mut bytes_read,
)
};
self.detach();
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "MmCopyVirtualMemory read failed",
});
}
Ok(bytes_read)
}
pub fn write<T: Copy>(&mut self, address: u64, value: &T) -> KmResult<()> {
let bytes = unsafe {
core::slice::from_raw_parts(value as *const T as *const u8, core::mem::size_of::<T>())
};
self.write_bytes(address, bytes)?;
Ok(())
}
pub fn write_bytes(&mut self, address: u64, buffer: &[u8]) -> KmResult<usize> {
if buffer.is_empty() {
return Ok(0);
}
self.attach()?;
let mut bytes_written = 0usize;
let status = unsafe {
MmCopyVirtualMemory(
PsGetCurrentProcess(),
buffer.as_ptr() as *const c_void,
self.eprocess.as_raw(),
address as *mut c_void,
buffer.len(),
AccessMode::KernelMode as u8,
&mut bytes_written,
)
};
self.detach();
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "MmCopyVirtualMemory write failed",
});
}
Ok(bytes_written)
}
pub fn allocate(
&mut self,
size: usize,
protection: u32,
preferred_address: Option<u64>,
) -> KmResult<u64> {
let mut base_address = preferred_address.unwrap_or(0) as *mut c_void;
let mut region_size = size;
let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
let status = unsafe {
ZwAllocateVirtualMemory(
process_handle,
&mut base_address,
0,
&mut region_size,
0x3000, protection,
)
};
unsafe { ZwClose(process_handle) };
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "ZwAllocateVirtualMemory failed",
});
}
Ok(base_address as u64)
}
pub fn free(&mut self, address: u64) -> KmResult<()> {
let mut base_address = address as *mut c_void;
let mut region_size = 0usize;
let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
let status = unsafe {
ZwFreeVirtualMemory(
process_handle,
&mut base_address,
&mut region_size,
0x8000, )
};
unsafe { ZwClose(process_handle) };
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "ZwFreeVirtualMemory failed",
});
}
Ok(())
}
pub fn protect(&mut self, address: u64, size: usize, protection: u32) -> KmResult<u32> {
let mut base_address = address as *mut c_void;
let mut region_size = size;
let mut old_protection = 0u32;
let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
let status = unsafe {
ZwProtectVirtualMemory(
process_handle,
&mut base_address,
&mut region_size,
protection,
&mut old_protection,
)
};
unsafe { ZwClose(process_handle) };
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "ZwProtectVirtualMemory failed",
});
}
Ok(old_protection)
}
pub fn get_module_base(&mut self, module_name: &[u16]) -> KmResult<u64> {
self.attach()?;
let peb = self.eprocess.peb();
if peb.is_null() {
self.detach();
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "PEB is null",
});
}
let ldr_offset = if cfg!(target_arch = "x86_64") { 0x18 } else { 0x0C };
let ldr_ptr = unsafe {
*(peb.cast::<u8>().add(ldr_offset) as *const *const c_void)
};
if ldr_ptr.is_null() {
self.detach();
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "PEB_LDR_DATA is null",
});
}
let list_offset = if cfg!(target_arch = "x86_64") { 0x10 } else { 0x0C };
let head = unsafe { ldr_ptr.cast::<u8>().add(list_offset) as *const ListEntry };
let mut current = unsafe { (*head).flink };
while current != head as *mut _ {
let name_offset = if cfg!(target_arch = "x86_64") { 0x58 } else { 0x2C };
let name_ptr = unsafe { current.cast::<u8>().add(name_offset) as *const UnicodeStringKernel };
let name = unsafe { &*name_ptr };
if !name.buffer.is_null() && name.length > 0 {
let name_slice = unsafe {
core::slice::from_raw_parts(name.buffer, (name.length / 2) as usize)
};
if name_slice.len() == module_name.len() {
let matches = name_slice.iter().zip(module_name.iter())
.all(|(a, b)| to_lower_u16(*a) == to_lower_u16(*b));
if matches {
let base_offset = if cfg!(target_arch = "x86_64") { 0x30 } else { 0x18 };
let base = unsafe {
*(current.cast::<u8>().add(base_offset) as *const u64)
};
self.detach();
return Ok(base);
}
}
}
current = unsafe { (*current).flink };
}
self.detach();
Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "module not found",
})
}
fn open_handle(&self, access: u32) -> KmResult<*mut c_void> {
let mut handle: *mut c_void = core::ptr::null_mut();
let status = unsafe {
ObOpenObjectByPointer(
self.eprocess.as_raw(),
0x200, core::ptr::null_mut(),
access,
core::ptr::null_mut(), AccessMode::KernelMode as u8,
&mut handle,
)
};
if !status::nt_success(status) {
return Err(KmError::ProcessOperationFailed {
pid: self.id(),
reason: "ObOpenObjectByPointer failed",
});
}
Ok(handle)
}
}
impl Drop for KmProcess {
fn drop(&mut self) {
self.detach();
}
}
#[repr(C)]
struct ListEntry {
flink: *mut ListEntry,
blink: *mut ListEntry,
}
#[repr(C)]
struct UnicodeStringKernel {
length: u16,
maximum_length: u16,
buffer: *const u16,
}
extern "system" {
fn PsLookupProcessByProcessId(ProcessId: *mut c_void, Process: *mut *mut c_void) -> NtStatus;
fn PsGetProcessId(Process: *mut c_void) -> usize;
fn PsGetProcessCr3(Process: *mut c_void) -> u64;
fn PsGetProcessImageFileName(Process: *mut c_void) -> *const u8;
fn PsGetProcessExitStatus(Process: *mut c_void) -> NtStatus;
fn PsGetProcessPeb(Process: *mut c_void) -> *mut c_void;
fn PsGetProcessWow64Process(Process: *mut c_void) -> *mut c_void;
fn PsGetCurrentProcess() -> *mut c_void;
fn ObDereferenceObject(Object: *mut c_void);
fn ObOpenObjectByPointer(
Object: *mut c_void,
HandleAttributes: u32,
PassedAccessState: *mut c_void,
DesiredAccess: u32,
ObjectType: *mut c_void,
AccessMode: u8,
Handle: *mut *mut c_void,
) -> NtStatus;
fn KeStackAttachProcess(Process: *mut c_void, ApcState: *mut c_void);
fn KeUnstackDetachProcess(ApcState: *mut c_void);
fn MmCopyVirtualMemory(
SourceProcess: *mut c_void,
SourceAddress: *const c_void,
TargetProcess: *mut c_void,
TargetAddress: *mut c_void,
BufferSize: usize,
PreviousMode: u8,
ReturnSize: *mut usize,
) -> NtStatus;
fn ZwAllocateVirtualMemory(
ProcessHandle: *mut c_void,
BaseAddress: *mut *mut c_void,
ZeroBits: usize,
RegionSize: *mut usize,
AllocationType: u32,
Protect: u32,
) -> NtStatus;
fn ZwFreeVirtualMemory(
ProcessHandle: *mut c_void,
BaseAddress: *mut *mut c_void,
RegionSize: *mut usize,
FreeType: u32,
) -> NtStatus;
fn ZwProtectVirtualMemory(
ProcessHandle: *mut c_void,
BaseAddress: *mut *mut c_void,
RegionSize: *mut usize,
NewProtect: u32,
OldProtect: *mut u32,
) -> NtStatus;
fn ZwClose(Handle: *mut c_void) -> NtStatus;
}
#[inline]
fn to_lower_u16(c: u16) -> u16 {
if c >= b'A' as u16 && c <= b'Z' as u16 {
c + 32
} else {
c
}
}