Hi. I have already implemented this feature.
Refer to the following:
Here is the code.
#![allow(nonstandard_style)]
use std::{
ffi::{c_uchar, c_ulong, c_ushort, c_void},
marker::PhantomData,
mem::{self, MaybeUninit},
ops::Deref,
slice,
};
use windows::{
Wdk::{
Foundation::{
NtQueryObject, ObjectTypeInformation, OBJECT_INFORMATION_CLASS, OBJECT_NAME_INFORMATION,
},
System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS},
},
Win32::{
Foundation::{
CloseHandle, DuplicateHandle, BOOL, DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS,
HANDLE, NTSTATUS,
},
System::Threading::{GetCurrentProcess, OpenProcess, PROCESS_ALL_ACCESS},
},
};
/// Declare this function by self, because it is not present in the windows crate.
#[inline]
pub unsafe fn NtDuplicateObject<P0>(
source_process_handle: P0,
source_handle: P0,
target_process_handle: P0,
target_handle: Option<*mut ::core::ffi::c_void>,
access_mask: u32,
attributes: u32,
options: u32,
) -> windows::core::Result<()>
where
P0: windows::core::IntoParam<HANDLE>,
{
windows_targets::link!("ntdll.dll" "system" fn NtDuplicateObject(source_process_handle: HANDLE, source_handle: HANDLE, target_process_handle: HANDLE, target_handle: *mut c_void, access_mask: u32, attributes: u32, roptions: u32) -> NTSTATUS);
NtDuplicateObject(
source_process_handle.into_param().abi(),
source_handle.into_param().abi(),
target_process_handle.into_param().abi(),
::core::mem::transmute(target_handle.unwrap_or(::std::ptr::null_mut())),
access_mask,
attributes,
options,
)
.ok()
}
/// Declare this struct by self, because it is not present in the windows crate.
/// see https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm
const SystemHandleInformation: SYSTEM_INFORMATION_CLASS = SYSTEM_INFORMATION_CLASS(0x10);
const ObjectNameInformation: OBJECT_INFORMATION_CLASS = OBJECT_INFORMATION_CLASS(0x1);
/// Declare this struct by self, because it is not present in the windows crate.
/// see https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm
#[derive(Debug)]
#[repr(C)]
struct SYSTEM_HANDLE_INFORMATION {
NumberOfHandles: usize,
Handles: [SYSTEM_HANDLE_TABLE_ENTRY_INFO; 1],
}
impl SYSTEM_HANDLE_INFORMATION {
fn handles(&self) -> &[SYSTEM_HANDLE_TABLE_ENTRY_INFO] {
// just like we'd do in C: take the address of the first element.
// note: this will blow up if `self.NumberOfHandles` is incorrect.
unsafe { slice::from_raw_parts(&self.Handles[0], self.NumberOfHandles) }
}
}
/// Declare this struct by self, because it is not present in the windows crate.
/// see https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm
#[derive(Debug)]
#[repr(C)]
struct SYSTEM_HANDLE_TABLE_ENTRY_INFO {
UniqueProcessId: c_ushort,
CreatorBackTraceIndex: c_ushort,
ObjectTypeIndex: c_uchar,
HandleAttributes: c_uchar,
HandleValue: c_ushort,
Object: *mut c_void,
GrantedAccess: c_ulong,
}
pub struct VLS<T> {
buffer: Vec<u8>,
_maker: PhantomData<T>,
}
impl<T> VLS<T> {
pub fn new<F>(fun: F) -> windows::core::Result<Self>
where
F: Fn(*mut T, u32, *mut u32) -> windows::core::Result<()>,
{
let mut buffer: Vec<u8> = vec![];
loop {
let mut return_length = 0;
let result = fun(
buffer.as_mut_ptr().cast(),
buffer.len() as u32,
&mut return_length,
);
let return_length = return_length as usize;
match result {
Ok(()) => break,
Err(_) if return_length > buffer.len() => {
buffer.clear();
buffer.reserve_exact(return_length);
buffer.resize(return_length, 0);
}
Err(e) => return Err(e),
}
}
Ok(Self {
buffer,
_maker: PhantomData::default(),
})
}
}
impl<T> Deref for VLS<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { mem::transmute(self.buffer.as_ptr()) }
}
}
pub fn query_handles_by_pid(pid: u32) -> windows::core::Result<Vec<u16>> {
let data: VLS<SYSTEM_HANDLE_INFORMATION> =
VLS::new(|ptr: *mut SYSTEM_HANDLE_INFORMATION, len, size| unsafe {
NtQuerySystemInformation(SystemHandleInformation, ptr.cast(), len, size)
})?;
let data = data
.handles()
.iter()
.filter(|item| item.UniqueProcessId as u32 == pid)
.map(|item| item.HandleValue)
.collect();
Ok(data)
}
pub fn query_object(target_handle: HANDLE, info_class: OBJECT_INFORMATION_CLASS) -> Option<String> {
let result = VLS::new(|ptr: *mut OBJECT_NAME_INFORMATION, len, size| unsafe {
NtQueryObject(target_handle, info_class, Some(ptr.cast()), len, Some(size))
});
match result {
Ok(info) if info.buffer.len() > mem::size_of::<OBJECT_NAME_INFORMATION>() => {
match unsafe { info.Name.Buffer.to_string() } {
Ok(name) => Some(name),
_ => None,
}
}
_ => None,
}
}
pub fn clone_handle(
source_process_handle: HANDLE,
source_handle: HANDLE,
target_process_handle: HANDLE,
) -> windows::core::Result<HANDLE> {
let mut target_handle: MaybeUninit<HANDLE> = MaybeUninit::uninit();
unsafe {
match NtDuplicateObject(
source_process_handle,
source_handle,
target_process_handle,
Some(target_handle.as_mut_ptr().cast()),
0,
0,
0,
) {
Ok(_) => Ok(target_handle.assume_init()),
Err(e) => Err(e),
}
}
}
pub fn close_handle<P>(pid: u32, r#type: &str, name_predicate: P) -> windows::core::Result<u32>
where
P: Fn(&str) -> bool,
{
let handles = query_handles_by_pid(pid)?;
// acquire permission
let source_process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, BOOL(0), pid)? };
let target_process_handle = unsafe { GetCurrentProcess() };
let table: Vec<_> = handles
.iter()
.filter_map(|value| {
let source_handle = HANDLE(*value as isize);
match clone_handle(source_process_handle, source_handle, target_process_handle) {
Ok(target_handle) => Some((source_handle, target_handle)),
Err(_) => None,
}
})
.filter_map(|(source_handle, target_handle)| {
// filter by type
match query_object(target_handle, ObjectTypeInformation) {
Some(name) if &name == r#type => Some((source_handle, target_handle)),
_ => {
let _ = unsafe { CloseHandle(target_handle) };
None
}
}
})
.filter_map(|(source_handle, target_handle)| {
// filter by name
match query_object(target_handle, ObjectNameInformation) {
Some(name) if name_predicate(&name) => Some((source_handle, target_handle)),
_ => {
let _ = unsafe { CloseHandle(target_handle) };
None
}
}
})
.collect();
let mut count = 0;
for (source_handle, mut target_handle) in table {
unsafe {
match DuplicateHandle(
source_process_handle,
source_handle,
target_process_handle,
&mut target_handle as *mut HANDLE,
0,
BOOL(0),
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE,
) {
Ok(_) => {
let _ = CloseHandle(target_handle);
count += 1;
}
Err(e) => eprintln!("DuplicateHandle: failed {}", e),
}
}
}
unsafe {
let _ = CloseHandle(source_process_handle);
let _ = CloseHandle(target_process_handle);
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_basic() {
let count = close_handle(47080, "Mutant", |name| {
name == r"\Sessions\1\BaseNamedObjects\_Some_Mutex_Name"
})
.unwrap();
dbg!(count);
}
}
cargo dependencies is here
windows = { version = "0.51", features = [
"Win32_Foundation",
"Win32_System_Threading",
"Win32_System_SystemServices",
"Wdk_Foundation",
"Wdk_System_SystemInformation"
] }
windows-targets = "0.48.5"