use std::ffi::c_void;
use winapi::shared::minwindef::FALSE;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcess};
use winapi::um::winnt::{DUPLICATE_SAME_ACCESS, HANDLE, PROCESS_DUP_HANDLE};
const SYSTEM_EXTENDED_HANDLE_INFORMATION: i32 = 64;
const OBJECT_TYPE_INFORMATION: i32 = 2;
const OBJECT_NAME_INFORMATION: i32 = 1;
const STATUS_SUCCESS: i32 = 0;
const STATUS_INFO_LENGTH_MISMATCH: i32 = 0xC0000004u32 as i32;
#[repr(C)]
#[derive(Copy, Clone)]
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,
}
#[repr(C)]
struct SystemHandleInformationExHeader {
number_of_handles: usize, reserved: usize, }
#[repr(C)]
#[derive(Copy, Clone)]
struct UnicodeString {
length: u16, maximum_length: u16, buffer: *mut u16,
}
#[link(name = "ntdll")]
extern "system" {
fn NtQuerySystemInformation(
system_information_class: i32,
system_information: *mut c_void,
system_information_length: u32,
return_length: *mut u32,
) -> i32;
fn NtQueryObject(
handle: HANDLE,
object_information_class: i32,
object_information: *mut c_void,
object_information_length: u32,
return_length: *mut u32,
) -> i32;
}
pub fn read_process_file_handles(pid: u32) -> std::io::Result<Vec<String>> {
if pid == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"pid 0 is the system idle process  not queryable",
));
}
let raw = query_system_handles()?;
let header_size = std::mem::size_of::<SystemHandleInformationExHeader>();
if raw.len() < header_size {
return Err(std::io::Error::other(
"NtQuerySystemInformation returned < header bytes",
));
}
let header =
unsafe { std::ptr::read(raw.as_ptr() as *const SystemHandleInformationExHeader) };
let entry_size = std::mem::size_of::<SystemHandleTableEntryInfoEx>();
let max_entries = (raw.len() - header_size) / entry_size;
let entries_count = std::cmp::min(header.number_of_handles, max_entries);
let target_proc = unsafe { OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid) };
if target_proc.is_null() {
return Err(std::io::Error::last_os_error());
}
let target_guard = ProcHandle(target_proc);
let mut handles = Vec::new();
let entries_ptr =
unsafe { raw.as_ptr().add(header_size) } as *const SystemHandleTableEntryInfoEx;
for i in 0..entries_count {
let entry = unsafe { std::ptr::read(entries_ptr.add(i)) };
if entry.unique_process_id as u32 != pid {
continue;
}
if let Some(path) = resolve_entry(target_guard.0, entry.handle_value as HANDLE) {
handles.push(path);
}
}
Ok(handles)
}
fn query_system_handles() -> std::io::Result<Vec<u8>> {
let mut size: u32 = 256 * 1024;
loop {
let mut buf = vec![0u8; size as usize];
let mut returned: u32 = 0;
let status = unsafe {
NtQuerySystemInformation(
SYSTEM_EXTENDED_HANDLE_INFORMATION,
buf.as_mut_ptr() as *mut c_void,
size,
&mut returned,
)
};
if status == STATUS_SUCCESS {
let used = returned.max(1) as usize;
buf.truncate(used.min(buf.len()));
return Ok(buf);
}
if status == STATUS_INFO_LENGTH_MISMATCH {
if size >= 256 * 1024 * 1024 {
return Err(std::io::Error::other(format!(
"NtQuerySystemInformation handle table exceeds 256 MiB (returned hint={returned})",
)));
}
size = size
.saturating_mul(2)
.max(returned.saturating_add(64 * 1024));
continue;
}
return Err(std::io::Error::other(format!(
"NtQuerySystemInformation returned status=0x{:08x}",
status as u32,
)));
}
}
fn resolve_entry(target_proc: HANDLE, foreign_handle: HANDLE) -> Option<String> {
use winapi::um::handleapi::DuplicateHandle;
let mut local_handle: HANDLE = std::ptr::null_mut();
let ok = unsafe {
DuplicateHandle(
target_proc,
foreign_handle,
GetCurrentProcess(),
&mut local_handle,
0,
FALSE,
DUPLICATE_SAME_ACCESS,
)
};
if ok == FALSE || local_handle.is_null() {
return None;
}
let local_guard = ProcHandle(local_handle);
let type_name = query_object_string(local_guard.0, OBJECT_TYPE_INFORMATION)?;
if type_name != "File" {
return None;
}
query_object_string(local_guard.0, OBJECT_NAME_INFORMATION).filter(|s| !s.is_empty())
}
fn query_object_string(handle: HANDLE, info_class: i32) -> Option<String> {
let mut size: u32 = 4 * 1024;
loop {
let mut buf = vec![0u8; size as usize];
let mut returned: u32 = 0;
let status = unsafe {
NtQueryObject(
handle,
info_class,
buf.as_mut_ptr() as *mut c_void,
size,
&mut returned,
)
};
if status == STATUS_SUCCESS {
buf.truncate((returned as usize).min(buf.len()));
return parse_leading_unicode_string(&buf);
}
if status == STATUS_INFO_LENGTH_MISMATCH {
if size >= 1024 * 1024 {
return None;
}
size = size.saturating_mul(2).max(returned);
continue;
}
return None;
}
}
fn parse_leading_unicode_string(buf: &[u8]) -> Option<String> {
let header_size = std::mem::size_of::<UnicodeString>();
if buf.len() < header_size {
return None;
}
let us = unsafe { std::ptr::read(buf.as_ptr() as *const UnicodeString) };
let len_bytes = us.length as usize;
if len_bytes == 0 || us.buffer.is_null() {
return Some(String::new());
}
let buf_start = buf.as_ptr() as usize;
let buf_end = buf_start + buf.len();
let buffer_addr = us.buffer as usize;
if buffer_addr < buf_start || buffer_addr.saturating_add(len_bytes) > buf_end {
return None;
}
let wide: &[u16] =
unsafe { std::slice::from_raw_parts(us.buffer as *const u16, len_bytes / 2) };
Some(String::from_utf16_lossy(wide))
}
struct ProcHandle(HANDLE);
impl Drop for ProcHandle {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { CloseHandle(self.0) };
}
}
}