1#![allow(unused_imports)]
5#![allow(dead_code)]
6#[cfg(target_family = "windows")]
7mod memory_windows;
8
9#[cfg(target_vendor = "apple")]
10mod memory_darwin;
11
12#[cfg(target_vendor = "unknown")]
13mod memory_linux;
14
15#[cfg(target_vendor = "unknown")]
16use nix::sys::ptrace;
17#[cfg(any(target_vendor = "unknown", target_os = "macos"))]
18use nix::libc::{SIGCONT, SIGKILL, SIGTRAP, WIFSTOPPED, WSTOPSIG, c_char, fork, kill, pid_t, waitpid};
19#[cfg(any(target_vendor = "unknown", target_os = "macos"))]
20use nix::unistd::close;
21
22#[cfg(target_family = "windows")]
23use winapi::um::handleapi::CloseHandle;
24#[cfg(target_family = "windows")]
25use winapi::um::handleapi::INVALID_HANDLE_VALUE;
26#[cfg(target_family = "windows")]
27use winapi::um::processthreadsapi::TerminateProcess;
28#[cfg(target_family = "windows")]
29use winapi::um::winnt::HANDLE;
30
31use std::mem::MaybeUninit;
32
33#[cfg(target_os = "macos")]
34use nix::libc::{POSIX_SPAWN_START_SUSPENDED, posix_spawn, posix_spawnattr_init, posix_spawnattr_setflags, posix_spawnattr_t};
35
36use std::ffi::CString;
37
38#[cfg(any(target_os = "macos", target_family = "windows"))]
45pub struct ProcessMemory {
46 base_address: usize,
47 handle: usize,
48 pid: u32,
49 thread: usize,
50 #[cfg(target_os = "macos")]
51 base_size: usize
52}
53
54#[cfg(target_vendor = "unknown")]
60pub struct ProcessMemory {
61 base_address: usize,
62 pid: u32
63}
64
65#[cfg(target_family = "windows")]
69fn close_valid_handle(value: HANDLE) -> bool {
70 if is_valid_handle!(value) {
71 unsafe { CloseHandle(value) };
72 return true;
73 }
74 return false;
75}
76
77#[cfg(target_family = "windows")]
81fn delimit(text: &Vec<String>) -> String {
82 let mut result: String = String::new();
83 for s in text {
84 result.push_str(s.as_str());
85 result.push(' ');
86 }
87 result.pop();
88 result
89}
90
91
92#[cfg(target_os = "macos")]
98fn make_null_terminated_string_array(strings: &mut Vec<std::ffi::CString>) -> Vec<*mut c_char> {
99 let mut result = Vec::<*mut c_char>::new();
100 for s in strings {
101 result.push(s.as_ptr() as *mut c_char);
102 }
103 result.push(std::ptr::null::<c_char>() as *mut c_char);
104 result
105}
106
107#[cfg(target_os = "macos")]
113fn collect_argv(argv: &[String]) -> Vec<std::ffi::CString> {
114 argv.iter()
115 .map(|s| std::ffi::CString::new(s.as_bytes()).unwrap())
116 .collect::<Vec<std::ffi::CString>>()
117}
118
119#[cfg(target_os = "macos")]
125fn collect_env() -> Vec<std::ffi::CString> {
126 use std::os::unix::ffi::OsStrExt;
127 std::env::vars_os().map(|(key, value) | {
128 std::ffi::CString::new(
129 [
130 key.as_os_str().as_bytes(),
131 &[b'='],
132 value.as_os_str().as_bytes(),
133 ]
134 .concat()
135 )
136 .unwrap()
137 })
138 .collect::<Vec<std::ffi::CString>>()
139}
140
141#[cfg(target_vendor = "unknown")]
146fn get_main_module(pid: u32) -> usize {
147 use std::fs::File;
148 use std::io::BufRead;
149 let proc = format!("/proc/{process_id}/maps", process_id=pid);
150 let file = File::open(proc).unwrap();
151 let reader = std::io::BufReader::new(file);
152
153 for line in reader.lines() {
154 for token in line.unwrap().split("-") {
155 return usize::from_str_radix(token, 16).unwrap();
156 }
157 }
158 0
159}
160
161#[cfg(target_vendor = "unknown")]
166fn create_reference_process(file_name: &str, arguments: &Vec<String>) {
167 ptrace::traceme().unwrap();
168 let cfile = CString::new(file_name).unwrap();
169 let mut cfile_args: Vec<CString> = vec![cfile];
170
171 for argument in arguments {
172 cfile_args.push(CString::new(argument.as_str()).unwrap());
173 }
174
175 nix::unistd::execv(&cfile_args[0], &cfile_args).unwrap();
176}
177
178impl ProcessMemory {
179
180 #[cfg(target_family = "windows")]
186 pub fn attach_process(pid: u32) -> Option<ProcessMemory> {
187 use winapi::um::{processthreadsapi::OpenProcess, winnt::PROCESS_ALL_ACCESS};
188 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
189 use winapi::um::winnt::HANDLE;
190 use crate::memory_windows::get_base_address;
191
192 let process = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false as _, pid) };
197
198 if !is_valid_handle!(process) {
199 return None
200 }
201
202 let base = get_base_address(process, None, pid).unwrap();
207
208 Some(
209 ProcessMemory{
210 base_address: base,
211 handle: process as _,
212 pid: pid,
213 thread: 0
214 }
215 )
216 }
217
218 #[cfg(target_vendor = "apple")]
222 pub fn attach_process(pid: u32) -> Option<ProcessMemory> {
223 let task = memory_darwin::get_task_for_pid(pid as _);
224
225 if task == 0 {
226 return None
227 }
228
229 let base = memory_darwin::get_base_address(task, 1).unwrap();
230
231 Some(
232 ProcessMemory{
233 base_address: base.0,
234 handle: task as _,
235 pid: pid as _,
236 thread: 0,
237 base_size: base.1,
238 }
239 )
240 }
241
242
243 #[cfg(target_vendor = "unknown")]
247 pub fn attach_process(pid: u32) -> Option<ProcessMemory> {
248 let nix_pid = nix::unistd::Pid::from_raw(pid as _);
249
250 match ptrace::attach(nix_pid) {
251 Ok(_) => (),
252 Err(_) => return None
253 }
254
255 let base = get_main_module(pid);
259
260 if base == 0 {
261 return None
262 }
263
264 Some(
265 ProcessMemory{
266 base_address: base,
267 pid: pid
268 }
269 )
270 }
271
272 #[cfg(target_vendor = "unknown")]
283 pub fn new_process(file_name: &str, arguments: &Vec<String>) -> Option<ProcessMemory> {
284 let pid: pid_t = unsafe { fork() };
285
286 match pid {
287 0 => create_reference_process(file_name, &arguments),
288 -1 => return None,
289 _ => ()
290 }
291
292 unsafe {
296 let mut status: i32 = 0;
297 waitpid(pid, &mut status, 0);
298 if WIFSTOPPED(status) && WSTOPSIG(status) != SIGTRAP {
299 panic!("waitpid failed");
300 }
301 }
302
303 let base = get_main_module(pid as _);
307
308 Some(
309 ProcessMemory {
310 base_address: base,
311 pid: pid as _
312 }
313 )
314 }
315
316 #[cfg(target_vendor = "apple")]
321 pub fn new_process(file_name: &str, arguments: &Vec<String>) -> Option<ProcessMemory> {
322 let mut pid: pid_t = 0;
323 unsafe {
324 let mut posix_attr = MaybeUninit::<posix_spawnattr_t>::uninit();
328 posix_spawnattr_init(posix_attr.as_mut_ptr());
329 let mut posix_attr = posix_attr.assume_init();
330 posix_spawnattr_setflags(&mut posix_attr, POSIX_SPAWN_START_SUSPENDED as _);
331
332 let mut envp = collect_env();
336 let envpp = make_null_terminated_string_array(&mut envp);
337
338 let mut argvp = collect_argv(arguments.as_slice());
339 let argvpp = make_null_terminated_string_array(&mut argvp);
340
341 let cfile_name = CString::new(file_name).unwrap();
342 if posix_spawn(
346 &mut pid,
347 cfile_name.as_ptr(),
348 0 as _,
349 &posix_attr,
350 argvpp.as_ptr(),
351 envpp.as_ptr()
352 ) != 0 {
353 return None
354 }
355 }
356
357 let task = memory_darwin::get_task_for_pid(pid as _);
361
362 if task == 0 {
363 panic!("Failed to get task port for process");
364 }
365
366 let base = memory_darwin::get_base_address(task, 1).unwrap();
370
371 Some(
372 ProcessMemory{
373 base_address: base.0,
374 handle: task as _,
375 pid: pid as _,
376 thread: 0,
377 base_size: base.1
378 }
379 )
380 }
381
382 #[cfg(target_family = "windows")]
386 pub fn new_process(file_path: &str, args: &Vec<String>) -> Option<ProcessMemory> {
387 use winapi::um::{processthreadsapi::{CreateProcessA, PROCESS_INFORMATION, STARTUPINFOA}, winbase::CREATE_SUSPENDED};
388
389 use crate::memory_windows::get_base_address;
390
391 let proc_string = CString::new(file_path).unwrap();
392 let argv_text = delimit(&args);
393 let argv = CString::new(argv_text.as_str()).unwrap();
394
395 let mut start_up: STARTUPINFOA = unsafe { std::mem::MaybeUninit::<STARTUPINFOA>::zeroed().assume_init() };
399 let mut proc_info: PROCESS_INFORMATION = unsafe { std::mem::MaybeUninit::<PROCESS_INFORMATION>::zeroed().assume_init() };
400
401 let result = unsafe {
405 CreateProcessA(
406 proc_string.as_ptr(),
407 argv.as_ptr() as _,
408 0 as _,
409 0 as _,
410 0,
411 CREATE_SUSPENDED,
412 0 as *mut _,
413 0 as *mut _,
414 &mut start_up as *mut _,
415 &mut proc_info as *mut _
416 )
417 };
418
419 if result == 0 {
420 return None;
421 }
422
423 let base = get_base_address(proc_info.hProcess, Some(proc_info.hThread), proc_info.dwProcessId).unwrap();
428 Some(
429 ProcessMemory{
430 base_address: base,
431 handle: proc_info.hProcess as _,
432 pid: proc_info.dwProcessId,
433 thread: proc_info.hThread as _
434 }
435 )
436 }
437
438 pub fn write_memory(&self, _address: usize, data: &Vec<u8>, offset: bool) {
451 let mut address: usize = _address;
452 if offset {
453 address = self.base_address + address;
454 }
455
456 #[cfg(target_family = "windows")] {
457 memory_windows::write_memory(self.handle as _, address, &data).unwrap()
458 }
459
460 #[cfg(target_os = "macos")] {
461 memory_darwin::write_memory(self.handle as _, address, &data).unwrap()
462 }
463
464 #[cfg(target_vendor = "unknown")] {
465 memory_linux::write_memory(self.pid, address, &data).unwrap()
466 }
467 }
468
469 pub fn read_memory(&self, _address: usize, size: usize, offset: bool) -> Vec<u8> {
485 let mut address: usize = _address;
486 if offset {
487 address = self.base_address + address;
488 }
489
490 #[cfg(target_vendor = "unknown")] {
491 memory_linux::read_memory(self.pid, address, size).unwrap()
492 }
493
494 #[cfg(target_family = "windows")] {
495 memory_windows::read_memory(self.handle as _, address, size).unwrap()
496 }
497
498 #[cfg(target_os = "macos")] {
499 memory_darwin::read_memory(self.handle as _, address, size).unwrap()
500 }
501 }
502
503 pub fn resume(&self) {
506 #[cfg(target_family = "unix")]
507 unsafe { kill(self.pid as _, SIGCONT);}
508 #[cfg(target_family = "windows")]
509 unsafe { winapi::um::processthreadsapi::ResumeThread(self.thread as _) };
510 }
511
512 pub fn base(&self) -> usize {
514 self.base_address
515 }
516
517 pub fn kill(&self) {
519 #[cfg(target_family = "unix")]
520 unsafe { kill(self.pid as _, SIGKILL);}
521 #[cfg(target_family = "windows")] unsafe {
522 TerminateProcess(self.handle as _, 0);
523 }
524 }
525
526 pub fn pid(&self) -> u32 {
528 self.pid
529 }
530
531 #[cfg(target_os = "macos")]
533 pub fn base_size(&self) -> usize {
534 self.base_size
535 }
536
537 pub fn raw_descriptor(&self) -> usize {
539 #[cfg(any(target_os = "macos", target_family = "windows"))] {
540 self.handle
541 }
542 #[cfg(target_vendor = "unknown")] {
543 self.pid as _
544 }
545 }
546
547 pub fn early_close(&mut self) {
549 #[cfg(target_family = "windows")]
550 close_valid_handle(self.handle as _);
551 #[cfg(target_os = "macos")]
552 unsafe { mach::mach_port::mach_port_deallocate(mach::traps::mach_task_self(), self.handle as _) };
553 #[cfg(target_vendor = "unknown")]
554 ptrace::detach(nix_pid, None).unwrap();
555 }
556}
557
558impl Drop for ProcessMemory {
559 #[cfg(target_family = "windows")]
563 fn drop(&mut self) {
564 close_valid_handle(self.handle as _);
565 close_valid_handle(self.thread as _);
566 }
567
568 #[cfg(target_os = "macos")]
572 fn drop(&mut self) {
573 unsafe {
574 mach::mach_port::mach_port_deallocate(mach::traps::mach_task_self(), self.handle as _);
575 }
576 }
577
578 #[cfg(target_vendor = "unknown")]
582 fn drop(&mut self) {
583 let nix_pid = nix::unistd::Pid::from_raw(self.pid as _);
584 ptrace::detach(nix_pid, None).unwrap();
585 }
586}