#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String, vec, vec::Vec};
#[cfg(feature = "std")]
use std::{format, string::String, vec, vec::Vec};
use crate::error::{Result, WraithError};
use crate::manipulation::syscall::{
get_syscall_table, nt_close, nt_success, DirectSyscall,
};
#[derive(Debug, Clone, Copy)]
pub struct HandleDuplicateOptions {
pub desired_access: u32,
pub attributes: u32,
pub options: u32,
}
impl Default for HandleDuplicateOptions {
fn default() -> Self {
Self {
desired_access: 0,
attributes: 0,
options: DUPLICATE_SAME_ACCESS,
}
}
}
impl HandleDuplicateOptions {
pub fn same_access() -> Self {
Self::default()
}
pub fn close_source() -> Self {
Self {
desired_access: 0,
attributes: 0,
options: DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE,
}
}
pub fn with_access(access: u32) -> Self {
Self {
desired_access: access,
attributes: 0,
options: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct HandleInfo {
pub handle_value: usize,
pub object_type: u32,
pub granted_access: u32,
pub object_name: Option<String>,
}
pub struct StolenHandle {
handle: usize,
owns_handle: bool,
}
impl StolenHandle {
pub fn handle(&self) -> usize {
self.handle
}
pub fn leak(mut self) -> usize {
self.owns_handle = false;
self.handle
}
pub unsafe fn from_raw(handle: usize) -> Self {
Self {
handle,
owns_handle: true,
}
}
pub unsafe fn from_raw_borrowed(handle: usize) -> Self {
Self {
handle,
owns_handle: false,
}
}
}
impl Drop for StolenHandle {
fn drop(&mut self) {
if self.owns_handle && self.handle != 0 {
let _ = nt_close(self.handle);
}
}
}
pub fn duplicate_handle(
source_process: usize,
source_handle: usize,
target_process: usize,
options: HandleDuplicateOptions,
) -> Result<StolenHandle> {
let table = get_syscall_table()?;
let syscall = DirectSyscall::from_table(table, "NtDuplicateObject")?;
let mut target_handle: usize = 0;
let status = unsafe {
syscall.call_many(&[
source_process,
source_handle,
target_process,
&mut target_handle as *mut usize as usize,
options.desired_access as usize,
options.attributes as usize,
options.options as usize,
])
};
if nt_success(status) {
Ok(StolenHandle {
handle: target_handle,
owns_handle: true,
})
} else {
Err(WraithError::HandleDuplicateFailed {
reason: format!("NtDuplicateObject failed: {:#x}", status as u32),
})
}
}
pub fn steal_handle(
source_process: usize,
remote_handle: usize,
options: HandleDuplicateOptions,
) -> Result<StolenHandle> {
let current_process: usize = usize::MAX; duplicate_handle(source_process, remote_handle, current_process, options)
}
pub fn enumerate_system_handles(
process_id_filter: Option<u32>,
object_type_filter: Option<u32>,
) -> Result<Vec<SystemHandleEntry>> {
let table = get_syscall_table()?;
let syscall = DirectSyscall::from_table(table, "NtQuerySystemInformation")?;
const SYSTEM_HANDLE_INFORMATION: u32 = 16;
const SYSTEM_EXTENDED_HANDLE_INFORMATION: u32 = 64;
let mut buffer_size: usize = 1024 * 1024;
let mut buffer: Vec<u8>;
let mut return_length: u32 = 0;
loop {
buffer = vec![0u8; buffer_size];
let status = unsafe {
syscall.call4(
SYSTEM_EXTENDED_HANDLE_INFORMATION as usize,
buffer.as_mut_ptr() as usize,
buffer.len(),
&mut return_length as *mut u32 as usize,
)
};
if nt_success(status) {
break;
}
if status == 0xC0000004_u32 as i32 {
buffer_size = return_length as usize + 0x10000;
if buffer_size > 256 * 1024 * 1024 {
return Err(WraithError::HandleDuplicateFailed {
reason: "buffer too large".into(),
});
}
continue;
}
return Err(WraithError::HandleDuplicateFailed {
reason: format!("NtQuerySystemInformation failed: {:#x}", status as u32),
});
}
parse_handle_info(&buffer, process_id_filter, object_type_filter)
}
#[repr(C)]
struct SystemHandleTableEntryInfoEx {
object: usize,
unique_process_id: usize,
handle_value: usize,
granted_access: u32,
creator_back_trace_index: u16,
object_type_index: u16,
handle_attributes: u32,
reserved: u32,
}
#[derive(Debug, Clone)]
pub struct SystemHandleEntry {
pub process_id: u32,
pub handle_value: usize,
pub object_type: u16,
pub granted_access: u32,
pub object_address: usize,
}
fn parse_handle_info(
buffer: &[u8],
process_id_filter: Option<u32>,
object_type_filter: Option<u32>,
) -> Result<Vec<SystemHandleEntry>> {
if buffer.len() < 16 {
return Ok(Vec::new());
}
let count = unsafe { *(buffer.as_ptr() as *const usize) };
if count == 0 || count > 10_000_000 {
return Ok(Vec::new());
}
let entry_size = core::mem::size_of::<SystemHandleTableEntryInfoEx>();
let entries_start = 2 * core::mem::size_of::<usize>();
let mut handles = Vec::new();
for i in 0..count {
let offset = entries_start + i * entry_size;
if offset + entry_size > buffer.len() {
break;
}
let entry = unsafe {
&*(buffer.as_ptr().add(offset) as *const SystemHandleTableEntryInfoEx)
};
if let Some(pid) = process_id_filter {
if entry.unique_process_id != pid as usize {
continue;
}
}
if let Some(obj_type) = object_type_filter {
if entry.object_type_index as u32 != obj_type {
continue;
}
}
handles.push(SystemHandleEntry {
process_id: entry.unique_process_id as u32,
handle_value: entry.handle_value,
object_type: entry.object_type_index,
granted_access: entry.granted_access,
object_address: entry.object,
});
}
Ok(handles)
}
pub fn find_handles_in_process(
target_pid: u32,
object_type: Option<u32>,
) -> Result<Vec<SystemHandleEntry>> {
enumerate_system_handles(Some(target_pid), object_type)
}
pub fn steal_process_handle(
source_process_handle: usize,
remote_handle: usize,
) -> Result<StolenHandle> {
steal_handle(source_process_handle, remote_handle, HandleDuplicateOptions::same_access())
}
pub mod object_types {
pub const PROCESS: u32 = 7;
pub const THREAD: u32 = 8;
pub const FILE: u32 = 31; pub const SECTION: u32 = 43; pub const KEY: u32 = 19; }
const DUPLICATE_CLOSE_SOURCE: u32 = 0x00000001;
const DUPLICATE_SAME_ACCESS: u32 = 0x00000002;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handle_duplicate_options() {
let opts = HandleDuplicateOptions::same_access();
assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
let opts = HandleDuplicateOptions::close_source();
assert!(opts.options & DUPLICATE_CLOSE_SOURCE != 0);
assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
}
#[test]
fn test_enumerate_own_handles() {
let pid = std::process::id();
let result = find_handles_in_process(pid, None);
assert!(result.is_ok());
let handles = result.unwrap();
assert!(!handles.is_empty(), "should have handles");
}
}