1use std::collections::HashMap;
4use std::mem::size_of;
5use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
6use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
7use windows::Win32::System::Threading::{
8 PEB, PROCESS_BASIC_INFORMATION, RTL_USER_PROCESS_PARAMETERS,
9};
10
11use super::processes::Process;
12use super::types::{ImagePath, ProcessParameters};
13use crate::error::{Error, ProcessError, ProcessOpenError, Result};
14
15#[repr(C)]
17struct UNICODE_STRING {
18 length: u16,
19 maximum_length: u16,
20 buffer: *mut u16,
21}
22
23const PROCESS_BASIC_INFORMATION_SIZE: usize = size_of::<PROCESS_BASIC_INFORMATION>();
25const PEB_SIZE: usize = size_of::<PEB>();
26const RTL_USER_PROCESS_PARAMETERS_SIZE: usize = size_of::<RTL_USER_PROCESS_PARAMETERS>();
27
28#[repr(C)]
33struct RTL_USER_PROCESS_PARAMETERS_PARTIAL {
34 _pad1: [u8; 32], _flags: u32, _pad2: [u8; 8], stdin: *mut u8, stdout: *mut u8, stderr: *mut u8, image_path_name: UNICODE_STRING, command_line: UNICODE_STRING, environment: *mut u16, }
44
45impl Process {
46 pub fn command_line(&self) -> Result<String> {
50 let mut buffer = Vec::with_capacity(8192);
51 self.command_line_with_buffer(&mut buffer)
52 }
53
54 pub fn command_line_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<String> {
56 let params = self.read_process_parameters(out_buffer)?;
57 Ok(params.command_line)
58 }
59
60 pub fn environment(&self) -> Result<HashMap<String, String>> {
62 let mut buffer = Vec::with_capacity(8192);
63 self.environment_with_buffer(&mut buffer)
64 }
65
66 pub fn environment_with_buffer(
68 &self,
69 out_buffer: &mut Vec<u8>,
70 ) -> Result<HashMap<String, String>> {
71 let peb_addr = self.read_peb_address(out_buffer)?;
73
74 out_buffer.clear();
75 if out_buffer.capacity() < PEB_SIZE {
76 out_buffer.reserve(PEB_SIZE - out_buffer.capacity());
77 }
78 unsafe {
79 out_buffer.set_len(PEB_SIZE);
80 }
81
82 let mut bytes_read = 0;
83 unsafe {
84 ReadProcessMemory(
85 self.as_raw_handle(),
86 peb_addr as _,
87 out_buffer.as_mut_ptr() as _,
88 PEB_SIZE,
89 Some(&mut bytes_read),
90 )
91 }
92 .map_err(|e| {
93 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
94 self.id().as_u32(),
95 "Failed to read PEB",
96 e.code().0,
97 )))
98 })?;
99
100 let peb = unsafe { &*(out_buffer.as_ptr() as *const PEB) };
101 let params_addr = peb.ProcessParameters as usize;
102
103 out_buffer.clear();
105 if out_buffer.capacity() < RTL_USER_PROCESS_PARAMETERS_SIZE {
106 out_buffer.reserve(RTL_USER_PROCESS_PARAMETERS_SIZE - out_buffer.capacity());
107 }
108 unsafe {
109 out_buffer.set_len(RTL_USER_PROCESS_PARAMETERS_SIZE);
110 }
111
112 bytes_read = 0;
113 unsafe {
114 ReadProcessMemory(
115 self.as_raw_handle(),
116 params_addr as _,
117 out_buffer.as_mut_ptr() as _,
118 RTL_USER_PROCESS_PARAMETERS_SIZE,
119 Some(&mut bytes_read),
120 )
121 }
122 .map_err(|e| {
123 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
124 self.id().as_u32(),
125 "Failed to read process parameters",
126 e.code().0,
127 )))
128 })?;
129
130 let params =
132 unsafe { &*(out_buffer.as_ptr() as *const RTL_USER_PROCESS_PARAMETERS_PARTIAL) };
133
134 self.read_environment_block(params.environment as usize, out_buffer)
136 }
137
138 pub fn parameters(&self) -> Result<ProcessParameters> {
140 let mut buffer = Vec::with_capacity(8192);
141 self.parameters_with_buffer(&mut buffer)
142 }
143
144 pub fn parameters_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<ProcessParameters> {
146 self.read_process_parameters(out_buffer)
147 }
148
149 fn read_peb_address(&self, buffer: &mut Vec<u8>) -> Result<usize> {
151 buffer.clear();
152 if buffer.capacity() < PROCESS_BASIC_INFORMATION_SIZE {
153 buffer.reserve(PROCESS_BASIC_INFORMATION_SIZE - buffer.capacity());
154 }
155 unsafe {
156 buffer.set_len(PROCESS_BASIC_INFORMATION_SIZE);
157 }
158
159 let mut return_length = 0u32;
160 unsafe {
161 NtQueryInformationProcess(
162 self.as_raw_handle(),
163 ProcessBasicInformation,
164 buffer.as_mut_ptr() as _,
165 PROCESS_BASIC_INFORMATION_SIZE as u32,
166 &mut return_length,
167 )
168 .ok()
169 }
170 .map_err(|e| {
171 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
172 self.id().as_u32(),
173 "Failed to query process information",
174 e.code().0,
175 )))
176 })?;
177
178 let basic_info = unsafe { &*(buffer.as_ptr() as *const PROCESS_BASIC_INFORMATION) };
179 Ok(basic_info.PebBaseAddress as usize)
180 }
181
182 fn read_process_parameters(&self, buffer: &mut Vec<u8>) -> Result<ProcessParameters> {
184 let peb_addr = self.read_peb_address(buffer)?;
185
186 buffer.clear();
188 if buffer.capacity() < PEB_SIZE {
189 buffer.reserve(PEB_SIZE - buffer.capacity());
190 }
191 unsafe {
192 buffer.set_len(PEB_SIZE);
193 }
194
195 let mut bytes_read = 0;
196 unsafe {
197 ReadProcessMemory(
198 self.as_raw_handle(),
199 peb_addr as _,
200 buffer.as_mut_ptr() as _,
201 PEB_SIZE,
202 Some(&mut bytes_read),
203 )
204 }
205 .map_err(|e| {
206 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
207 self.id().as_u32(),
208 "Failed to read PEB for parameters",
209 e.code().0,
210 )))
211 })?;
212
213 let peb = unsafe { &*(buffer.as_ptr() as *const PEB) };
214 let params_addr = peb.ProcessParameters as usize;
215
216 buffer.clear();
218 if buffer.capacity() < RTL_USER_PROCESS_PARAMETERS_SIZE {
219 buffer.reserve(RTL_USER_PROCESS_PARAMETERS_SIZE - buffer.capacity());
220 }
221 unsafe {
222 buffer.set_len(RTL_USER_PROCESS_PARAMETERS_SIZE);
223 }
224
225 bytes_read = 0;
226 unsafe {
227 ReadProcessMemory(
228 self.as_raw_handle(),
229 params_addr as _,
230 buffer.as_mut_ptr() as _,
231 RTL_USER_PROCESS_PARAMETERS_SIZE,
232 Some(&mut bytes_read),
233 )
234 }
235 .map_err(|e| {
236 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
237 self.id().as_u32(),
238 "Failed to read RTL_USER_PROCESS_PARAMETERS",
239 e.code().0,
240 )))
241 })?;
242
243 let params = unsafe { &*(buffer.as_ptr() as *const RTL_USER_PROCESS_PARAMETERS) };
244
245 let cmd_line = self.read_unicode_string(
247 params.CommandLine.Buffer.0 as usize,
248 params.CommandLine.Length as usize,
249 buffer,
250 )?;
251
252 let image_path = self.read_unicode_string(
254 params.ImagePathName.Buffer.0 as usize,
255 params.ImagePathName.Length as usize,
256 buffer,
257 )?;
258
259 Ok(ProcessParameters {
260 command_line: cmd_line,
261 current_directory: String::new(), image_path: ImagePath::from_str(&image_path),
263 })
264 }
265
266 fn read_unicode_string(
268 &self,
269 addr: usize,
270 length: usize,
271 buffer: &mut Vec<u8>,
272 ) -> Result<String> {
273 if addr == 0 || length == 0 {
274 return Ok(String::new());
275 }
276
277 buffer.clear();
278 if buffer.capacity() < length {
279 buffer.reserve(length - buffer.capacity());
280 }
281 unsafe {
282 buffer.set_len(length);
283 }
284
285 let mut bytes_read = 0;
286 unsafe {
287 ReadProcessMemory(
288 self.as_raw_handle(),
289 addr as _,
290 buffer.as_mut_ptr() as _,
291 length,
292 Some(&mut bytes_read),
293 )
294 }
295 .map_err(|e| {
296 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
297 self.id().as_u32(),
298 "Failed to read string from process memory",
299 e.code().0,
300 )))
301 })?;
302
303 let u16_slice =
304 unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u16, bytes_read / 2) };
305
306 let end = u16_slice
308 .iter()
309 .position(|&c| c == 0)
310 .unwrap_or(u16_slice.len());
311
312 Ok(String::from_utf16_lossy(&u16_slice[..end]))
313 }
314
315 fn read_environment_block(
318 &self,
319 addr: usize,
320 buffer: &mut Vec<u8>,
321 ) -> Result<HashMap<String, String>> {
322 if addr == 0 {
323 return Ok(HashMap::new());
324 }
325
326 let max_size = 65536;
328 buffer.clear();
329 buffer.resize(max_size, 0);
330
331 let mut bytes_read = 0;
332 unsafe {
333 ReadProcessMemory(
334 self.as_raw_handle(),
335 addr as _,
336 buffer.as_mut_ptr() as _,
337 max_size,
338 Some(&mut bytes_read),
339 )
340 }
341 .map_err(|e| {
342 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
343 self.id().as_u32(),
344 "Failed to read environment block",
345 e.code().0,
346 )))
347 })?;
348
349 let u16_data =
351 unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u16, bytes_read / 2) };
352
353 let mut env_vars = HashMap::new();
354 let mut pos = 0;
355
356 while pos < u16_data.len() {
359 let start = pos;
361 while pos < u16_data.len() && u16_data[pos] != 0 {
362 pos += 1;
363 }
364
365 if start == pos {
367 break;
368 }
369
370 let entry = String::from_utf16_lossy(&u16_data[start..pos]);
372
373 if let Some(eq_pos) = entry.find('=') {
375 let key = entry[..eq_pos].to_string();
376 let value = entry[eq_pos + 1..].to_string();
377 env_vars.insert(key, value);
378 }
379
380 pos += 1; }
382
383 Ok(env_vars)
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 fn str_to_u16_vec(s: &str) -> Vec<u16> {
393 s.encode_utf16().collect()
394 }
395
396 fn create_env_block(pairs: &[(&str, &str)]) -> Vec<u8> {
398 let mut block = Vec::new();
399
400 for (key, value) in pairs {
401 let entry = format!("{}={}", key, value);
402 for u16_val in entry.encode_utf16() {
403 block.push((u16_val & 0xFF) as u8);
404 block.push(((u16_val >> 8) & 0xFF) as u8);
405 }
406 block.push(0);
408 block.push(0);
409 }
410
411 block.push(0);
413 block.push(0);
414
415 block
416 }
417
418 #[test]
419 fn test_parse_simple_environment() {
420 let env_block = create_env_block(&[("PATH", "C:\\Windows"), ("TEMP", "C:\\Temp")]);
422
423 let u16_data: Vec<u16> = env_block
425 .chunks_exact(2)
426 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
427 .collect();
428
429 let mut env_vars: HashMap<String, String> = HashMap::new();
431 let mut pos = 0;
432
433 while pos < u16_data.len() {
434 let start = pos;
435 while pos < u16_data.len() && u16_data[pos] != 0 {
436 pos += 1;
437 }
438
439 if start == pos {
440 break;
441 }
442
443 let entry = String::from_utf16_lossy(&u16_data[start..pos]);
444 if let Some(eq_pos) = entry.find('=') {
445 let key = entry[..eq_pos].to_string();
446 let value = entry[eq_pos + 1..].to_string();
447 env_vars.insert(key, value);
448 }
449
450 pos += 1;
451 }
452
453 assert_eq!(
454 env_vars.get("PATH").map(|s| s.as_str()),
455 Some("C:\\Windows")
456 );
457 assert_eq!(env_vars.get("TEMP").map(|s| s.as_str()), Some("C:\\Temp"));
458 }
459
460 #[test]
461 fn test_parse_environment_with_equals_in_value() {
462 let env_block = create_env_block(&[("URL", "https://example.com?foo=bar")]);
464
465 let u16_data: Vec<u16> = env_block
466 .chunks_exact(2)
467 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
468 .collect();
469
470 let mut env_vars: HashMap<String, String> = HashMap::new();
471 let mut pos = 0;
472
473 while pos < u16_data.len() {
474 let start = pos;
475 while pos < u16_data.len() && u16_data[pos] != 0 {
476 pos += 1;
477 }
478
479 if start == pos {
480 break;
481 }
482
483 let entry = String::from_utf16_lossy(&u16_data[start..pos]);
484 if let Some(eq_pos) = entry.find('=') {
485 let key = entry[..eq_pos].to_string();
486 let value = entry[eq_pos + 1..].to_string();
487 env_vars.insert(key, value);
488 }
489
490 pos += 1;
491 }
492
493 assert_eq!(
494 env_vars.get("URL").map(|s| s.as_str()),
495 Some("https://example.com?foo=bar")
496 );
497 }
498
499 #[test]
500 fn test_parse_environment_many_variables() {
501 let pairs = vec![
503 ("PATH", "C:\\Windows"),
504 ("TEMP", "C:\\Temp"),
505 ("WINDIR", "C:\\Windows"),
506 ("USERNAME", "Admin"),
507 ("COMPUTERNAME", "DESKTOP"),
508 ("PROCESSOR_ARCHITECTURE", "AMD64"),
509 ];
510
511 let env_block = create_env_block(&pairs);
512 let u16_data: Vec<u16> = env_block
513 .chunks_exact(2)
514 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
515 .collect();
516
517 let mut env_vars = HashMap::new();
518 let mut pos = 0;
519
520 while pos < u16_data.len() {
521 let start = pos;
522 while pos < u16_data.len() && u16_data[pos] != 0 {
523 pos += 1;
524 }
525
526 if start == pos {
527 break;
528 }
529
530 let entry = String::from_utf16_lossy(&u16_data[start..pos]);
531 if let Some(eq_pos) = entry.find('=') {
532 let key = entry[..eq_pos].to_string();
533 let value = entry[eq_pos + 1..].to_string();
534 env_vars.insert(key, value);
535 }
536
537 pos += 1;
538 }
539
540 assert_eq!(env_vars.len(), 6);
541 assert_eq!(
542 env_vars.get("PATH").map(|s| s.as_str()),
543 Some("C:\\Windows")
544 );
545 assert_eq!(env_vars.get("USERNAME").map(|s| s.as_str()), Some("Admin"));
546 assert_eq!(
547 env_vars.get("PROCESSOR_ARCHITECTURE").map(|s| s.as_str()),
548 Some("AMD64")
549 );
550 }
551
552 #[test]
553 fn test_parse_environment_unicode_values() {
554 let env_block = create_env_block(&[("TEST", "Hello🌍World")]);
556
557 let u16_data: Vec<u16> = env_block
558 .chunks_exact(2)
559 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
560 .collect();
561
562 let mut env_vars: HashMap<String, String> = HashMap::new();
563 let mut pos = 0;
564
565 while pos < u16_data.len() {
566 let start = pos;
567 while pos < u16_data.len() && u16_data[pos] != 0 {
568 pos += 1;
569 }
570
571 if start == pos {
572 break;
573 }
574
575 let entry = String::from_utf16_lossy(&u16_data[start..pos]);
576 if let Some(eq_pos) = entry.find('=') {
577 let key = entry[..eq_pos].to_string();
578 let value = entry[eq_pos + 1..].to_string();
579 env_vars.insert(key, value);
580 }
581
582 pos += 1;
583 }
584
585 assert_eq!(
586 env_vars.get("TEST").map(|s| s.as_str()),
587 Some("Hello🌍World")
588 );
589 }
590
591 #[test]
592 fn test_unicode_string_struct_layout() {
593 assert_eq!(
595 size_of::<UNICODE_STRING>(),
596 16,
597 "UNICODE_STRING should be 16 bytes"
598 );
599 }
600
601 #[test]
602 fn test_rtl_user_process_parameters_partial_layout() {
603 assert!(
606 size_of::<RTL_USER_PROCESS_PARAMETERS_PARTIAL>() >= 0x6C,
607 "RTL_USER_PROCESS_PARAMETERS_PARTIAL should be at least 0x6C bytes"
608 );
609 }
610
611 #[test]
612 fn test_str_to_u16_conversion() {
613 let s = "PATH";
614 let u16_vec = str_to_u16_vec(s);
615 let recovered = String::from_utf16_lossy(&u16_vec);
616 assert_eq!(recovered, s);
617 }
618
619 #[test]
620 fn test_environment_block_structure() {
621 let block = create_env_block(&[("A", "B"), ("C", "D")]);
623
624 assert!(!block.is_empty(), "Block should not be empty");
626
627 if block.len() >= 6 {
629 let a_char = u16::from_le_bytes([block[0], block[1]]);
630 let eq_char = u16::from_le_bytes([block[2], block[3]]);
631 let b_char = u16::from_le_bytes([block[4], block[5]]);
632
633 assert_eq!(a_char, b'A' as u16, "First char should be 'A'");
634 assert_eq!(eq_char, b'=' as u16, "Second should be '='");
635 assert_eq!(b_char, b'B' as u16, "Third should be 'B'");
636 }
637 }
638
639 #[test]
643 #[ignore] fn test_command_line_of_current_process() {
645 let current_process = Process::current();
647 let cmd_line = current_process
648 .command_line()
649 .expect("Should read command line");
650
651 assert!(!cmd_line.is_empty(), "Command line should not be empty");
653
654 assert!(
657 cmd_line.contains(".exe") || cmd_line.contains("cargo") || !cmd_line.is_empty(),
658 "Command line should contain executable name"
659 );
660 }
661
662 #[test]
663 #[ignore] fn test_command_line_with_buffer() {
665 let current_process = Process::current();
667 let mut buffer = Vec::with_capacity(8192);
668
669 let cmd_line = current_process
670 .command_line_with_buffer(&mut buffer)
671 .expect("Should read command line with buffer");
672
673 assert!(!cmd_line.is_empty(), "Command line should not be empty");
674
675 let cmd_line2 = current_process
677 .command_line_with_buffer(&mut buffer)
678 .expect("Should read command line again");
679
680 assert_eq!(cmd_line, cmd_line2, "Command line should be consistent");
681 }
682
683 #[test]
684 #[ignore] fn test_parameters_of_current_process() {
686 let current_process = Process::current();
688 let params = current_process
689 .parameters()
690 .expect("Should read parameters");
691
692 assert!(
694 !params.command_line.is_empty(),
695 "Parameters command_line should not be empty"
696 );
697
698 let image_str = params.image_path.as_str();
700 assert!(
701 !image_str.is_empty(),
702 "Parameters image_path should not be empty"
703 );
704 assert!(
705 image_str.contains(".exe") || !image_str.is_empty(),
706 "Image path should look like an executable"
707 );
708 }
709
710 #[test]
711 #[ignore] fn test_parameters_with_buffer() {
713 let current_process = Process::current();
715 let mut buffer = Vec::with_capacity(8192);
716
717 let params = current_process
718 .parameters_with_buffer(&mut buffer)
719 .expect("Should read parameters with buffer");
720
721 assert!(
722 !params.command_line.is_empty(),
723 "Command line should not be empty"
724 );
725
726 let params2 = current_process
728 .parameters_with_buffer(&mut buffer)
729 .expect("Should read parameters again");
730
731 assert_eq!(
732 params.command_line, params2.command_line,
733 "Command line should be consistent"
734 );
735 assert_eq!(
736 params.image_path.as_str(),
737 params2.image_path.as_str(),
738 "Image path should be consistent"
739 );
740 }
741
742 #[test]
743 #[ignore] fn test_environment_of_current_process() {
745 let current_process = Process::current();
747 let env = current_process
748 .environment()
749 .expect("Should read environment variables");
750
751 assert!(!env.is_empty(), "Environment should not be empty");
753
754 let has_path = env.iter().any(|(k, _)| k.eq_ignore_ascii_case("PATH"));
756 assert!(
757 has_path || env.len() > 5,
758 "Should have PATH or multiple environment variables"
759 );
760 }
761
762 #[test]
763 #[ignore] fn test_environment_with_buffer() {
765 let current_process = Process::current();
767 let mut buffer = Vec::with_capacity(8192);
768
769 let env = current_process
770 .environment_with_buffer(&mut buffer)
771 .expect("Should read environment with buffer");
772
773 assert!(!env.is_empty(), "Environment should not be empty");
774
775 let env2 = current_process
777 .environment_with_buffer(&mut buffer)
778 .expect("Should read environment again");
779
780 assert_eq!(
781 env.len(),
782 env2.len(),
783 "Environment variable count should be consistent"
784 );
785 }
786
787 #[test]
788 #[ignore] fn test_environment_common_variables() {
790 let current_process = Process::current();
792 let env = current_process
793 .environment()
794 .expect("Should read environment variables");
795
796 let common_vars = ["PATH", "TEMP", "TMP", "WINDIR", "SYSTEMROOT", "USERNAME"];
798 let found = common_vars
799 .iter()
800 .any(|var| env.iter().any(|(k, _)| k.eq_ignore_ascii_case(var)));
801
802 assert!(
803 found,
804 "Should find at least one common environment variable (PATH, TEMP, WINDIR, etc.)"
805 );
806 }
807
808 #[test]
809 #[ignore] fn test_environment_values_are_valid_strings() {
811 let current_process = Process::current();
813 let env = current_process
814 .environment()
815 .expect("Should read environment variables");
816
817 for (key, _value) in env.iter() {
819 assert!(
820 !key.is_empty(),
821 "Environment variable key should not be empty"
822 );
823 assert!(
825 key.chars().all(|c| c.is_ascii_graphic() || c == '_'),
826 "Environment variable key should contain valid characters"
827 );
828 }
829 }
830}