From 7dcbc7cf3628b6f950a86f94b2570ad8424a6d6c Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 00:24:32 +0100 Subject: [PATCH 1/7] processes_iter() for Windows --- Cargo.toml | 2 +- src/lib.rs | 27 ++++++++++++++++++ src/windows.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd19522..874e6e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ libc = "0.2" mach = "0.3" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winnt", "memoryapi", "minwindef", "processthreadsapi"] } +winapi = { version = "0.3.9", features = ["winnt", "memoryapi", "minwindef", "processthreadsapi", "tlhelp32", "wow64apiset", "handleapi"] } diff --git a/src/lib.rs b/src/lib.rs index 27024cb..695af53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,6 +190,22 @@ pub trait ProcessHandleExt { fn set_arch(self, arch: Architecture) -> Self; } +/// Handling modules (e.g. DLLs) in a process. +pub trait LibraryInfo { + /// Gets the base address of a module in a process. For example, "GameAssembly.dll" when on Windows. + /// You can then use the address in the `base` parameter of [`set_offset`] for example. + /// + /// # Errors + /// Returns `std::io::ErrorKind::NotFound` when no such module name exists. + /// Returns OS Error when something else went wrong. + /// + /// # Panics + /// Panics when closing the handle fails (e.g. double close). + /// + /// [`set_offset`]: trait.Memory.html#tymethod.set_offset + fn get_library_base(&self, name: &str) -> std::io::Result; +} + /// A trait that refers to and allows writing to a region of memory in a running program. pub trait Memory { /// Set the offsets to the location in memory. This is used for things such as multi-level @@ -259,3 +275,14 @@ where source.copy_address(addr, &mut copy)?; Ok(copy) } + +/// A minimal amount of information about a system process. +#[derive(Debug)] +pub struct Process { + /// Process ID of this process. + pub pid: Pid, + /// Name of this process. For example "MyGame.exe" on Windows. + pub name: String, +} + +pub use platform::processes_iter; diff --git a/src/windows.rs b/src/windows.rs index 1ed2a39..52c8f57 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,10 +1,13 @@ use winapi::shared::minwindef; +use winapi::um::{handleapi::CloseHandle, tlhelp32}; use std::os::windows::io::AsRawHandle; use std::process::Child; use std::ptr; -use super::{Architecture, CopyAddress, ProcessHandleExt, PutAddress, TryIntoProcessHandle}; +use super::{ + Architecture, CopyAddress, Process, ProcessHandleExt, PutAddress, TryIntoProcessHandle, +}; /// On Windows a `Pid` is a `DWORD`. pub type Pid = minwindef::DWORD; @@ -107,3 +110,75 @@ impl PutAddress for ProcessHandle { } } } + +/// List all processes on the system. +/// +/// Example: +/// ```rust +/// match process_memory::processes_iter().unwrap().find(|p| p.name == "MyGame.exe") { +/// None => println!("No such process currently exists"), +/// Some(p) => println!("Pid of {} is {}", p.name, p.pid) +/// } +/// ``` +/// +/// # Errors +/// Returns an `std::io::Error` with last operating system error when fetching process +/// list fails for some reason. +/// Return an `std::io::Error` with `Other` kind when failing to close the Windows `HANDLE`. +#[allow(clippy::cast_possible_truncation)] +pub fn processes_iter() -> std::io::Result> { + let mut entry = tlhelp32::PROCESSENTRY32 { + dwSize: std::mem::size_of::() as u32, + cntUsage: 0, + th32ProcessID: 0, + th32DefaultHeapID: 0, + th32ModuleID: 0, + cntThreads: 0, + th32ParentProcessID: 0, + pcPriClassBase: 0, + dwFlags: 0, + szExeFile: [0; minwindef::MAX_PATH], + }; + + let mut processes = Vec::new(); + + let snapshot: winapi::um::winnt::HANDLE; + unsafe { + snapshot = tlhelp32::CreateToolhelp32Snapshot(tlhelp32::TH32CS_SNAPPROCESS, 0); + if snapshot.is_null() { + return Err(std::io::Error::last_os_error()); + } + + if tlhelp32::Process32First(snapshot, &mut entry) == minwindef::TRUE { + // makeshift do-while + loop { + processes.push(Process { + pid: entry.th32ProcessID, + name: utf8_to_string(&entry.szExeFile), + }); + + if tlhelp32::Process32Next(snapshot, &mut entry) == minwindef::FALSE { + break; + } + } + } + + if CloseHandle(snapshot) == minwindef::FALSE { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Could not close handle.", + )); + }; + Ok(processes.into_iter()) + } +} + +/// A helper function to turn a `c_char` array to a String +fn utf8_to_string(bytes: &[i8]) -> String { + use std::ffi::CStr; + unsafe { + CStr::from_ptr(bytes.as_ptr()) + .to_string_lossy() + .into_owned() + } +} From 5fa11cb65fb3c14f18b59faee17656d4c7a3f100 Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 01:03:10 +0100 Subject: [PATCH 2/7] GetLibraryInfo on Windows --- src/lib.rs | 24 +++++++---- src/windows.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 695af53..ad48c69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,19 +191,16 @@ pub trait ProcessHandleExt { } /// Handling modules (e.g. DLLs) in a process. -pub trait LibraryInfo { - /// Gets the base address of a module in a process. For example, "GameAssembly.dll" when on Windows. - /// You can then use the address in the `base` parameter of [`set_offset`] for example. +pub trait GetLibraryInfo { + /// Lists all loaded libraries in a given process. + /// You can then use the address in [`set_offset`] for example. /// /// # Errors - /// Returns `std::io::ErrorKind::NotFound` when no such module name exists. /// Returns OS Error when something else went wrong. - /// - /// # Panics - /// Panics when closing the handle fails (e.g. double close). + /// Returns other error when closing the handle fails. /// /// [`set_offset`]: trait.Memory.html#tymethod.set_offset - fn get_library_base(&self, name: &str) -> std::io::Result; + fn libs_iter(&self) -> std::io::Result>; } /// A trait that refers to and allows writing to a region of memory in a running program. @@ -278,7 +275,7 @@ where /// A minimal amount of information about a system process. #[derive(Debug)] -pub struct Process { +pub struct ProcessInfo { /// Process ID of this process. pub pid: Pid, /// Name of this process. For example "MyGame.exe" on Windows. @@ -286,3 +283,12 @@ pub struct Process { } pub use platform::processes_iter; + +/// A minimal amount of information about a library (also called module, DLL,...) inside another process. +#[derive(Debug)] +pub struct LibraryInfo { + /// Name of the library. + pub name: String, + /// Base address of the module in the remote process. + pub base: usize, +} diff --git a/src/windows.rs b/src/windows.rs index 52c8f57..206b9f3 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -6,7 +6,8 @@ use std::process::Child; use std::ptr; use super::{ - Architecture, CopyAddress, Process, ProcessHandleExt, PutAddress, TryIntoProcessHandle, + Architecture, CopyAddress, GetLibraryInfo, LibraryInfo, ProcessHandleExt, ProcessInfo, + PutAddress, TryIntoProcessHandle, }; /// On Windows a `Pid` is a `DWORD`. @@ -126,9 +127,9 @@ impl PutAddress for ProcessHandle { /// list fails for some reason. /// Return an `std::io::Error` with `Other` kind when failing to close the Windows `HANDLE`. #[allow(clippy::cast_possible_truncation)] -pub fn processes_iter() -> std::io::Result> { +pub fn processes_iter() -> std::io::Result> { let mut entry = tlhelp32::PROCESSENTRY32 { - dwSize: std::mem::size_of::() as u32, + dwSize: std::mem::size_of::() as u32, // need to allow clippy truncation because of this cntUsage: 0, th32ProcessID: 0, th32DefaultHeapID: 0, @@ -152,7 +153,7 @@ pub fn processes_iter() -> std::io::Result> { if tlhelp32::Process32First(snapshot, &mut entry) == minwindef::TRUE { // makeshift do-while loop { - processes.push(Process { + processes.push(ProcessInfo { pid: entry.th32ProcessID, name: utf8_to_string(&entry.szExeFile), }); @@ -173,6 +174,62 @@ pub fn processes_iter() -> std::io::Result> { } } +/// Use `CreateToolhelp32Snapshot` to get and filter list of loaded libraries (also called modules) +/// of this process, returning the base address of it. +#[allow(clippy::cast_possible_truncation)] +impl GetLibraryInfo for Pid { + fn libs_iter(&self) -> std::io::Result> { + // taken from https://stackoverflow.com/questions/41552466/how-do-i-get-the-physical-baseaddress-of-an-dll-used-in-a-process + let mut module_entry = tlhelp32::MODULEENTRY32 { + dwSize: std::mem::size_of::() as u32, // need the clippy exception because of this + th32ModuleID: 0, + th32ProcessID: 0, + GlblcntUsage: 0, + ProccntUsage: 0, + modBaseAddr: std::ptr::null_mut(), + modBaseSize: 0, + hModule: std::ptr::null_mut(), + szModule: [0; tlhelp32::MAX_MODULE_NAME32 + 1], + szExePath: [0; minwindef::MAX_PATH], + }; + + let mut libs = Vec::new(); + + unsafe { + let snapshot = tlhelp32::CreateToolhelp32Snapshot( + tlhelp32::TH32CS_SNAPMODULE | tlhelp32::TH32CS_SNAPMODULE32, + *self, + ); + if snapshot.is_null() { + return Err(std::io::Error::last_os_error()); + } + + if tlhelp32::Module32First(snapshot, &mut module_entry) == minwindef::TRUE { + // makeshift do-while + loop { + libs.push(LibraryInfo { + name: utf8_to_string(&module_entry.szModule), + base: module_entry.modBaseAddr as usize, + }); + + if tlhelp32::Module32Next(snapshot, &mut module_entry) == minwindef::FALSE { + break; + } + } + } + + // We searched everything, nothing found + if CloseHandle(snapshot) == minwindef::FALSE { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Could not close handle.", + )); + }; + Ok(libs) + } + } +} + /// A helper function to turn a `c_char` array to a String fn utf8_to_string(bytes: &[i8]) -> String { use std::ffi::CStr; @@ -182,3 +239,49 @@ fn utf8_to_string(bytes: &[i8]) -> String { .into_owned() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn module_info() { + let pid = std::process::id() as Pid; + let base = pid + .libs_iter() + .unwrap() + .iter() + .find(|lib| lib.name == "ntdll.dll") + .unwrap() + .base; + assert_ne!(0, base); + println!("ntdll.exe address: 0x{:X}", base); + + match pid + .libs_iter() + .unwrap() + .iter() + .find(|lib| lib.name == "this_dll_doesnt_exist.dll") + { + Some(_) => panic!(), + None => {} + } + } + + #[test] + fn getpid() { + let proc = processes_iter() + .unwrap() + .find(|p| p.name == "svchost.exe") + .unwrap(); + assert_eq!("svchost.exe", proc.name); + + match processes_iter() + .unwrap() + .find(|p| p.name == "this_process_doesnt_exist.exe") + { + Some(_) => panic!(), + None => {} + } + } +} From bbea959b24f115f00d96777962f54f046579f6fd Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 01:07:05 +0100 Subject: [PATCH 3/7] *.dll *.so docs --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ad48c69..298ff00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -194,6 +194,8 @@ pub trait ProcessHandleExt { pub trait GetLibraryInfo { /// Lists all loaded libraries in a given process. /// You can then use the address in [`set_offset`] for example. + /// On Windows, libraries are also called "modules", and usually end in `*.dll`. + /// On Linux and macOS, libraries are also called "shared library", and usually end in `*.so`. /// /// # Errors /// Returns OS Error when something else went wrong. @@ -284,7 +286,9 @@ pub struct ProcessInfo { pub use platform::processes_iter; -/// A minimal amount of information about a library (also called module, DLL,...) inside another process. +/// A minimal amount of information about a library inside another process. +/// On Windows, libraries are also called "modules", and usually end in `*.dll`. +/// On Linux and macOS, libraries are also called "shared library", and usually end in `*.so`. #[derive(Debug)] pub struct LibraryInfo { /// Name of the library. From 11ea944bf3f893bedc433d33d1de8c83f531e200 Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 01:21:35 +0100 Subject: [PATCH 4/7] Add more convenient get_library_base --- src/lib.rs | 23 ++++++++++++++++++++++- src/windows.rs | 10 ++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 298ff00..df75462 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,7 @@ pub trait ProcessHandleExt { fn set_arch(self, arch: Architecture) -> Self; } -/// Handling modules (e.g. DLLs) in a process. +/// Getting some information (such as base address) of loaded libraries in a process. pub trait GetLibraryInfo { /// Lists all loaded libraries in a given process. /// You can then use the address in [`set_offset`] for example. @@ -203,6 +203,27 @@ pub trait GetLibraryInfo { /// /// [`set_offset`]: trait.Memory.html#tymethod.set_offset fn libs_iter(&self) -> std::io::Result>; + + /// Finds the base address of a loaded library in a process. + /// You can then use the address in [`set_offset`] for example. + /// On Windows, libraries are also called "modules", and usually end in `*.dll`. + /// On Linux and macOS, libraries are also called "shared library", and usually end in `*.so`. + /// + /// # Errors + /// Returns OS Error when something else went wrong. + /// Returns other error when closing the handle fails. + /// Returns a `NotFound` when no such library is loaded. + /// + /// [`set_offset`]: trait.Memory.html#tymethod.set_offset + fn get_library_base(&self, name: &str) -> std::io::Result { + match self.libs_iter()?.iter().find(|p| p.name == name) { + None => Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Count not find base address of library \"{}\"", name), + )), + Some(lib) => Ok(lib.base), + } + } } /// A trait that refers to and allows writing to a region of memory in a running program. diff --git a/src/windows.rs b/src/windows.rs index 206b9f3..946e33f 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -247,15 +247,9 @@ mod tests { #[test] fn module_info() { let pid = std::process::id() as Pid; - let base = pid - .libs_iter() - .unwrap() - .iter() - .find(|lib| lib.name == "ntdll.dll") - .unwrap() - .base; + let base = pid.get_library_base("ntdll.dll").unwrap(); assert_ne!(0, base); - println!("ntdll.exe address: 0x{:X}", base); + println!("ntdll.dll address: 0x{:X}", base); match pid .libs_iter() From 724b1419435b3edb09a4bae9aebfa8c6395ff283 Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 01:30:00 +0100 Subject: [PATCH 5/7] whoops --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index df75462..0c5ada3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -305,6 +305,7 @@ pub struct ProcessInfo { pub name: String, } +#[cfg(windows)] pub use platform::processes_iter; /// A minimal amount of information about a library inside another process. From bf6dc995aa2c7e84a44c0fe650664968e56051cf Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 02:13:06 +0100 Subject: [PATCH 6/7] try_into_process_handle() for ProcessInfo --- src/windows.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/windows.rs b/src/windows.rs index 946e33f..278b60a 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -59,6 +59,13 @@ impl TryIntoProcessHandle for Child { } } +/// A `ProcessInfo` has a pid (=u32, =minwindef::DWORD), which can be turned into a Processhandle. +impl TryIntoProcessHandle for ProcessInfo { + fn try_into_process_handle(&self) -> std::io::Result { + self.pid.try_into_process_handle() + } +} + /// Use `ReadProcessMemory` to read memory from another process on Windows. impl CopyAddress for ProcessHandle { #[allow(clippy::inline_always)] @@ -242,6 +249,8 @@ fn utf8_to_string(bytes: &[i8]) -> String { #[cfg(test)] mod tests { + use winapi::um::handleapi::INVALID_HANDLE_VALUE; + use super::*; #[test] @@ -278,4 +287,15 @@ mod tests { None => {} } } + + #[test] + fn processinfo_handle() { + let proc = processes_iter() + .unwrap() + .find(|p| p.name == "svchost.exe") + .unwrap(); + + let handle = proc.try_into_process_handle().unwrap(); + assert!(handle.check_handle()); + } } From e1567c93d6e122c6473d01a55573f25df3e75794 Mon Sep 17 00:00:00 2001 From: Kiiya Date: Fri, 22 Jan 2021 02:21:19 +0100 Subject: [PATCH 7/7] whoops2 --- src/windows.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/windows.rs b/src/windows.rs index 278b60a..711f344 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -59,7 +59,7 @@ impl TryIntoProcessHandle for Child { } } -/// A `ProcessInfo` has a pid (=u32, =minwindef::DWORD), which can be turned into a Processhandle. +/// A `ProcessInfo` has a pid (= `u32`, = `minwindef::DWORD`), which can be turned into a `ProcessHandle`. impl TryIntoProcessHandle for ProcessInfo { fn try_into_process_handle(&self) -> std::io::Result { self.pid.try_into_process_handle() @@ -249,8 +249,6 @@ fn utf8_to_string(bytes: &[i8]) -> String { #[cfg(test)] mod tests { - use winapi::um::handleapi::INVALID_HANDLE_VALUE; - use super::*; #[test] @@ -287,15 +285,4 @@ mod tests { None => {} } } - - #[test] - fn processinfo_handle() { - let proc = processes_iter() - .unwrap() - .find(|p| p.name == "svchost.exe") - .unwrap(); - - let handle = proc.try_into_process_handle().unwrap(); - assert!(handle.check_handle()); - } }