wraith/manipulation/remote/
handle.rs

1//! Handle duplication and stealing operations
2
3#[cfg(all(not(feature = "std"), feature = "alloc"))]
4use alloc::{format, string::String, vec, vec::Vec};
5
6#[cfg(feature = "std")]
7use std::{format, string::String, vec, vec::Vec};
8
9use crate::error::{Result, WraithError};
10use crate::manipulation::syscall::{
11    get_syscall_table, nt_close, nt_success, DirectSyscall,
12};
13
14/// options for handle duplication
15#[derive(Debug, Clone, Copy)]
16pub struct HandleDuplicateOptions {
17    /// desired access rights for the duplicated handle
18    pub desired_access: u32,
19    /// handle attributes (e.g., OBJ_INHERIT)
20    pub attributes: u32,
21    /// options (e.g., DUPLICATE_SAME_ACCESS, DUPLICATE_CLOSE_SOURCE)
22    pub options: u32,
23}
24
25impl Default for HandleDuplicateOptions {
26    fn default() -> Self {
27        Self {
28            desired_access: 0,
29            attributes: 0,
30            options: DUPLICATE_SAME_ACCESS,
31        }
32    }
33}
34
35impl HandleDuplicateOptions {
36    /// duplicate with same access rights
37    pub fn same_access() -> Self {
38        Self::default()
39    }
40
41    /// duplicate and close the source handle
42    pub fn close_source() -> Self {
43        Self {
44            desired_access: 0,
45            attributes: 0,
46            options: DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE,
47        }
48    }
49
50    /// duplicate with specific access rights
51    pub fn with_access(access: u32) -> Self {
52        Self {
53            desired_access: access,
54            attributes: 0,
55            options: 0,
56        }
57    }
58}
59
60/// information about a handle in a process
61#[derive(Debug, Clone)]
62pub struct HandleInfo {
63    pub handle_value: usize,
64    pub object_type: u32,
65    pub granted_access: u32,
66    pub object_name: Option<String>,
67}
68
69/// wrapper for a stolen/duplicated handle
70pub struct StolenHandle {
71    handle: usize,
72    owns_handle: bool,
73}
74
75impl StolenHandle {
76    /// get the raw handle value
77    pub fn handle(&self) -> usize {
78        self.handle
79    }
80
81    /// release ownership of the handle (don't close on drop)
82    pub fn leak(mut self) -> usize {
83        self.owns_handle = false;
84        self.handle
85    }
86
87    /// create from raw handle value
88    ///
89    /// # Safety
90    /// caller must ensure handle is valid
91    pub unsafe fn from_raw(handle: usize) -> Self {
92        Self {
93            handle,
94            owns_handle: true,
95        }
96    }
97
98    /// create without ownership
99    ///
100    /// # Safety
101    /// caller must ensure handle is valid
102    pub unsafe fn from_raw_borrowed(handle: usize) -> Self {
103        Self {
104            handle,
105            owns_handle: false,
106        }
107    }
108}
109
110impl Drop for StolenHandle {
111    fn drop(&mut self) {
112        if self.owns_handle && self.handle != 0 {
113            let _ = nt_close(self.handle);
114        }
115    }
116}
117
118/// duplicate a handle from one process to another
119pub fn duplicate_handle(
120    source_process: usize,
121    source_handle: usize,
122    target_process: usize,
123    options: HandleDuplicateOptions,
124) -> Result<StolenHandle> {
125    let table = get_syscall_table()?;
126    let syscall = DirectSyscall::from_table(table, "NtDuplicateObject")?;
127
128    let mut target_handle: usize = 0;
129
130    // SAFETY: all handles are assumed valid by caller
131    let status = unsafe {
132        syscall.call_many(&[
133            source_process,
134            source_handle,
135            target_process,
136            &mut target_handle as *mut usize as usize,
137            options.desired_access as usize,
138            options.attributes as usize,
139            options.options as usize,
140        ])
141    };
142
143    if nt_success(status) {
144        Ok(StolenHandle {
145            handle: target_handle,
146            owns_handle: true,
147        })
148    } else {
149        Err(WraithError::HandleDuplicateFailed {
150            reason: format!("NtDuplicateObject failed: {:#x}", status as u32),
151        })
152    }
153}
154
155/// steal a handle from a remote process to the current process
156pub fn steal_handle(
157    source_process: usize,
158    remote_handle: usize,
159    options: HandleDuplicateOptions,
160) -> Result<StolenHandle> {
161    let current_process: usize = usize::MAX; // pseudo handle
162    duplicate_handle(source_process, remote_handle, current_process, options)
163}
164
165/// enumerate handles in the system
166///
167/// returns handles matching optional filter criteria
168pub fn enumerate_system_handles(
169    process_id_filter: Option<u32>,
170    object_type_filter: Option<u32>,
171) -> Result<Vec<SystemHandleEntry>> {
172    let table = get_syscall_table()?;
173    let syscall = DirectSyscall::from_table(table, "NtQuerySystemInformation")?;
174
175    const SYSTEM_HANDLE_INFORMATION: u32 = 16;
176    const SYSTEM_EXTENDED_HANDLE_INFORMATION: u32 = 64;
177
178    // start with 1MB buffer, grow if needed
179    let mut buffer_size: usize = 1024 * 1024;
180    let mut buffer: Vec<u8>;
181    let mut return_length: u32 = 0;
182
183    loop {
184        buffer = vec![0u8; buffer_size];
185
186        let status = unsafe {
187            syscall.call4(
188                SYSTEM_EXTENDED_HANDLE_INFORMATION as usize,
189                buffer.as_mut_ptr() as usize,
190                buffer.len(),
191                &mut return_length as *mut u32 as usize,
192            )
193        };
194
195        if nt_success(status) {
196            break;
197        }
198
199        // STATUS_INFO_LENGTH_MISMATCH
200        if status == 0xC0000004_u32 as i32 {
201            buffer_size = return_length as usize + 0x10000;
202            if buffer_size > 256 * 1024 * 1024 {
203                return Err(WraithError::HandleDuplicateFailed {
204                    reason: "buffer too large".into(),
205                });
206            }
207            continue;
208        }
209
210        return Err(WraithError::HandleDuplicateFailed {
211            reason: format!("NtQuerySystemInformation failed: {:#x}", status as u32),
212        });
213    }
214
215    // parse the handle information
216    parse_handle_info(&buffer, process_id_filter, object_type_filter)
217}
218
219#[repr(C)]
220struct SystemHandleTableEntryInfoEx {
221    object: usize,
222    unique_process_id: usize,
223    handle_value: usize,
224    granted_access: u32,
225    creator_back_trace_index: u16,
226    object_type_index: u16,
227    handle_attributes: u32,
228    reserved: u32,
229}
230
231/// entry from system handle enumeration
232#[derive(Debug, Clone)]
233pub struct SystemHandleEntry {
234    pub process_id: u32,
235    pub handle_value: usize,
236    pub object_type: u16,
237    pub granted_access: u32,
238    pub object_address: usize,
239}
240
241fn parse_handle_info(
242    buffer: &[u8],
243    process_id_filter: Option<u32>,
244    object_type_filter: Option<u32>,
245) -> Result<Vec<SystemHandleEntry>> {
246    if buffer.len() < 16 {
247        return Ok(Vec::new());
248    }
249
250    // first usize is number of handles
251    let count = unsafe { *(buffer.as_ptr() as *const usize) };
252    if count == 0 || count > 10_000_000 {
253        return Ok(Vec::new());
254    }
255
256    let entry_size = core::mem::size_of::<SystemHandleTableEntryInfoEx>();
257    let entries_start = 2 * core::mem::size_of::<usize>(); // skip count and reserved
258
259    let mut handles = Vec::new();
260
261    for i in 0..count {
262        let offset = entries_start + i * entry_size;
263        if offset + entry_size > buffer.len() {
264            break;
265        }
266
267        let entry = unsafe {
268            &*(buffer.as_ptr().add(offset) as *const SystemHandleTableEntryInfoEx)
269        };
270
271        // apply filters
272        if let Some(pid) = process_id_filter {
273            if entry.unique_process_id != pid as usize {
274                continue;
275            }
276        }
277
278        if let Some(obj_type) = object_type_filter {
279            if entry.object_type_index as u32 != obj_type {
280                continue;
281            }
282        }
283
284        handles.push(SystemHandleEntry {
285            process_id: entry.unique_process_id as u32,
286            handle_value: entry.handle_value,
287            object_type: entry.object_type_index,
288            granted_access: entry.granted_access,
289            object_address: entry.object,
290        });
291    }
292
293    Ok(handles)
294}
295
296/// find handles of a specific type in a target process
297pub fn find_handles_in_process(
298    target_pid: u32,
299    object_type: Option<u32>,
300) -> Result<Vec<SystemHandleEntry>> {
301    enumerate_system_handles(Some(target_pid), object_type)
302}
303
304/// steal a process handle from another process
305///
306/// useful for bypassing process protection by duplicating an existing handle
307pub fn steal_process_handle(
308    source_process_handle: usize,
309    remote_handle: usize,
310) -> Result<StolenHandle> {
311    steal_handle(source_process_handle, remote_handle, HandleDuplicateOptions::same_access())
312}
313
314/// object types for filtering
315pub mod object_types {
316    pub const PROCESS: u32 = 7;
317    pub const THREAD: u32 = 8;
318    pub const FILE: u32 = 31; // varies by Windows version
319    pub const SECTION: u32 = 43; // varies by Windows version
320    pub const KEY: u32 = 19; // registry key
321}
322
323// duplicate options
324const DUPLICATE_CLOSE_SOURCE: u32 = 0x00000001;
325const DUPLICATE_SAME_ACCESS: u32 = 0x00000002;
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    #[test]
332    fn test_handle_duplicate_options() {
333        let opts = HandleDuplicateOptions::same_access();
334        assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
335
336        let opts = HandleDuplicateOptions::close_source();
337        assert!(opts.options & DUPLICATE_CLOSE_SOURCE != 0);
338        assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
339    }
340
341    #[test]
342    fn test_enumerate_own_handles() {
343        let pid = std::process::id();
344        let result = find_handles_in_process(pid, None);
345        assert!(result.is_ok());
346
347        let handles = result.unwrap();
348        // we should have at least some handles
349        assert!(!handles.is_empty(), "should have handles");
350    }
351}