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