1use super::{get_syscall_table, nt_success, DirectSyscall};
7use crate::error::{Result, WraithError};
8
9#[repr(C)]
13pub struct ObjectAttributes {
14 pub length: u32,
15 pub root_directory: usize,
16 pub object_name: *const UnicodeString,
17 pub attributes: u32,
18 pub security_descriptor: *const core::ffi::c_void,
19 pub security_quality_of_service: *const core::ffi::c_void,
20}
21
22impl Default for ObjectAttributes {
23 fn default() -> Self {
24 Self {
25 length: core::mem::size_of::<Self>() as u32,
26 root_directory: 0,
27 object_name: core::ptr::null(),
28 attributes: 0,
29 security_descriptor: core::ptr::null(),
30 security_quality_of_service: core::ptr::null(),
31 }
32 }
33}
34
35impl ObjectAttributes {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn case_insensitive() -> Self {
43 Self {
44 attributes: OBJ_CASE_INSENSITIVE,
45 ..Self::default()
46 }
47 }
48}
49
50#[repr(C)]
52#[derive(Debug, Clone, Copy, Default)]
53pub struct ClientId {
54 pub unique_process: usize,
55 pub unique_thread: usize,
56}
57
58impl ClientId {
59 pub fn for_process(pid: u32) -> Self {
61 Self {
62 unique_process: pid as usize,
63 unique_thread: 0,
64 }
65 }
66
67 pub fn for_thread(tid: u32) -> Self {
69 Self {
70 unique_process: 0,
71 unique_thread: tid as usize,
72 }
73 }
74}
75
76#[repr(C)]
78pub struct UnicodeString {
79 pub length: u16,
80 pub maximum_length: u16,
81 pub buffer: *const u16,
82}
83
84pub const OBJ_CASE_INSENSITIVE: u32 = 0x00000040;
86pub const OBJ_INHERIT: u32 = 0x00000002;
87
88pub const PROCESS_ALL_ACCESS: u32 = 0x1F0FFF;
90pub const PROCESS_VM_READ: u32 = 0x0010;
91pub const PROCESS_VM_WRITE: u32 = 0x0020;
92pub const PROCESS_VM_OPERATION: u32 = 0x0008;
93pub const PROCESS_QUERY_INFORMATION: u32 = 0x0400;
94pub const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
95
96pub const THREAD_ALL_ACCESS: u32 = 0x1F03FF;
98pub const THREAD_SET_INFORMATION: u32 = 0x0020;
99pub const THREAD_QUERY_INFORMATION: u32 = 0x0040;
100
101pub const THREAD_HIDE_FROM_DEBUGGER: u32 = 17;
103
104pub const MEM_COMMIT: u32 = 0x1000;
106pub const MEM_RESERVE: u32 = 0x2000;
107pub const MEM_RELEASE: u32 = 0x8000;
108
109pub const PAGE_NOACCESS: u32 = 0x01;
111pub const PAGE_READONLY: u32 = 0x02;
112pub const PAGE_READWRITE: u32 = 0x04;
113pub const PAGE_WRITECOPY: u32 = 0x08;
114pub const PAGE_EXECUTE: u32 = 0x10;
115pub const PAGE_EXECUTE_READ: u32 = 0x20;
116pub const PAGE_EXECUTE_READWRITE: u32 = 0x40;
117pub const PAGE_EXECUTE_WRITECOPY: u32 = 0x80;
118pub const PAGE_GUARD: u32 = 0x100;
119
120pub const CURRENT_PROCESS: usize = usize::MAX; pub const CURRENT_THREAD: usize = usize::MAX - 1; pub fn nt_close(handle: usize) -> Result<()> {
126 let table = get_syscall_table()?;
127 let syscall = DirectSyscall::from_table(table, "NtClose")?;
128
129 let status = unsafe { syscall.call1(handle) };
131
132 if nt_success(status) {
133 Ok(())
134 } else {
135 Err(WraithError::SyscallFailed {
136 name: "NtClose".into(),
137 status,
138 })
139 }
140}
141
142pub fn nt_open_process(
144 desired_access: u32,
145 object_attributes: &ObjectAttributes,
146 client_id: &ClientId,
147) -> Result<usize> {
148 let mut handle: usize = 0;
149
150 let table = get_syscall_table()?;
151 let syscall = DirectSyscall::from_table(table, "NtOpenProcess")?;
152
153 let status = unsafe {
155 syscall.call4(
156 &mut handle as *mut usize as usize,
157 desired_access as usize,
158 object_attributes as *const _ as usize,
159 client_id as *const _ as usize,
160 )
161 };
162
163 if nt_success(status) {
164 Ok(handle)
165 } else {
166 Err(WraithError::SyscallFailed {
167 name: "NtOpenProcess".into(),
168 status,
169 })
170 }
171}
172
173pub fn nt_read_virtual_memory(
175 process_handle: usize,
176 base_address: usize,
177 buffer: &mut [u8],
178) -> Result<usize> {
179 let mut bytes_read: usize = 0;
180
181 let table = get_syscall_table()?;
182 let syscall = DirectSyscall::from_table(table, "NtReadVirtualMemory")?;
183
184 let status = unsafe {
186 syscall.call5(
187 process_handle,
188 base_address,
189 buffer.as_mut_ptr() as usize,
190 buffer.len(),
191 &mut bytes_read as *mut usize as usize,
192 )
193 };
194
195 if nt_success(status) {
196 Ok(bytes_read)
197 } else {
198 Err(WraithError::SyscallFailed {
199 name: "NtReadVirtualMemory".into(),
200 status,
201 })
202 }
203}
204
205pub fn nt_write_virtual_memory(
207 process_handle: usize,
208 base_address: usize,
209 buffer: &[u8],
210) -> Result<usize> {
211 let mut bytes_written: usize = 0;
212
213 let table = get_syscall_table()?;
214 let syscall = DirectSyscall::from_table(table, "NtWriteVirtualMemory")?;
215
216 let status = unsafe {
218 syscall.call5(
219 process_handle,
220 base_address,
221 buffer.as_ptr() as usize,
222 buffer.len(),
223 &mut bytes_written as *mut usize as usize,
224 )
225 };
226
227 if nt_success(status) {
228 Ok(bytes_written)
229 } else {
230 Err(WraithError::SyscallFailed {
231 name: "NtWriteVirtualMemory".into(),
232 status,
233 })
234 }
235}
236
237pub fn nt_allocate_virtual_memory(
239 process_handle: usize,
240 preferred_base: usize,
241 size: usize,
242 allocation_type: u32,
243 protect: u32,
244) -> Result<(usize, usize)> {
245 let mut base_address = preferred_base;
246 let mut region_size = size;
247
248 let table = get_syscall_table()?;
249 let syscall = DirectSyscall::from_table(table, "NtAllocateVirtualMemory")?;
250
251 let status = unsafe {
253 syscall.call6(
254 process_handle,
255 &mut base_address as *mut usize as usize,
256 0, &mut region_size as *mut usize as usize,
258 allocation_type as usize,
259 protect as usize,
260 )
261 };
262
263 if nt_success(status) {
264 Ok((base_address, region_size))
265 } else {
266 Err(WraithError::SyscallFailed {
267 name: "NtAllocateVirtualMemory".into(),
268 status,
269 })
270 }
271}
272
273pub fn nt_free_virtual_memory(
275 process_handle: usize,
276 base_address: usize,
277 free_type: u32,
278) -> Result<()> {
279 let mut base = base_address;
280 let mut size: usize = 0;
281
282 let table = get_syscall_table()?;
283 let syscall = DirectSyscall::from_table(table, "NtFreeVirtualMemory")?;
284
285 let status = unsafe {
287 syscall.call4(
288 process_handle,
289 &mut base as *mut usize as usize,
290 &mut size as *mut usize as usize,
291 free_type as usize,
292 )
293 };
294
295 if nt_success(status) {
296 Ok(())
297 } else {
298 Err(WraithError::SyscallFailed {
299 name: "NtFreeVirtualMemory".into(),
300 status,
301 })
302 }
303}
304
305pub fn nt_protect_virtual_memory(
307 process_handle: usize,
308 base_address: usize,
309 size: usize,
310 new_protect: u32,
311) -> Result<u32> {
312 let mut base = base_address;
313 let mut region_size = size;
314 let mut old_protect: u32 = 0;
315
316 let table = get_syscall_table()?;
317 let syscall = DirectSyscall::from_table(table, "NtProtectVirtualMemory")?;
318
319 let status = unsafe {
321 syscall.call5(
322 process_handle,
323 &mut base as *mut usize as usize,
324 &mut region_size as *mut usize as usize,
325 new_protect as usize,
326 &mut old_protect as *mut u32 as usize,
327 )
328 };
329
330 if nt_success(status) {
331 Ok(old_protect)
332 } else {
333 Err(WraithError::SyscallFailed {
334 name: "NtProtectVirtualMemory".into(),
335 status,
336 })
337 }
338}
339
340pub fn nt_set_information_thread(
344 thread_handle: usize,
345 information_class: u32,
346 thread_information: *const core::ffi::c_void,
347 thread_information_length: u32,
348) -> Result<()> {
349 let table = get_syscall_table()?;
350 let syscall = DirectSyscall::from_table(table, "NtSetInformationThread")?;
351
352 let status = unsafe {
354 syscall.call4(
355 thread_handle,
356 information_class as usize,
357 thread_information as usize,
358 thread_information_length as usize,
359 )
360 };
361
362 if nt_success(status) {
363 Ok(())
364 } else {
365 Err(WraithError::SyscallFailed {
366 name: "NtSetInformationThread".into(),
367 status,
368 })
369 }
370}
371
372pub fn hide_thread_from_debugger() -> Result<()> {
374 nt_set_information_thread(
375 CURRENT_THREAD,
376 THREAD_HIDE_FROM_DEBUGGER,
377 core::ptr::null(),
378 0,
379 )
380}
381
382pub fn nt_query_system_information(
384 information_class: u32,
385 buffer: &mut [u8],
386) -> Result<u32> {
387 let mut return_length: u32 = 0;
388
389 let table = get_syscall_table()?;
390 let syscall = DirectSyscall::from_table(table, "NtQuerySystemInformation")?;
391
392 let status = unsafe {
394 syscall.call4(
395 information_class as usize,
396 buffer.as_mut_ptr() as usize,
397 buffer.len(),
398 &mut return_length as *mut u32 as usize,
399 )
400 };
401
402 if nt_success(status) {
403 Ok(return_length)
404 } else {
405 Err(WraithError::SyscallFailed {
406 name: "NtQuerySystemInformation".into(),
407 status,
408 })
409 }
410}
411
412pub fn nt_query_information_process(
414 process_handle: usize,
415 information_class: u32,
416 buffer: &mut [u8],
417) -> Result<u32> {
418 let mut return_length: u32 = 0;
419
420 let table = get_syscall_table()?;
421 let syscall = DirectSyscall::from_table(table, "NtQueryInformationProcess")?;
422
423 let status = unsafe {
425 syscall.call5(
426 process_handle,
427 information_class as usize,
428 buffer.as_mut_ptr() as usize,
429 buffer.len(),
430 &mut return_length as *mut u32 as usize,
431 )
432 };
433
434 if nt_success(status) {
435 Ok(return_length)
436 } else {
437 Err(WraithError::SyscallFailed {
438 name: "NtQueryInformationProcess".into(),
439 status,
440 })
441 }
442}
443
444pub fn nt_query_virtual_memory(
446 process_handle: usize,
447 base_address: usize,
448 information_class: u32,
449 buffer: &mut [u8],
450) -> Result<usize> {
451 let mut return_length: usize = 0;
452
453 let table = get_syscall_table()?;
454 let syscall = DirectSyscall::from_table(table, "NtQueryVirtualMemory")?;
455
456 let status = unsafe {
458 syscall.call6(
459 process_handle,
460 base_address,
461 information_class as usize,
462 buffer.as_mut_ptr() as usize,
463 buffer.len(),
464 &mut return_length as *mut usize as usize,
465 )
466 };
467
468 if nt_success(status) {
469 Ok(return_length)
470 } else {
471 Err(WraithError::SyscallFailed {
472 name: "NtQueryVirtualMemory".into(),
473 status,
474 })
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 #[test]
483 fn test_nt_close_invalid() {
484 let result = nt_close(0xDEADBEEF);
485 assert!(result.is_err());
486 }
487
488 #[test]
489 fn test_allocate_and_free() {
490 let result = nt_allocate_virtual_memory(
492 CURRENT_PROCESS,
493 0, 4096,
495 MEM_COMMIT | MEM_RESERVE,
496 PAGE_READWRITE,
497 );
498
499 if let Ok((base, _size)) = result {
500 assert!(base != 0, "should have allocated memory");
501
502 let free_result = nt_free_virtual_memory(CURRENT_PROCESS, base, MEM_RELEASE);
504 assert!(free_result.is_ok(), "should free memory");
505 }
506 }
507
508 #[test]
509 fn test_protect_memory() {
510 let (base, _) = nt_allocate_virtual_memory(
512 CURRENT_PROCESS,
513 0,
514 4096,
515 MEM_COMMIT | MEM_RESERVE,
516 PAGE_READWRITE,
517 )
518 .expect("should allocate");
519
520 let old = nt_protect_virtual_memory(CURRENT_PROCESS, base, 4096, PAGE_READONLY)
522 .expect("should change protection");
523
524 assert_eq!(old, PAGE_READWRITE);
525
526 let _ = nt_free_virtual_memory(CURRENT_PROCESS, base, MEM_RELEASE);
528 }
529}