1use std::ffi::c_void;
4use std::{
5 io,
6 mem,
7 process,
8 ptr,
9};
10
11use ntapi::ntpsapi::NtSetInformationProcess;
12use num_enum::{
13 IntoPrimitive,
14 TryFromPrimitive,
15};
16use windows::Wdk::System::Threading::{
17 NtQueryInformationProcess,
18 ProcessIoPriority,
19};
20use windows::Win32::Foundation::{
21 HANDLE,
22 WAIT_ABANDONED,
23 WAIT_EVENT,
24 WAIT_FAILED,
25 WAIT_OBJECT_0,
26 WAIT_TIMEOUT,
27};
28use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
29use windows::Win32::System::Diagnostics::ToolHelp::{
30 CreateToolhelp32Snapshot,
31 TH32CS_SNAPTHREAD,
32 THREADENTRY32,
33 Thread32First,
34 Thread32Next,
35};
36use windows::Win32::System::Memory::{
37 MEM_COMMIT,
38 MEM_DECOMMIT,
39 MEM_RELEASE,
40 MEM_RESERVE,
41 PAGE_READWRITE,
42 VirtualAllocEx,
43 VirtualFreeEx,
44};
45use windows::Win32::System::Threading::{
46 self,
47 CreateRemoteThreadEx,
48 GetCurrentProcess,
49 GetCurrentProcessId,
50 GetCurrentThread,
51 GetCurrentThreadId,
52 GetProcessId,
53 GetThreadId,
54 INFINITE,
55 OpenProcess,
56 OpenThread,
57 PROCESS_ALL_ACCESS,
58 PROCESS_CREATION_FLAGS,
59 PROCESS_MODE_BACKGROUND_BEGIN,
60 PROCESS_MODE_BACKGROUND_END,
61 ResumeThread,
62 SetPriorityClass,
63 SetThreadPriority,
64 THREAD_ALL_ACCESS,
65 THREAD_MODE_BACKGROUND_BEGIN,
66 THREAD_MODE_BACKGROUND_END,
67 THREAD_PRIORITY,
68 WaitForInputIdle,
69 WaitForSingleObject,
70};
71use windows::Win32::UI::WindowsAndMessaging::{
72 PostThreadMessageW,
73 WM_QUIT,
74};
75
76use crate::internal::{
77 AutoClose,
78 ResultExt,
79 ReturnValue,
80 custom_err_with_code,
81};
82
83pub struct Process {
85 handle: AutoClose<HANDLE>,
86}
87
88impl Process {
89 pub fn current() -> Self {
91 let pseudo_handle = unsafe { GetCurrentProcess() };
92 Self::from_maybe_null(pseudo_handle)
93 .unwrap_or_else(|| unreachable!("Pseudo process handle should never be null"))
94 }
95
96 pub fn from_id<I>(id: I) -> io::Result<Self>
100 where
101 I: Into<ProcessId>,
102 {
103 let raw_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, id.into().0)? };
104 Ok(Self {
105 handle: raw_handle.into(),
106 })
107 }
108
109 pub fn get_id(&self) -> ProcessId {
110 let id = unsafe { GetProcessId(self.handle.entity) };
111 ProcessId(id)
112 }
113
114 pub fn begin_background_mode() -> io::Result<()> {
118 unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_BEGIN)? };
119 Ok(())
120 }
121
122 pub fn end_background_mode() -> io::Result<()> {
124 unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_END)? };
125 Ok(())
126 }
127
128 pub fn set_priority(&self, priority: ProcessPriority) -> io::Result<()> {
140 unsafe { SetPriorityClass(self.handle.entity, priority.into())? };
141 Ok(())
142 }
143
144 pub fn get_io_priority(&self) -> io::Result<Option<IoPriority>> {
148 let mut raw_io_priority: i32 = 0;
149 let mut return_length: u32 = 0;
150 let ret_val = unsafe {
151 NtQueryInformationProcess(
152 self.handle.entity,
153 ProcessIoPriority,
154 (&raw mut raw_io_priority).cast::<c_void>(),
155 u32::try_from(mem::size_of::<i32>()).unwrap_or_else(|_| unreachable!()),
156 &raw mut return_length,
157 )
158 };
159 ret_val.0.if_non_null_to_error(|| {
160 custom_err_with_code("Getting IO priority failed", ret_val.0)
161 })?;
162 Ok(IoPriority::try_from(raw_io_priority.cast_unsigned()).ok())
163 }
164
165 pub fn set_io_priority(&self, io_priority: IoPriority) -> io::Result<()> {
166 let mut raw_io_priority = u32::from(io_priority);
167 let ret_val = unsafe {
168 NtSetInformationProcess(
169 self.handle.entity.0.cast::<ntapi::winapi::ctypes::c_void>(),
170 ProcessIoPriority.0.cast_unsigned(),
171 (&raw mut raw_io_priority).cast::<ntapi::winapi::ctypes::c_void>(),
172 u32::try_from(mem::size_of::<u32>()).unwrap_or_else(|_| unreachable!()),
173 )
174 };
175 ret_val
176 .if_non_null_to_error(|| custom_err_with_code("Setting IO priority failed", ret_val))?;
177 Ok(())
178 }
179
180 pub fn wait_for_initialization(&self) -> io::Result<()> {
181 let return_code = unsafe { WaitForInputIdle(self.as_raw_handle(), INFINITE) };
182 let return_code = WAIT_EVENT(return_code);
183 match return_code {
184 WAIT_EVENT(0) => Ok(()),
185 _ if return_code == WAIT_FAILED => Err(io::Error::last_os_error()),
186 _ if return_code == WAIT_TIMEOUT => Err(io::ErrorKind::TimedOut.into()),
187 _ => unreachable!(),
188 }
189 }
190
191 pub unsafe fn create_remote_thread(
198 &self,
199 start_address: *const c_void,
200 call_param0: Option<*const c_void>,
201 ) -> io::Result<Thread> {
202 let thread_handle = unsafe {
203 let start_fn =
204 mem::transmute::<*const c_void, unsafe extern "system" fn(_) -> _>(start_address);
205 CreateRemoteThreadEx(
206 self.as_raw_handle(),
207 None,
208 0,
209 Some(start_fn),
210 call_param0,
211 0,
212 None,
213 None,
214 )
215 }?;
216 Ok(Thread::from_non_null(thread_handle))
217 }
218
219 fn as_raw_handle(&self) -> HANDLE {
220 self.handle.entity
221 }
222
223 #[expect(dead_code)]
224 fn from_non_null(handle: HANDLE) -> Self {
225 Self {
226 handle: handle.into(),
227 }
228 }
229
230 fn from_maybe_null(handle: HANDLE) -> Option<Self> {
231 if handle.is_null() {
232 None
233 } else {
234 Some(Self {
235 handle: handle.into(),
236 })
237 }
238 }
239}
240
241impl AsRef<Process> for Process {
242 fn as_ref(&self) -> &Process {
243 self
244 }
245}
246
247impl TryFrom<ProcessId> for Process {
248 type Error = io::Error;
249
250 fn try_from(value: ProcessId) -> Result<Self, Self::Error> {
251 Process::from_id(value)
252 }
253}
254
255impl TryFrom<&process::Child> for Process {
256 type Error = io::Error;
257
258 fn try_from(value: &process::Child) -> Result<Self, Self::Error> {
259 Self::try_from(ProcessId::from(value))
260 }
261}
262
263#[derive(Copy, Clone, Eq, PartialEq, Debug)]
265pub struct ProcessId(pub(crate) u32);
266
267impl ProcessId {
268 pub fn current() -> Self {
270 Self(unsafe { GetCurrentProcessId() })
271 }
272
273 fn from_raw_id(raw_id: u32) -> Self {
274 Self(raw_id)
275 }
276}
277
278impl From<&process::Child> for ProcessId {
279 fn from(value: &process::Child) -> Self {
280 Self::from_raw_id(value.id())
281 }
282}
283
284pub struct Thread {
286 handle: AutoClose<HANDLE>,
287}
288
289impl Thread {
290 pub fn current() -> Self {
294 let pseudo_handle = unsafe { GetCurrentThread() };
295 Self::from_maybe_null(pseudo_handle)
296 .unwrap_or_else(|| unreachable!("Pseudo thread handle should never be null"))
297 }
298
299 pub fn from_id<I>(id: I) -> io::Result<Self>
303 where
304 I: Into<ThreadId>,
305 {
306 let raw_handle = unsafe { OpenThread(THREAD_ALL_ACCESS, false, id.into().0)? };
307 Ok(Self {
308 handle: raw_handle.into(),
309 })
310 }
311
312 pub fn join(&self) -> io::Result<()> {
314 let event = unsafe { WaitForSingleObject(self.handle.entity, INFINITE) };
315 match event {
316 _ if event == WAIT_OBJECT_0 => Ok(()),
317 _ if event == WAIT_FAILED => Err(io::Error::last_os_error()),
318 _ if event == WAIT_ABANDONED => Err(io::ErrorKind::InvalidData.into()),
319 _ if event == WAIT_TIMEOUT => Err(io::ErrorKind::TimedOut.into()),
320 _ => unreachable!(),
321 }
322 }
323
324 pub fn resume(&self) -> io::Result<()> {
326 unsafe { ResumeThread(self.handle.entity) }
327 .cast_signed()
328 .if_eq_to_error(-1, io::Error::last_os_error)
329 }
330
331 pub fn begin_background_mode() -> io::Result<()> {
335 unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_BEGIN)? };
336 Ok(())
337 }
338
339 pub fn end_background_mode() -> io::Result<()> {
341 unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_END)? };
342 Ok(())
343 }
344
345 pub fn set_priority(&self, priority: ThreadPriority) -> Result<(), io::Error> {
357 unsafe { SetThreadPriority(self.handle.entity, priority.into())? };
358 Ok(())
359 }
360
361 pub fn get_id(&self) -> ThreadId {
362 let id = unsafe { GetThreadId(self.handle.entity) };
363 ThreadId(id)
364 }
365
366 fn from_non_null(handle: HANDLE) -> Self {
367 Self {
368 handle: handle.into(),
369 }
370 }
371
372 fn from_maybe_null(handle: HANDLE) -> Option<Self> {
373 if handle.is_null() {
374 None
375 } else {
376 Some(Self {
377 handle: handle.into(),
378 })
379 }
380 }
381}
382
383impl TryFrom<ThreadId> for Thread {
384 type Error = io::Error;
385
386 fn try_from(value: ThreadId) -> Result<Self, Self::Error> {
387 Thread::from_id(value)
388 }
389}
390
391#[derive(Copy, Clone, Eq, PartialEq, Debug)]
393pub struct ThreadId(pub(crate) u32);
394
395impl ThreadId {
396 pub fn current() -> Self {
398 Self(unsafe { GetCurrentThreadId() })
399 }
400
401 pub fn post_quit_message(self) -> io::Result<()> {
402 unsafe { PostThreadMessageW(self.0, WM_QUIT, Default::default(), Default::default())? }
403 Ok(())
404 }
405}
406
407#[derive(Copy, Clone, Debug)]
409pub struct ThreadInfo {
410 raw_entry: THREADENTRY32,
411}
412
413impl ThreadInfo {
414 pub fn all_threads() -> io::Result<Vec<Self>> {
416 fn get_empty_thread_entry() -> THREADENTRY32 {
417 THREADENTRY32 {
418 dwSize: mem::size_of::<THREADENTRY32>().try_into().unwrap(),
419 ..Default::default()
420 }
421 }
422 let mut result: Vec<Self> = Vec::new();
423 let snapshot: AutoClose<HANDLE> =
424 unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)? }.into();
425
426 let mut thread_entry = get_empty_thread_entry();
427 unsafe {
428 Thread32First(snapshot.entity, &raw mut thread_entry)?;
429 }
430 result.push(Self::from_raw(thread_entry));
431 loop {
432 let mut thread_entry = get_empty_thread_entry();
433 let next_ret_val = unsafe { Thread32Next(snapshot.entity, &raw mut thread_entry) };
434 if next_ret_val.is_ok() {
435 result.push(Self::from_raw(thread_entry));
436 } else {
437 break;
438 }
439 }
440 Ok(result)
441 }
442
443 pub fn all_process_threads<P>(process_id: P) -> io::Result<Vec<Self>>
445 where
446 P: Into<ProcessId>,
447 {
448 let pid: ProcessId = process_id.into();
449 let result = Self::all_threads()?
450 .into_iter()
451 .filter(|thread_info| thread_info.get_owner_process_id() == pid)
452 .collect();
453 Ok(result)
454 }
455
456 fn from_raw(raw_info: THREADENTRY32) -> Self {
457 ThreadInfo {
458 raw_entry: raw_info,
459 }
460 }
461
462 pub fn get_thread_id(&self) -> ThreadId {
464 ThreadId(self.raw_entry.th32ThreadID)
465 }
466
467 pub fn get_owner_process_id(&self) -> ProcessId {
469 ProcessId(self.raw_entry.th32OwnerProcessID)
470 }
471}
472
473#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
475#[repr(u32)]
476pub enum ProcessPriority {
477 Idle = Threading::IDLE_PRIORITY_CLASS.0,
478 BelowNormal = Threading::BELOW_NORMAL_PRIORITY_CLASS.0,
479 Normal = Threading::NORMAL_PRIORITY_CLASS.0,
480 AboveNormal = Threading::ABOVE_NORMAL_PRIORITY_CLASS.0,
481 High = Threading::HIGH_PRIORITY_CLASS.0,
482 Realtime = Threading::REALTIME_PRIORITY_CLASS.0,
483}
484
485impl From<ProcessPriority> for PROCESS_CREATION_FLAGS {
486 fn from(value: ProcessPriority) -> Self {
487 PROCESS_CREATION_FLAGS(value.into())
488 }
489}
490
491#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
493#[repr(i32)]
494pub enum ThreadPriority {
495 Idle = Threading::THREAD_PRIORITY_IDLE.0,
496 Lowest = Threading::THREAD_PRIORITY_LOWEST.0,
497 BelowNormal = Threading::THREAD_PRIORITY_BELOW_NORMAL.0,
498 Normal = Threading::THREAD_PRIORITY_NORMAL.0,
499 AboveNormal = Threading::THREAD_PRIORITY_ABOVE_NORMAL.0,
500 Highest = Threading::THREAD_PRIORITY_HIGHEST.0,
501 TimeCritical = Threading::THREAD_PRIORITY_TIME_CRITICAL.0,
502}
503
504impl From<ThreadPriority> for THREAD_PRIORITY {
505 fn from(value: ThreadPriority) -> Self {
506 THREAD_PRIORITY(value.into())
507 }
508}
509
510#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
512#[repr(u32)]
513pub enum IoPriority {
514 VeryLow = 0,
515 Low = 1,
516 Normal = 2,
517}
518
519pub struct ProcessMemoryAllocation<P: AsRef<Process>> {
520 remote_ptr: *mut c_void,
521 num_bytes: usize,
522 process: P,
523 skipped_reserve: bool,
524}
525
526impl<P: AsRef<Process>> ProcessMemoryAllocation<P> {
527 pub fn with_data<D: ?Sized>(process: P, skip_reserve: bool, data: &D) -> io::Result<Self> {
536 let data_size = mem::size_of_val(data);
537 assert_ne!(data_size, 0);
538 let allocation = Self::new_empty(process, skip_reserve, data_size)?;
539 allocation.write(data)?;
540 Ok(allocation)
541 }
542
543 fn new_empty(process: P, skip_reserve: bool, num_bytes: usize) -> io::Result<Self> {
544 assert_ne!(num_bytes, 0);
545 let mut allocation_type = MEM_COMMIT;
546 if !skip_reserve {
547 allocation_type |= MEM_RESERVE;
549 }
550 let remote_ptr = unsafe {
551 VirtualAllocEx(
552 process.as_ref().as_raw_handle(),
553 None,
554 num_bytes,
555 allocation_type,
556 PAGE_READWRITE,
557 )
558 }
559 .if_null_get_last_error()?;
560 Ok(Self {
561 remote_ptr,
562 num_bytes,
563 process,
564 skipped_reserve: skip_reserve,
565 })
566 }
567
568 fn write<D: ?Sized>(&self, data: &D) -> io::Result<()> {
569 let data_size = mem::size_of_val(data);
570 assert_ne!(data_size, 0);
571 assert!(data_size <= self.num_bytes);
572 unsafe {
573 WriteProcessMemory(
574 self.process.as_ref().as_raw_handle(),
575 self.remote_ptr,
576 ptr::from_ref(data).cast::<c_void>(),
577 data_size,
578 None,
579 )?;
580 }
581 Ok(())
582 }
583
584 fn free(&self) -> io::Result<()> {
585 let free_type = if self.skipped_reserve {
586 MEM_DECOMMIT
587 } else {
588 MEM_RELEASE
589 };
590 unsafe {
591 VirtualFreeEx(
592 self.process.as_ref().as_raw_handle(),
593 self.remote_ptr,
594 0,
595 free_type,
596 )?;
597 }
598 Ok(())
599 }
600}
601
602impl<P: AsRef<Process>> Drop for ProcessMemoryAllocation<P> {
603 fn drop(&mut self) {
604 self.free().unwrap_or_default_and_print_error();
605 }
606}
607
608pub trait CommandExtWE {
609 fn set_creation_flags(&mut self, flags: ProcessCreationFlags) -> &mut process::Command;
610}
611
612impl CommandExtWE for process::Command {
613 fn set_creation_flags(&mut self, flags: ProcessCreationFlags) -> &mut process::Command {
614 use std::os::windows::process::CommandExt;
615 self.creation_flags(flags.into())
616 }
617}
618
619#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
620#[repr(u32)]
621pub enum ProcessCreationFlags {
622 CreateSuspended = Threading::CREATE_SUSPENDED.0,
623}
624
625impl From<ProcessCreationFlags> for PROCESS_CREATION_FLAGS {
626 fn from(value: ProcessCreationFlags) -> Self {
627 PROCESS_CREATION_FLAGS(value.into())
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use more_asserts::*;
634
635 use super::*;
636 use crate::module::ExecutableModule;
637 use crate::string::ZeroTerminatedString;
638 #[cfg(feature = "ui")]
639 use crate::ui::window::WindowHandle;
640
641 #[test]
642 fn get_all_threads() -> io::Result<()> {
643 let all_threads = ThreadInfo::all_threads()?;
644 assert_gt!(all_threads.len(), 0);
645 Ok(())
646 }
647
648 #[test]
649 fn get_all_process_threads() -> io::Result<()> {
650 let all_threads = ThreadInfo::all_process_threads(Process::current().get_id())?;
651 assert_gt!(all_threads.len(), 0);
652 Ok(())
653 }
654
655 #[cfg(feature = "ui")]
656 #[test]
657 fn get_all_threads_and_windows() -> io::Result<()> {
658 let all_threads = ThreadInfo::all_threads()?;
659 let all_windows: Vec<WindowHandle> = all_threads
660 .into_iter()
661 .flat_map(|thread_info| WindowHandle::get_nonchild_windows(thread_info.get_thread_id()))
662 .collect();
663 assert_gt!(all_windows.len(), 0);
664 Ok(())
665 }
666
667 #[test]
668 fn set_get_io_priority() -> io::Result<()> {
669 let curr_process = Process::current();
670 let target_priority = IoPriority::Low;
671 curr_process.set_io_priority(target_priority)?;
672 let priority = curr_process.get_io_priority()?.unwrap();
673 assert_eq!(priority, target_priority);
674 Ok(())
675 }
676
677 #[test]
678 fn write_process_memory() -> io::Result<()> {
679 write_process_memory_internal(false)?;
680 write_process_memory_internal(true)?;
681 Ok(())
682 }
683
684 fn write_process_memory_internal(skip_reserve: bool) -> io::Result<()> {
685 let process = Process::current();
686 let memory = ProcessMemoryAllocation::with_data(process, skip_reserve, "123")?;
687 assert!(!memory.remote_ptr.is_null());
688 Ok(())
689 }
690
691 #[test]
692 fn create_remote_thread_locally() -> io::Result<()> {
693 let process = Process::current();
694 let module = ExecutableModule::from_loaded("kernel32.dll")?;
695 let load_library_fn_ptr = module.get_symbol_ptr_by_name("LoadLibraryA")?;
696 let raw_lib_name = ZeroTerminatedString::from("kernel32.dll");
697 let thread = unsafe {
698 process.create_remote_thread(
699 load_library_fn_ptr,
700 Some(raw_lib_name.as_raw_pcstr().as_ptr().cast::<c_void>()),
701 )
702 }?;
703 thread.join()
704 }
705}