How go get handle from a specific process on windows?

I want to get a specific file handle of a specific process, and then close the handle from outer of the process.

The follow screenshot is from ProcessHacker. What I want like this.

But I can not find a suitable api, can any one help me and show me an example?

that's not possible in general (well, not without hacky solutions). handles are essentially tokens to access certain kernel services, when you pass a handle to another process (e.g. child process will inherit handles that are explicitly set to be inheritable), the kernel actually "duplicate" the handle and increase the handle counter of the particular resource.

what problem are you really trying to solve? why do you want to close a handle of another process? if you describe the real problem, maybe you can achieve the goal from another direction.


there might be "hacky" solutions, but they are not guaranteed to work, for various reasons, mostly security related. but you can still try, if your process have enough privileges.

for example, you can inject code to the target process and create a remote thread which calls CloseHandle on behalf of the process. or you can even install a kernel mode driver to block the target process to access the resources. you get the idea.

Thanks for you reply.
I don't know it is a problems looks "hacky" before. :sweat_smile:
But is it possible list all opening handles of own process?

yes it's possible. it's even possible to list handles to other processes if your process have enough privileges. but as far as I know, you'll have to use undocumented APIs.

specifically, the SYSTEM_INFORMATION_CLASS enum is not fully documented by Microsoft, but there's reverse engineering work on the internet. the missing piece here is SystemHandleInformation, which allows you to get a list of open handles for a specific process.

undocumented Windows APIs is a deep deep rabbit hole, I don't want to get too much into it. you can do your own research if you like. here's a how a debugger does it:

1 Like

Thanks~
I will explore it on my own according to the ideas you provided.

now that I mentioned debugger, I recall you can indeed "close" a handle from another process, the API is actually DuplicateHandle, how surprising! the trick is, this API allows you to pass a flag so that while a handle gets duplicated, the original handle will be closed. check the documentation for details.

but the security requirement of sufficient privileges still applies. otherwise, it's a big security vulnerability.

2 Likes

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"
1 Like