wraith/manipulation/remote/
handle.rs1use crate::error::{Result, WraithError};
4use crate::manipulation::syscall::{
5 get_syscall_table, nt_close, nt_success, DirectSyscall,
6};
7
8#[derive(Debug, Clone, Copy)]
10pub struct HandleDuplicateOptions {
11 pub desired_access: u32,
13 pub attributes: u32,
15 pub options: u32,
17}
18
19impl Default for HandleDuplicateOptions {
20 fn default() -> Self {
21 Self {
22 desired_access: 0,
23 attributes: 0,
24 options: DUPLICATE_SAME_ACCESS,
25 }
26 }
27}
28
29impl HandleDuplicateOptions {
30 pub fn same_access() -> Self {
32 Self::default()
33 }
34
35 pub fn close_source() -> Self {
37 Self {
38 desired_access: 0,
39 attributes: 0,
40 options: DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE,
41 }
42 }
43
44 pub fn with_access(access: u32) -> Self {
46 Self {
47 desired_access: access,
48 attributes: 0,
49 options: 0,
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct HandleInfo {
57 pub handle_value: usize,
58 pub object_type: u32,
59 pub granted_access: u32,
60 pub object_name: Option<String>,
61}
62
63pub struct StolenHandle {
65 handle: usize,
66 owns_handle: bool,
67}
68
69impl StolenHandle {
70 pub fn handle(&self) -> usize {
72 self.handle
73 }
74
75 pub fn leak(mut self) -> usize {
77 self.owns_handle = false;
78 self.handle
79 }
80
81 pub unsafe fn from_raw(handle: usize) -> Self {
86 Self {
87 handle,
88 owns_handle: true,
89 }
90 }
91
92 pub unsafe fn from_raw_borrowed(handle: usize) -> Self {
97 Self {
98 handle,
99 owns_handle: false,
100 }
101 }
102}
103
104impl Drop for StolenHandle {
105 fn drop(&mut self) {
106 if self.owns_handle && self.handle != 0 {
107 let _ = nt_close(self.handle);
108 }
109 }
110}
111
112pub fn duplicate_handle(
114 source_process: usize,
115 source_handle: usize,
116 target_process: usize,
117 options: HandleDuplicateOptions,
118) -> Result<StolenHandle> {
119 let table = get_syscall_table()?;
120 let syscall = DirectSyscall::from_table(table, "NtDuplicateObject")?;
121
122 let mut target_handle: usize = 0;
123
124 let status = unsafe {
126 syscall.call_many(&[
127 source_process,
128 source_handle,
129 target_process,
130 &mut target_handle as *mut usize as usize,
131 options.desired_access as usize,
132 options.attributes as usize,
133 options.options as usize,
134 ])
135 };
136
137 if nt_success(status) {
138 Ok(StolenHandle {
139 handle: target_handle,
140 owns_handle: true,
141 })
142 } else {
143 Err(WraithError::HandleDuplicateFailed {
144 reason: format!("NtDuplicateObject failed: {:#x}", status as u32),
145 })
146 }
147}
148
149pub fn steal_handle(
151 source_process: usize,
152 remote_handle: usize,
153 options: HandleDuplicateOptions,
154) -> Result<StolenHandle> {
155 let current_process: usize = usize::MAX; duplicate_handle(source_process, remote_handle, current_process, options)
157}
158
159pub fn enumerate_system_handles(
163 process_id_filter: Option<u32>,
164 object_type_filter: Option<u32>,
165) -> Result<Vec<SystemHandleEntry>> {
166 let table = get_syscall_table()?;
167 let syscall = DirectSyscall::from_table(table, "NtQuerySystemInformation")?;
168
169 const SYSTEM_HANDLE_INFORMATION: u32 = 16;
170 const SYSTEM_EXTENDED_HANDLE_INFORMATION: u32 = 64;
171
172 let mut buffer_size: usize = 1024 * 1024;
174 let mut buffer: Vec<u8>;
175 let mut return_length: u32 = 0;
176
177 loop {
178 buffer = vec![0u8; buffer_size];
179
180 let status = unsafe {
181 syscall.call4(
182 SYSTEM_EXTENDED_HANDLE_INFORMATION as usize,
183 buffer.as_mut_ptr() as usize,
184 buffer.len(),
185 &mut return_length as *mut u32 as usize,
186 )
187 };
188
189 if nt_success(status) {
190 break;
191 }
192
193 if status == 0xC0000004_u32 as i32 {
195 buffer_size = return_length as usize + 0x10000;
196 if buffer_size > 256 * 1024 * 1024 {
197 return Err(WraithError::HandleDuplicateFailed {
198 reason: "buffer too large".into(),
199 });
200 }
201 continue;
202 }
203
204 return Err(WraithError::HandleDuplicateFailed {
205 reason: format!("NtQuerySystemInformation failed: {:#x}", status as u32),
206 });
207 }
208
209 parse_handle_info(&buffer, process_id_filter, object_type_filter)
211}
212
213#[repr(C)]
214struct SystemHandleTableEntryInfoEx {
215 object: usize,
216 unique_process_id: usize,
217 handle_value: usize,
218 granted_access: u32,
219 creator_back_trace_index: u16,
220 object_type_index: u16,
221 handle_attributes: u32,
222 reserved: u32,
223}
224
225#[derive(Debug, Clone)]
227pub struct SystemHandleEntry {
228 pub process_id: u32,
229 pub handle_value: usize,
230 pub object_type: u16,
231 pub granted_access: u32,
232 pub object_address: usize,
233}
234
235fn parse_handle_info(
236 buffer: &[u8],
237 process_id_filter: Option<u32>,
238 object_type_filter: Option<u32>,
239) -> Result<Vec<SystemHandleEntry>> {
240 if buffer.len() < 16 {
241 return Ok(Vec::new());
242 }
243
244 let count = unsafe { *(buffer.as_ptr() as *const usize) };
246 if count == 0 || count > 10_000_000 {
247 return Ok(Vec::new());
248 }
249
250 let entry_size = core::mem::size_of::<SystemHandleTableEntryInfoEx>();
251 let entries_start = 2 * core::mem::size_of::<usize>(); let mut handles = Vec::new();
254
255 for i in 0..count {
256 let offset = entries_start + i * entry_size;
257 if offset + entry_size > buffer.len() {
258 break;
259 }
260
261 let entry = unsafe {
262 &*(buffer.as_ptr().add(offset) as *const SystemHandleTableEntryInfoEx)
263 };
264
265 if let Some(pid) = process_id_filter {
267 if entry.unique_process_id != pid as usize {
268 continue;
269 }
270 }
271
272 if let Some(obj_type) = object_type_filter {
273 if entry.object_type_index as u32 != obj_type {
274 continue;
275 }
276 }
277
278 handles.push(SystemHandleEntry {
279 process_id: entry.unique_process_id as u32,
280 handle_value: entry.handle_value,
281 object_type: entry.object_type_index,
282 granted_access: entry.granted_access,
283 object_address: entry.object,
284 });
285 }
286
287 Ok(handles)
288}
289
290pub fn find_handles_in_process(
292 target_pid: u32,
293 object_type: Option<u32>,
294) -> Result<Vec<SystemHandleEntry>> {
295 enumerate_system_handles(Some(target_pid), object_type)
296}
297
298pub fn steal_process_handle(
302 source_process_handle: usize,
303 remote_handle: usize,
304) -> Result<StolenHandle> {
305 steal_handle(source_process_handle, remote_handle, HandleDuplicateOptions::same_access())
306}
307
308pub mod object_types {
310 pub const PROCESS: u32 = 7;
311 pub const THREAD: u32 = 8;
312 pub const FILE: u32 = 31; pub const SECTION: u32 = 43; pub const KEY: u32 = 19; }
316
317const DUPLICATE_CLOSE_SOURCE: u32 = 0x00000001;
319const DUPLICATE_SAME_ACCESS: u32 = 0x00000002;
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_handle_duplicate_options() {
327 let opts = HandleDuplicateOptions::same_access();
328 assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
329
330 let opts = HandleDuplicateOptions::close_source();
331 assert!(opts.options & DUPLICATE_CLOSE_SOURCE != 0);
332 assert!(opts.options & DUPLICATE_SAME_ACCESS != 0);
333 }
334
335 #[test]
336 fn test_enumerate_own_handles() {
337 let pid = std::process::id();
338 let result = find_handles_in_process(pid, None);
339 assert!(result.is_ok());
340
341 let handles = result.unwrap();
342 assert!(!handles.is_empty(), "should have handles");
344 }
345}