Skip to main content

dll_syringe/process/
module.rs

1use std::{
2    ffi::{CStr, CString, OsString},
3    io,
4    mem::{self, MaybeUninit},
5    path::{Path, PathBuf},
6    ptr::NonNull,
7};
8
9use crate::{
10    error::{GetLocalProcedureAddressError, IoOrNulError},
11    function::{FunctionPtr, RawFunctionPtr},
12    process::{BorrowedProcess, OwnedProcess, Process},
13    utils::{win_fill_path_buf_helper, FillPathBufResult},
14};
15use path_absolutize::Absolutize;
16use widestring::{U16CStr, U16CString};
17use winapi::{
18    shared::{
19        minwindef::{HINSTANCE__, HMODULE},
20        winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_MOD_NOT_FOUND},
21    },
22    um::{
23        libloaderapi::{GetModuleFileNameW, GetModuleHandleW, GetProcAddress},
24        memoryapi::VirtualQueryEx,
25        psapi::{GetModuleBaseNameW, GetModuleFileNameExW},
26        winnt::{MEMORY_BASIC_INFORMATION, PAGE_NOACCESS},
27    },
28};
29
30/// A handle to a process module.
31///
32/// # Note
33/// This is not a [`HANDLE`](https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#HANDLE)
34/// but a [`HMODULE`](https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#HMODULE)
35/// which is the base address of a loaded module.
36pub type ModuleHandle = HMODULE;
37
38/// A struct representing a loaded module of a running process.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct ProcessModule<P: Process> {
41    handle: NonNull<HINSTANCE__>,
42    process: P,
43}
44
45/// Type alias for a [`ProcessModule`] that owns its [`Process`] instance.
46pub type OwnedProcessModule = ProcessModule<OwnedProcess>;
47/// Type alias for a [`ProcessModule`] that does **NOT** own its [`Process`] instance.
48pub type BorrowedProcessModule<'a> = ProcessModule<BorrowedProcess<'a>>;
49
50unsafe impl<P: Process + Send> Send for ProcessModule<P> {}
51unsafe impl<P: Process + Sync> Sync for ProcessModule<P> {}
52
53impl<P: Process> ProcessModule<P> {
54    /// Contructs a new instance from the given module handle and its corresponding process.
55    ///
56    /// # Safety
57    /// The caller must guarantee that the given handle is valid and that the module is loaded into the given process.
58    /// (and stays that way while using the returned instance).
59    pub unsafe fn new_unchecked(handle: ModuleHandle, process: P) -> Self {
60        let handle = unsafe { NonNull::new_unchecked(handle) };
61        Self { handle, process }
62    }
63
64    /// Contructs a new instance from the given module handle loaded in the current process.
65    ///
66    /// # Safety
67    /// The caller must guarantee that the given handle is valid and that the module is loaded into the given process.
68    /// (and stays that way while using the returned instance).
69    pub unsafe fn new_local_unchecked(handle: ModuleHandle) -> Self {
70        unsafe { ProcessModule::new_unchecked(handle, P::current()) }
71    }
72
73    /// Returns a borrowed instance of this module.
74    pub fn borrowed(&self) -> BorrowedProcessModule<'_> {
75        ProcessModule {
76            handle: self.handle,
77            process: self.process.borrowed(),
78        }
79    }
80
81    /// Searches for a module with the given name or path in the given process.
82    /// If the extension is omitted, the default library extension `.dll` is appended.
83    pub fn find(
84        module_name_or_path: impl AsRef<Path>,
85        process: P,
86    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
87        let module_name_or_path = module_name_or_path.as_ref();
88        if module_name_or_path.parent().is_some() {
89            Self::find_by_path(module_name_or_path, process)
90        } else {
91            Self::find_by_name(module_name_or_path, process)
92        }
93    }
94
95    /// Searches for a module with the given name in the given process.
96    /// If the extension is omitted, the default library extension `.dll` is appended.
97    pub fn find_by_name(
98        module_name: impl AsRef<Path>,
99        process: P,
100    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
101        if process.is_current() {
102            Self::find_local_by_name(module_name)
103        } else {
104            Self::_find_remote_by_name(module_name, process)
105        }
106    }
107
108    /// Searches for a module with the given path in the given process.
109    /// If the extension is omitted, the default library extension `.dll` is appended.
110    pub fn find_by_path(
111        module_path: impl AsRef<Path>,
112        process: P,
113    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
114        if process.is_current() {
115            Self::find_local_by_path(module_path)
116        } else {
117            Self::_find_remote_by_path(module_path, process)
118        }
119    }
120
121    /// Searches for a module with the given name or path in the current process.
122    /// If the extension is omitted, the default library extension `.dll` is appended.
123    pub fn find_local(
124        module_name_or_path: impl AsRef<Path>,
125    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
126        Self::find(module_name_or_path, P::current())
127    }
128
129    /// Searches for a module with the given name in the current process.
130    /// If the extension is omitted, the default library extension `.dll` is appended.
131    pub fn find_local_by_name(
132        module_name: impl AsRef<Path>,
133    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
134        Self::find_local_by_name_or_abs_path(module_name.as_ref())
135    }
136
137    /// Searches for a module with the given path in the current process.
138    /// If the extension is omitted, the default library extension `.dll` is appended.
139    pub fn find_local_by_path(
140        module_path: impl AsRef<Path>,
141    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
142        let absolute_path = module_path.as_ref().absolutize()?;
143        Self::find_local_by_name_or_abs_path(absolute_path.as_ref())
144    }
145
146    pub(crate) fn find_local_by_name_or_abs_path(
147        module: &Path,
148    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
149        let module = U16CString::from_os_str(module.as_os_str())?;
150        Self::find_local_by_name_or_abs_path_wstr(&module).map_err(|e| e.into())
151    }
152
153    pub(crate) fn find_local_by_name_or_abs_path_wstr(
154        module: &U16CStr,
155    ) -> Result<Option<ProcessModule<P>>, io::Error> {
156        let handle = unsafe { GetModuleHandleW(module.as_ptr()) };
157        if handle.is_null() {
158            let err = io::Error::last_os_error();
159            if err.raw_os_error().unwrap() == ERROR_MOD_NOT_FOUND as _ {
160                return Ok(None);
161            }
162
163            return Err(err);
164        }
165
166        Ok(Some(unsafe { Self::new_local_unchecked(handle) }))
167    }
168
169    fn _find_remote_by_name(
170        module_name: impl AsRef<Path>,
171        process: P,
172    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
173        assert!(!process.is_current());
174
175        process
176            .find_module_by_name(module_name)
177            .map_err(|e| e.into())
178    }
179
180    fn _find_remote_by_path(
181        module_path: impl AsRef<Path>,
182        process: P,
183    ) -> Result<Option<ProcessModule<P>>, IoOrNulError> {
184        assert!(!process.is_current());
185
186        process
187            .find_module_by_path(module_path)
188            .map_err(|e| e.into())
189    }
190
191    /// Returns the underlying handle og the module.
192    #[must_use]
193    pub fn handle(&self) -> ModuleHandle {
194        self.handle.as_ptr()
195    }
196
197    /// Returns the process this module is loaded in.
198    #[must_use]
199    pub fn process(&self) -> &P {
200        &self.process
201    }
202
203    /// Returns a value indicating whether the module is loaded in current process.
204    #[must_use]
205    pub fn is_local(&self) -> bool {
206        self.process().is_current()
207    }
208    /// Returns a value indicating whether the module is loaded in a remote process (not the current one).
209    #[must_use]
210    pub fn is_remote(&self) -> bool {
211        !self.is_local()
212    }
213
214    /// Returns the path that the module was loaded from.
215    pub fn path(&self) -> Result<PathBuf, io::Error> {
216        if self.is_local() {
217            win_fill_path_buf_helper(|buf_ptr, buf_size| {
218                let buf_size = buf_size as u32;
219                let result = unsafe { GetModuleFileNameW(self.handle(), buf_ptr, buf_size) };
220                if result == 0 {
221                    let err = io::Error::last_os_error();
222                    if err.raw_os_error().unwrap() == ERROR_INSUFFICIENT_BUFFER as i32 {
223                        FillPathBufResult::BufTooSmall { size_hint: None }
224                    } else {
225                        FillPathBufResult::Error(err)
226                    }
227                } else if result >= buf_size {
228                    FillPathBufResult::BufTooSmall { size_hint: None }
229                } else {
230                    FillPathBufResult::Success {
231                        actual_len: result as usize,
232                    }
233                }
234            })
235        } else {
236            win_fill_path_buf_helper(|buf_ptr, buf_size| {
237                let buf_size = buf_size as u32;
238                let result = unsafe {
239                    GetModuleFileNameExW(
240                        self.process().as_raw_handle().cast(),
241                        self.handle(),
242                        buf_ptr,
243                        buf_size,
244                    )
245                };
246                if result == 0 {
247                    let err = io::Error::last_os_error();
248                    if err.raw_os_error().unwrap() == ERROR_INSUFFICIENT_BUFFER as i32 {
249                        FillPathBufResult::BufTooSmall { size_hint: None }
250                    } else {
251                        FillPathBufResult::Error(err)
252                    }
253                } else if result >= buf_size {
254                    FillPathBufResult::BufTooSmall { size_hint: None }
255                } else {
256                    FillPathBufResult::Success {
257                        actual_len: result as usize,
258                    }
259                }
260            })
261        }
262    }
263
264    /// Returns the base name of the file the module was loaded from.
265    pub fn base_name(&self) -> Result<OsString, io::Error> {
266        if self.is_local() {
267            self.path().map(|path| path.file_name().unwrap().to_owned())
268        } else {
269            win_fill_path_buf_helper(|buf_ptr, buf_size| {
270                let buf_size = buf_size as u32;
271                let result = unsafe {
272                    GetModuleBaseNameW(
273                        self.process().as_raw_handle().cast(),
274                        self.handle(),
275                        buf_ptr,
276                        buf_size,
277                    )
278                };
279                if result == 0 {
280                    let err = io::Error::last_os_error();
281                    if err.raw_os_error().unwrap() == ERROR_INSUFFICIENT_BUFFER as i32 {
282                        FillPathBufResult::BufTooSmall { size_hint: None }
283                    } else {
284                        FillPathBufResult::Error(err)
285                    }
286                } else if result >= buf_size {
287                    FillPathBufResult::BufTooSmall { size_hint: None }
288                } else {
289                    FillPathBufResult::Success {
290                        actual_len: result as usize,
291                    }
292                }
293            })
294            .map(|e| e.into())
295        }
296    }
297
298    /// Returns a pointer to the procedure with the given name from this module.
299    ///
300    /// # Note
301    /// This function is only supported for modules in the current process.
302    pub fn get_local_procedure_address(
303        &self,
304        proc_name: impl AsRef<str>,
305    ) -> Result<RawFunctionPtr, GetLocalProcedureAddressError> {
306        if self.is_remote() {
307            return Err(GetLocalProcedureAddressError::UnsupportedRemoteTarget);
308        }
309
310        let proc_name = CString::new(proc_name.as_ref())?;
311        self.get_local_procedure_address_cstr(&proc_name)
312            .map_err(|e| e.into())
313    }
314
315    /// Returns a pointer to the procedure with the given name from this module.
316    ///
317    /// # Note
318    /// This function is only supported for modules in the current process.
319    ///
320    /// # Safety
321    /// The target function must abide by the given function signature.
322    pub unsafe fn get_local_procedure<F: FunctionPtr>(
323        &self,
324        proc_name: impl AsRef<str>,
325    ) -> Result<F, GetLocalProcedureAddressError> {
326        self.get_local_procedure_address(proc_name)
327            .map(|addr| unsafe { F::from_ptr(addr) })
328    }
329
330    pub(crate) fn get_local_procedure_address_cstr(
331        &self,
332        proc_name: &CStr,
333    ) -> Result<RawFunctionPtr, io::Error> {
334        assert!(self.is_local());
335
336        let fn_ptr = unsafe { GetProcAddress(self.handle(), proc_name.as_ptr()) };
337        if let Some(fn_ptr) = NonNull::new(fn_ptr) {
338            Ok(fn_ptr.as_ptr())
339        } else {
340            Err(io::Error::last_os_error())
341        }
342    }
343
344    /// Returns whether this module is still loaded in the respective process.
345    /// If the operation fails, the module is considered to be unloaded.
346    pub fn guess_is_loaded(&self) -> bool {
347        self.try_guess_is_loaded().unwrap_or(false)
348    }
349
350    /// Returns whether this module is still loaded in the respective process.
351    pub fn try_guess_is_loaded(&self) -> Result<bool, io::Error> {
352        if !self.process().is_alive() {
353            return Ok(false);
354        }
355
356        let mut module_info = MaybeUninit::uninit();
357        let raw_module = self.handle.as_ptr().cast();
358        let result = unsafe {
359            VirtualQueryEx(
360                self.process.as_raw_handle().cast(),
361                raw_module,
362                module_info.as_mut_ptr(),
363                mem::size_of::<MEMORY_BASIC_INFORMATION>(),
364            )
365        };
366
367        if result == 0 {
368            Err(io::Error::last_os_error())
369        } else {
370            let module_info = unsafe { module_info.assume_init() };
371            Ok(module_info.BaseAddress == raw_module && module_info.Protect != PAGE_NOACCESS)
372        }
373    }
374}
375
376impl BorrowedProcessModule<'_> {
377    /// Tries to create a new [`OwnedProcessModule`] instance for this process module.
378    pub fn try_to_owned(&self) -> Result<OwnedProcessModule, io::Error> {
379        self.process
380            .try_to_owned()
381            .map(|process| OwnedProcessModule {
382                process,
383                handle: self.handle,
384            })
385    }
386}
387
388impl TryFrom<BorrowedProcessModule<'_>> for OwnedProcessModule {
389    type Error = io::Error;
390
391    fn try_from(module: BorrowedProcessModule<'_>) -> Result<Self, Self::Error> {
392        module.try_to_owned()
393    }
394}
395
396impl<'a> From<&'a OwnedProcessModule> for BorrowedProcessModule<'a> {
397    fn from(module: &'a OwnedProcessModule) -> Self {
398        module.borrowed()
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn find_local_by_name_present() {
408        let result = BorrowedProcessModule::find_local_by_name("kernel32.dll");
409        assert!(result.is_ok());
410        assert!(result.as_ref().unwrap().is_some());
411
412        let module = result.unwrap().unwrap();
413        assert!(module.is_local());
414        assert!(!module.handle().is_null());
415    }
416
417    #[test]
418    fn find_local_by_name_absent() {
419        let result = BorrowedProcessModule::find_local_by_name("kernel33.dll");
420        assert!(&result.is_ok());
421        assert!(result.unwrap().is_none());
422    }
423}