1#![doc = include_str!("../readme.md")]
2
3use std::{
4 fs::{File, OpenOptions, read_dir, read_link},
5 io::{BufRead, BufReader},
6 os::unix::fs::FileExt,
7 path::Path,
8 str::FromStr,
9};
10
11pub use bytemuck::{AnyBitPattern, NoUninit};
12use error::{MemoryError, ProcessError};
13#[cfg(feature = "syscall")]
14use libc::{iovec, process_vm_readv, process_vm_writev};
15use library::LibraryInfo;
16
17mod elf;
18pub mod error;
20pub mod library;
22
23#[derive(PartialEq)]
25pub enum MemoryMode {
26 File,
28 #[cfg(feature = "syscall")]
30 Syscall,
31}
32
33pub struct Process {
35 pid: i32,
36 memory: File,
37 mode: MemoryMode,
38}
39
40impl Process {
41 fn find_pid<S: AsRef<str>>(name: S) -> Result<i32, ProcessError> {
42 let dir = read_dir("/proc").map_err(ProcessError::Io)?;
43 for dir_entry in dir {
44 let entry = match dir_entry {
45 Ok(entry) => entry,
46 Err(_) => continue,
47 };
48
49 match entry.file_type() {
50 Ok(file_type) => {
51 if !file_type.is_dir() {
52 continue;
53 }
54 }
55 Err(_) => continue,
56 }
57
58 let pid_osstr = entry.file_name();
59 let pid = match pid_osstr.to_str() {
60 Some(pid) => pid,
61 None => continue,
62 };
63
64 if !pid.chars().all(|char| char.is_numeric()) {
65 continue;
66 }
67
68 let Ok(exe_path) = read_link(format!("/proc/{}/exe", pid)) else {
69 continue;
70 };
71
72 let exe_name = match exe_path.file_name() {
73 Some(exe_name) => exe_name,
74 None => continue,
75 };
76
77 if exe_name == name.as_ref() {
78 let pid = pid.parse::<i32>()?;
79 return Ok(pid);
80 }
81 }
82 Err(ProcessError::NotFound)
83 }
84
85 pub fn open_exe_name<S: AsRef<str>>(name: S) -> Result<Process, ProcessError> {
94 let pid = Process::find_pid(name)?;
95 Process::open_pid(pid)
96 }
97
98 pub fn open_pid(pid: i32) -> Result<Process, ProcessError> {
102 #[cfg(feature = "syscall")]
105 let has_proc_read = {
106 let mut dummy_data = [0u8; 1];
107 let iov = iovec {
108 iov_base: dummy_data.as_mut_ptr() as *mut libc::c_void,
109 iov_len: 1,
110 };
111
112 unsafe { process_vm_readv(pid, &iov, 1, &iov, 1, 0) > 0 }
113 };
114
115 let memory = OpenOptions::new()
116 .read(true)
117 .write(true)
118 .open(format!("/proc/{pid}/mem"))
119 .map_err(ProcessError::Io)?;
120
121 Ok(Process {
122 pid,
123 memory,
124 #[cfg(feature = "syscall")]
125 mode: if has_proc_read {
126 MemoryMode::Syscall
127 } else {
128 MemoryMode::File
129 },
130 #[cfg(not(feature = "syscall"))]
131 mode: MemoryMode::File,
132 })
133 }
134
135 #[cfg(feature = "syscall")]
137 pub fn set_mode(&mut self, mode: MemoryMode) {
138 self.mode = mode;
139 }
140
141 pub fn is_running(&self) -> bool {
143 Path::new(&format!("/proc/{}/mem", self.pid)).exists()
144 }
145
146 pub fn pid(&self) -> i32 {
148 self.pid
149 }
150
151 #[cfg(feature = "syscall")]
152 fn syscall_read(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
153 let local_iov = iovec {
154 iov_base: buffer.as_mut_ptr() as *mut libc::c_void,
155 iov_len: buffer.len(),
156 };
157 let remote_iov = iovec {
158 iov_base: address as *mut libc::c_void,
159 iov_len: buffer.len(),
160 };
161
162 let bytes_read = unsafe { process_vm_readv(self.pid, &local_iov, 1, &remote_iov, 1, 0) };
163
164 if bytes_read < 0 {
165 Err(MemoryError::Io(std::io::Error::last_os_error()))
166 } else if (bytes_read as usize) < buffer.len() {
167 Err(MemoryError::PartialTransfer(
168 bytes_read as usize,
169 buffer.len(),
170 ))
171 } else {
172 Ok(())
173 }
174 }
175
176 #[cfg(not(feature = "syscall"))]
177 #[inline]
178 fn read_impl(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
179 self.memory
180 .read_at(buffer, address as u64)
181 .map_err(MemoryError::Io)?;
182 Ok(())
183 }
184
185 #[cfg(feature = "syscall")]
186 #[inline]
187 fn read_impl(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
188 if self.mode == MemoryMode::File {
189 self.memory
190 .read_at(buffer, address as u64)
191 .map_err(MemoryError::Io)?;
192 Ok(())
193 } else {
194 self.syscall_read(buffer, address)
195 }
196 }
197
198 pub fn read<T: AnyBitPattern>(&self, address: usize) -> Result<T, MemoryError> {
203 let mut buffer = vec![0u8; std::mem::size_of::<T>()];
204
205 self.read_impl(&mut buffer, address)?;
206
207 match bytemuck::try_from_bytes::<T>(&buffer).cloned() {
208 Ok(value) => Ok(value),
209 Err(_) => Err(MemoryError::InvalidData(std::any::type_name::<T>())),
210 }
211 }
212
213 pub fn read_vec<T: AnyBitPattern>(
218 &self,
219 address: usize,
220 count: usize,
221 ) -> Result<Vec<T>, MemoryError> {
222 let mut buffer = vec![0u8; std::mem::size_of::<T>() * count];
223
224 self.read_impl(&mut buffer, address)?;
225
226 let slice: &[T] = match bytemuck::try_cast_slice(&buffer) {
227 Ok(value) => Ok(value),
228 Err(_) => Err(MemoryError::InvalidData(std::any::type_name::<T>())),
229 }?;
230 Ok(slice.to_vec())
231 }
232
233 #[cfg(feature = "syscall")]
234 fn syscall_write(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
235 let local_iov = iovec {
236 iov_base: buffer.as_mut_ptr() as *mut libc::c_void,
237 iov_len: buffer.len(),
238 };
239 let remote_iov = iovec {
240 iov_base: address as *mut libc::c_void,
241 iov_len: buffer.len(),
242 };
243
244 let bytes_written =
245 unsafe { process_vm_writev(self.pid, &local_iov, 1, &remote_iov, 1, 0) };
246 if bytes_written < 0 {
247 Err(MemoryError::Io(std::io::Error::last_os_error()))
248 } else if (bytes_written as usize) < buffer.len() {
249 Err(MemoryError::PartialTransfer(
250 bytes_written as usize,
251 buffer.len(),
252 ))
253 } else {
254 Ok(())
255 }
256 }
257
258 #[cfg(not(feature = "syscall"))]
259 #[inline]
260 fn write_impl(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
261 self.memory
262 .write_at(buffer, address as u64)
263 .map_err(MemoryError::Io)?;
264 Ok(())
265 }
266
267 #[cfg(feature = "syscall")]
268 #[inline]
269 fn write_impl(&self, buffer: &mut [u8], address: usize) -> Result<(), MemoryError> {
270 if self.mode == MemoryMode::File {
271 self.memory
272 .write_at(buffer, address as u64)
273 .map_err(MemoryError::Io)?;
274 Ok(())
275 } else {
276 self.syscall_write(buffer, address)
277 }
278 }
279
280 pub fn write<T: NoUninit>(&self, address: usize, value: &T) -> Result<(), MemoryError> {
287 let mut buffer = bytemuck::bytes_of(value).to_vec();
288 self.write_impl(&mut buffer, address)
289 }
290
291 pub fn write_vec<T: NoUninit>(&self, address: usize, value: &[T]) -> Result<(), MemoryError> {
298 let mut buffer = bytemuck::cast_slice(value).to_vec();
299 self.write_impl(&mut buffer, address)
300 }
301
302 pub fn read_bytes(&self, address: usize, count: usize) -> Result<Vec<u8>, MemoryError> {
308 let mut buffer = vec![0u8; count];
309 self.memory
310 .read_at(&mut buffer, address as u64)
311 .map_err(MemoryError::Io)?;
312 Ok(buffer)
313 }
314
315 pub fn write_bytes(&self, address: usize, value: &[u8]) -> Result<(), MemoryError> {
321 self.memory
322 .write_at(value, address as u64)
323 .map_err(MemoryError::Io)?;
324 Ok(())
325 }
326
327 pub fn read_terminated_string(&self, address: usize) -> Result<String, MemoryError> {
330 const MAX_BYTES: usize = 1024;
331 const SIZE: usize = 32;
332 let mut buffer = Vec::with_capacity(SIZE);
333 let mut current_address = address;
334 let mut bytes_read = 0;
335 loop {
336 let chunk = self.read_bytes(current_address, SIZE)?;
337 bytes_read += SIZE;
338
339 if let Some(null_pos) = chunk.iter().position(|&b| b == 0) {
340 buffer.extend_from_slice(&chunk[..null_pos]);
341 return String::from_utf8(buffer).map_err(|_| MemoryError::InvalidString);
342 }
343
344 buffer.extend_from_slice(&chunk);
345 current_address += SIZE;
346
347 if bytes_read >= MAX_BYTES {
348 return Err(MemoryError::StringTooLong);
349 }
350 }
351 }
352
353 pub fn read_string(&self, address: usize, length: usize) -> Result<String, MemoryError> {
355 let bytes = self.read_bytes(address, length)?;
356 String::from_utf8(bytes).map_err(|_| MemoryError::InvalidString)
357 }
358
359 pub fn write_string<S: AsRef<str>>(&self, address: usize, value: S) -> Result<(), MemoryError> {
361 self.write_bytes(address, value.as_ref().as_bytes())
362 }
363
364 fn maps(&self) -> String {
365 format!("/proc/{}/maps", self.pid)
366 }
367
368 pub fn find_library<S: AsRef<str>>(&self, lib_name: S) -> Result<LibraryInfo, ProcessError> {
371 let libraries = self.all_libraries()?;
372 for lib in libraries {
373 if lib.offset() != 0 {
374 continue;
375 }
376 if let Some(path) = lib.path() {
377 if !path.starts_with('/') {
378 continue;
379 }
380 let Some((_, file_name)) = path.rsplit_once('/') else {
381 continue;
382 };
383 if file_name.starts_with(lib_name.as_ref()) {
384 return Ok(lib);
385 }
386 }
387 }
388 Err(ProcessError::NotFound)
389 }
390
391 pub fn all_libraries(&self) -> Result<Vec<LibraryInfo>, ProcessError> {
392 let mut libraries = Vec::with_capacity(8);
393
394 let maps = File::open(self.maps()).map_err(ProcessError::Io)?;
395 for line in BufReader::new(maps).lines() {
396 let Ok(line) = line else {
397 continue;
398 };
399 let lib = LibraryInfo::from_str(&line)?;
400 libraries.push(lib);
401 }
402
403 Ok(libraries)
404 }
405
406 pub fn elf_size(&self, library: &LibraryInfo) -> Result<usize, MemoryError> {
408 if library.offset() != 0 {
409 return Err(MemoryError::OutOfRange);
410 }
411 let header = self.read::<u32>(library.start())?;
413 if header != 0x464C457F && header != 0x7F454C46 {
414 return Err(MemoryError::OutOfRange);
415 }
416 let section_header_offset =
417 self.read::<usize>(library.start() + elf::SECTION_HEADER_OFFSET)?;
418 let section_header_entry_size =
419 self.read::<u16>(library.start() + elf::SECTION_HEADER_ENTRY_SIZE)? as usize;
420 let section_header_num_entries =
421 self.read::<u16>(library.start() + elf::SECTION_HEADER_NUM_ENTRIES)? as usize;
422
423 Ok(section_header_offset + section_header_entry_size * section_header_num_entries)
424 }
425
426 pub fn dump_library(&self, library: &LibraryInfo) -> Result<Vec<u8>, MemoryError> {
432 if library.offset() != 0 {
433 return Err(MemoryError::InvalidLibrary);
434 }
435 let header = self.read::<u32>(library.start())?;
437 if header != 0x464C457F && header != 0x7F454C46 {
438 return Err(MemoryError::OutOfRange);
439 }
440 let lib_size = self.elf_size(library)?;
441 self.read_bytes(library.start(), lib_size)
442 }
443
444 pub fn scan_pattern<S: AsRef<str>>(
457 &self,
458 pattern: S,
459 library: &LibraryInfo,
460 ) -> Result<usize, MemoryError> {
461 let pattern_string = pattern.as_ref();
462 let mut pattern = Vec::with_capacity(pattern_string.len());
463 let mut mask = Vec::with_capacity(pattern_string.len());
464
465 for c in pattern_string.split(' ') {
466 match u8::from_str_radix(c, 16) {
467 Ok(c) => {
468 pattern.push(c);
469 mask.push(1);
470 }
471 Err(_) => {
472 pattern.push(0);
473 mask.push(0);
474 }
475 }
476 }
477
478 let module = self.dump_library(library)?;
479 if module.len() < 500 {
480 return Err(MemoryError::InvalidLibrary);
481 }
482
483 let pattern_length = pattern.len();
484 let stop_index = module.len() - pattern_length;
485 'outer: for i in 0..stop_index {
486 for j in 0..pattern_length {
487 if mask[j] != 0 && module[i + j] != pattern[j] {
488 continue 'outer;
489 }
490 }
491 return Ok(library.start() + i);
492 }
493 Err(MemoryError::NotFound)
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use crate::error::{MemoryError, ProcessError};
500
501 use super::Process;
502
503 fn pid() -> i32 {
505 std::process::id() as i32
506 }
507
508 #[test]
509 fn create() {
510 assert!(Process::open_pid(pid()).is_ok());
511 }
512
513 #[test]
514 fn read() -> Result<(), MemoryError> {
515 let process = Process::open_pid(pid()).unwrap();
516 let buffer = [0x55u8];
517 let value = process.read::<u8>(buffer.as_ptr() as usize)?;
518 assert_eq!(value, buffer[0]);
519 Ok(())
520 }
521
522 #[test]
523 fn read_vec() -> Result<(), MemoryError> {
524 let process = Process::open_pid(pid()).unwrap();
525 let buffer = [0x11u8, 0x22, 0x33, 0x44];
526 let addr = buffer.as_ptr() as usize;
527 let values: Vec<u8> = process.read_vec(addr, buffer.len())?;
528 assert_eq!(values, buffer.to_vec());
529 Ok(())
530 }
531
532 #[test]
533 fn write() -> Result<(), MemoryError> {
534 let process = Process::open_pid(pid()).unwrap();
535 let buffer = [0x55u8];
536 const VALUE: u8 = 0x66;
537 process.write::<u8>(buffer.as_ptr() as usize, &VALUE)?;
538 assert_eq!(buffer[0], VALUE);
539 Ok(())
540 }
541
542 #[test]
543 fn write_vec() -> Result<(), MemoryError> {
544 let process = Process::open_pid(pid()).unwrap();
545 let mut buffer = [0x11u8, 0x22, 0x33, 0x44];
546 let addr = buffer.as_mut_ptr() as usize;
547 let to_write = [0x44u8, 0x33, 0x22, 0x11];
548 process.write_vec(addr, &to_write)?;
549 assert_eq!(buffer, to_write);
550 Ok(())
551 }
552
553 #[test]
554 fn read_bytes() -> Result<(), MemoryError> {
555 let process = Process::open_pid(pid()).unwrap();
556 let buffer: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
557 let value = process.read_bytes(buffer.as_ptr() as usize, 4)?;
558 assert_eq!(value, buffer);
559 Ok(())
560 }
561
562 #[test]
563 fn write_bytes() -> Result<(), MemoryError> {
564 let process = Process::open_pid(pid()).unwrap();
565 let buffer: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
566 const VALUE: [u8; 4] = [0x55, 0x66, 0x77, 0x88];
567 process.write_bytes(buffer.as_ptr() as usize, &VALUE)?;
568 assert_eq!(buffer, VALUE);
569 Ok(())
570 }
571
572 #[test]
573 fn read_terminated_string() -> Result<(), MemoryError> {
574 let process = Process::open_pid(pid()).unwrap();
575 const STRING: &str = "Hello World";
576 let buffer = std::ffi::CString::new(STRING).unwrap();
577 let value = process.read_terminated_string(buffer.as_ptr() as usize)?;
578 assert_eq!(value, *STRING);
579 Ok(())
580 }
581
582 #[test]
583 fn read_string() -> Result<(), MemoryError> {
584 let process = Process::open_pid(pid()).unwrap();
585 const STRING: &str = "Hello World";
586 let value = process.read_string(STRING.as_ptr() as usize, STRING.len())?;
587 assert_eq!(value, STRING);
588 Ok(())
589 }
590
591 #[test]
592 fn scan_pattern() -> Result<(), MemoryError> {
593 let process = Process::open_pid(pid()).unwrap();
594 const STRING: &str = "Hello World";
595
596 let exe_path = std::env::current_exe().unwrap();
598 let exe_name = exe_path.file_name().unwrap().to_str().unwrap();
599 let lib = process.find_library(exe_name).unwrap();
600
601 let pattern = STRING
603 .as_bytes()
604 .iter()
605 .map(|c| format!("{:02x}", c))
606 .collect::<Vec<String>>()
607 .join(" ");
608
609 let value = process.scan_pattern(pattern, &lib)?;
610 assert_eq!(value, STRING.as_ptr() as usize);
611 Ok(())
612 }
613
614 #[test]
615 fn parse_library() -> Result<(), ProcessError> {
616 let process = Process::open_pid(pid())?;
617 let libraries = process.all_libraries()?;
618 assert!(libraries.iter().any(|lib| lib.path() == Some("[heap]")));
619 Ok(())
620 }
621}