dll_syringe/process/
process.rs

1use std::{
2    ffi::OsString,
3    io,
4    mem::{self, MaybeUninit},
5    num::NonZeroU32,
6    os::windows::prelude::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle},
7    path::{Path, PathBuf},
8    ptr,
9    time::Duration,
10};
11
12use winapi::{
13    shared::{
14        minwindef::{DWORD, FALSE},
15        winerror::{ERROR_CALL_NOT_IMPLEMENTED, ERROR_INSUFFICIENT_BUFFER},
16    },
17    um::{
18        minwinbase::STILL_ACTIVE,
19        processthreadsapi::{
20            CreateRemoteThread, GetCurrentProcess, GetExitCodeProcess, GetExitCodeThread,
21            GetProcessId, TerminateProcess,
22        },
23        synchapi::WaitForSingleObject,
24        winbase::{QueryFullProcessImageNameW, INFINITE, WAIT_FAILED},
25        winnt::{
26            PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION,
27            PROCESS_VM_READ, PROCESS_VM_WRITE,
28        },
29        wow64apiset::{GetSystemWow64DirectoryA, IsWow64Process},
30    },
31};
32
33use crate::{
34    process::{BorrowedProcess, ProcessModule},
35    utils::{win_fill_path_buf_helper, FillPathBufResult},
36};
37
38/// A handle to a running process.
39pub type ProcessHandle = std::os::windows::raw::HANDLE;
40
41/// The [privileges](https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights) required for a process handle to be usable for dll injection.
42pub const PROCESS_INJECTION_ACCESS: DWORD = PROCESS_CREATE_THREAD
43    | PROCESS_QUERY_INFORMATION
44    | PROCESS_VM_OPERATION
45    | PROCESS_VM_READ
46    | PROCESS_VM_WRITE;
47
48/// A trait representing a running process.
49///
50/// # Note
51/// The underlying handle has the following [privileges](https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights):
52///  - `PROCESS_CREATE_THREAD`
53///  - `PROCESS_QUERY_INFORMATION`
54///  - `PROCESS_VM_OPERATION`
55///  - `PROCESS_VM_WRITE`
56///  - `PROCESS_VM_READ`
57pub trait Process: AsHandle + AsRawHandle {
58    /// The underlying handle type.
59    type Handle;
60
61    /// Returns a borrowed instance of this process.
62    fn borrowed(&self) -> BorrowedProcess<'_>;
63
64    /// Tries to clone this process into a new instance.
65    fn try_clone(&self) -> Result<Self, io::Error>
66    where
67        Self: Sized;
68
69    /// Returns the underlying process handle.
70    #[must_use]
71    fn into_handle(self) -> Self::Handle;
72
73    /// Creates a new instance from the given handle.
74    ///
75    /// # Safety
76    /// The caller must ensure that the handle is a valid process handle and has the required priviledges.
77    #[must_use]
78    unsafe fn from_handle_unchecked(handle: Self::Handle) -> Self;
79
80    /// Returns the raw pseudo handle representing the current process.
81    #[must_use]
82    fn raw_current_handle() -> ProcessHandle {
83        unsafe { GetCurrentProcess() }.cast()
84    }
85
86    /// Returns the pseudo handle representing the current process.
87    #[must_use]
88    fn current_handle() -> Self::Handle;
89
90    /// Returns an instance representing the current process.
91    #[must_use]
92    fn current() -> Self
93    where
94        Self: Sized,
95    {
96        unsafe { Self::from_handle_unchecked(Self::current_handle()) }
97    }
98
99    /// Returns whether this instance represents the current process.
100    #[must_use]
101    fn is_current(&self) -> bool {
102        self.borrowed() == BorrowedProcess::current()
103    }
104
105    /// Returns whether this process is still alive and running.
106    ///
107    /// # Note
108    /// If the operation to determine the status fails, this function assumes that the process has exited.
109    #[must_use]
110    fn is_alive(&self) -> bool {
111        if self.is_current() {
112            return true;
113        }
114
115        let mut exit_code = MaybeUninit::uninit();
116        let result =
117            unsafe { GetExitCodeProcess(self.as_raw_handle().cast(), exit_code.as_mut_ptr()) };
118        result != FALSE && unsafe { exit_code.assume_init() } == STILL_ACTIVE
119    }
120
121    /// Returns the id of this process.
122    fn pid(&self) -> Result<NonZeroU32, io::Error> {
123        let result = unsafe { GetProcessId(self.as_raw_handle().cast()) };
124        NonZeroU32::new(result).ok_or_else(io::Error::last_os_error)
125    }
126
127    /// Returns whether this process is running under [WOW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/running-32-bit-applications).
128    /// This is the case for 32-bit programs running on a 64-bit platform.
129    ///
130    /// # Note
131    /// This method also returns `false` for a 32-bit process running under 32-bit Windows or 64-bit Windows 10 on ARM.
132    fn runs_under_wow64(&self) -> Result<bool, io::Error> {
133        let mut is_wow64 = MaybeUninit::uninit();
134        let result = unsafe { IsWow64Process(self.as_raw_handle().cast(), is_wow64.as_mut_ptr()) };
135        if result == 0 {
136            return Err(io::Error::last_os_error());
137        }
138        Ok(unsafe { is_wow64.assume_init() } != FALSE)
139    }
140
141    /// Returns whether this process is a 64-bit process.
142    fn is_x64(&self) -> Result<bool, io::Error> {
143        Ok(is_x64_windows()? && !self.runs_under_wow64()?)
144    }
145
146    /// Returns whether this process is a 32-bit process.
147    fn is_x86(&self) -> Result<bool, io::Error> {
148        Ok(is_x32_windows()? || is_x64_windows()? && self.runs_under_wow64()?)
149    }
150
151    /// Returns the executable path of this process.
152    fn path(&self) -> Result<PathBuf, io::Error> {
153        win_fill_path_buf_helper(|buf_ptr, buf_size| {
154            let mut buf_size = buf_size as u32;
155            let result = unsafe {
156                QueryFullProcessImageNameW(self.as_raw_handle().cast(), 0, buf_ptr, &mut buf_size)
157            };
158            if result == 0 {
159                let err = io::Error::last_os_error();
160                if err.raw_os_error().unwrap() == ERROR_INSUFFICIENT_BUFFER as i32 {
161                    FillPathBufResult::BufTooSmall {
162                        size_hint: Some(buf_size as usize),
163                    }
164                } else {
165                    FillPathBufResult::Error(err)
166                }
167            } else {
168                FillPathBufResult::Success {
169                    actual_len: buf_size as usize,
170                }
171            }
172        })
173    }
174
175    /// Returns the file name of the executable of this process.
176    fn base_name(&self) -> Result<OsString, io::Error> {
177        self.path()
178            .map(|path| path.file_name().unwrap().to_os_string())
179    }
180
181    /// Terminates this process with exit code 1.
182    fn kill(&self) -> Result<(), io::Error> {
183        self.kill_with_exit_code(1)
184    }
185
186    /// Terminates this process with the given exit code.
187    fn kill_with_exit_code(&self, exit_code: u32) -> Result<(), io::Error> {
188        let result = unsafe { TerminateProcess(self.as_raw_handle().cast(), exit_code) };
189        if result == 0 {
190            return Err(io::Error::last_os_error());
191        }
192        Ok(())
193    }
194
195    /// Starts a new thread in this process with the given entry point and argument, and waits for it to finish, returning the exit code.
196    fn run_remote_thread<T>(
197        &self,
198        remote_fn: extern "system" fn(*mut T) -> u32,
199        parameter: *mut T,
200    ) -> Result<u32, io::Error> {
201        let thread_handle = self.start_remote_thread(remote_fn, parameter)?;
202
203        let reason = unsafe { WaitForSingleObject(thread_handle.as_raw_handle().cast(), INFINITE) };
204        if reason == WAIT_FAILED {
205            return Err(io::Error::last_os_error());
206        }
207
208        let mut exit_code = MaybeUninit::uninit();
209        let result = unsafe {
210            GetExitCodeThread(thread_handle.as_raw_handle().cast(), exit_code.as_mut_ptr())
211        };
212        if result == 0 {
213            return Err(io::Error::last_os_error());
214        }
215        debug_assert_ne!(
216            result as u32, STILL_ACTIVE,
217            "GetExitCodeThread returned STILL_ACTIVE after WaitForSingleObject"
218        );
219
220        Ok(unsafe { exit_code.assume_init() })
221    }
222
223    /// Starts a new thread in this process with the given entry point and argument and returns the thread handle.
224    #[allow(clippy::not_unsafe_ptr_arg_deref)] // not relevant as ptr is dereffed in the target process and any invalid deref will only result in an io::Error.
225    fn start_remote_thread<T>(
226        &self,
227        remote_fn: unsafe extern "system" fn(*mut T) -> u32,
228        parameter: *mut T,
229    ) -> Result<OwnedHandle, io::Error> {
230        // create a remote thread that will call LoadLibraryW with payload_path as its argument.
231        let thread_handle = unsafe {
232            CreateRemoteThread(
233                self.as_raw_handle().cast(),
234                ptr::null_mut(),
235                0,
236                Some(mem::transmute(remote_fn)),
237                parameter.cast(),
238                0, // RUN_IMMEDIATELY
239                ptr::null_mut(),
240            )
241        };
242        if thread_handle.is_null() {
243            return Err(io::Error::last_os_error());
244        }
245
246        Ok(unsafe { OwnedHandle::from_raw_handle(thread_handle.cast()) })
247    }
248
249    /// Searches the modules in this process for one with the given name.
250    /// The comparison of names is case-insensitive.
251    /// If the extension is omitted, the default library extension `.dll` is appended.
252    ///
253    /// # Note
254    /// If the process is currently starting up and has not loaded all its modules, the returned list may be incomplete.
255    /// See also [`Process::wait_for_module_by_name`].
256    fn find_module_by_name(
257        &self,
258        module_name: impl AsRef<Path>,
259    ) -> Result<Option<ProcessModule<Self>>, io::Error>
260    where
261        Self: Sized;
262
263    /// Searches the modules in this process for one with the given path.
264    /// The comparison of paths is case-insensitive.
265    /// If the extension is omitted, the default library extension `.dll` is appended.
266    ///
267    /// # Note
268    /// If the process is currently starting up and has not loaded all its modules, the returned list may be incomplete.
269    /// See also [`Process::wait_for_module_by_path`].
270    fn find_module_by_path(
271        &self,
272        module_path: impl AsRef<Path>,
273    ) -> Result<Option<ProcessModule<Self>>, io::Error>
274    where
275        Self: Sized;
276
277    /// Searches the modules in this process for one with the given name, repeatedly until a matching module is found or the given timeout elapses.
278    /// The comparison of names is case-insensitive.
279    /// If the extension is omitted, the default library extension `.dll` is appended.
280    fn wait_for_module_by_name(
281        &self,
282        module_name: impl AsRef<Path>,
283        timeout: Duration,
284    ) -> Result<Option<ProcessModule<Self>>, io::Error>
285    where
286        Self: Sized;
287
288    /// Searches the modules in this process for one with the given path, repeatedly until a matching module is found or the given timeout elapses.
289    /// The comparison of paths is case-insensitive.
290    /// If the extension is omitted, the default library extension `.dll` is appended.
291    fn wait_for_module_by_path(
292        &self,
293        module_path: impl AsRef<Path>,
294        timeout: Duration,
295    ) -> Result<Option<ProcessModule<Self>>, io::Error>
296    where
297        Self: Sized;
298
299    /// Returns a snapshot of all modules currently loaded in this process.
300    ///
301    /// # Note
302    /// If the process is currently starting up and has not loaded all its modules yet, the returned list may be incomplete.
303    fn modules(&self) -> Result<Vec<ProcessModule<Self>>, io::Error>
304    where
305        Self: Sized,
306    {
307        let module_handles = self.borrowed().module_handles()?;
308        let mut modules = Vec::with_capacity(module_handles.len());
309        for module_handle in module_handles {
310            modules.push(unsafe { ProcessModule::new_unchecked(module_handle, self.try_clone()?) });
311        }
312        Ok(modules)
313    }
314}
315
316fn is_x32_windows() -> Result<bool, io::Error> {
317    // TODO: use GetNativeSystemInfo() instead?
318    let result = unsafe { GetSystemWow64DirectoryA(ptr::null_mut(), 0) };
319    if result == 0 {
320        let err = io::Error::last_os_error();
321        if err.raw_os_error().unwrap() == ERROR_CALL_NOT_IMPLEMENTED as _ {
322            Ok(true)
323        } else {
324            Err(err)
325        }
326    } else {
327        Ok(false)
328    }
329}
330
331fn is_x64_windows() -> Result<bool, io::Error> {
332    if cfg!(target_arch = "x86_64") {
333        Ok(true)
334    } else {
335        Ok(!is_x32_windows()?)
336    }
337}