1#![doc = include_str!("../readme.md")]
2
3use std::{
4 fs::{File, OpenOptions, read_dir, read_link},
5 io::{BufRead, BufReader, Error, ErrorKind},
6 os::unix::fs::FileExt,
7 path::Path,
8};
9
10use bytemuck::{AnyBitPattern, NoUninit};
11use error::{MemoryError, ProcessError};
12use libc::{EFAULT, EPERM, ESRCH, iovec, process_vm_readv, process_vm_writev};
13
14mod elf;
15pub mod error;
17
18#[derive(PartialEq)]
20pub enum MemoryMode {
21 File,
23 Syscall,
25}
26
27pub struct Process {
29 pid: i32,
30 memory: File,
31 mode: MemoryMode,
32}
33
34impl Process {
35 fn find_pid<S: AsRef<str>>(name: S) -> Result<i32, ProcessError> {
36 for dir in read_dir("/proc").unwrap() {
37 let entry = match dir {
38 Ok(entry) => entry,
39 Err(_) => continue,
40 };
41
42 match entry.file_type() {
43 Ok(file_type) => {
44 if !file_type.is_dir() {
45 continue;
46 }
47 }
48 Err(_) => continue,
49 }
50
51 let pid_osstr = entry.file_name();
52 let pid = match pid_osstr.to_str() {
53 Some(pid) => pid,
54 None => continue,
55 };
56
57 if !pid.chars().all(|char| char.is_numeric()) {
58 continue;
59 }
60
61 let Ok(exe_path) = read_link(format!("/proc/{}/exe", pid)) else {
62 continue;
63 };
64
65 let exe_name = match exe_path.file_name() {
66 Some(exe_name) => exe_name,
67 None => continue,
68 };
69
70 if exe_name == name.as_ref() {
71 let pid = pid
72 .parse::<i32>()
73 .map_err(|error| ProcessError::InvalidPid(error.kind().clone()))?;
74 return Ok(pid);
75 }
76 }
77 Err(ProcessError::NotFound)
78 }
79
80 fn map_mem_error(error: Error) -> MemoryError {
81 if error.kind() == ErrorKind::PermissionDenied {
82 MemoryError::PermissionDenied
83 } else {
84 MemoryError::Unknown
85 }
86 }
87
88 pub fn open_exe_name<S: AsRef<str>>(name: S) -> Result<Process, ProcessError> {
97 let pid = Process::find_pid(name)?;
98 Process::open_pid(pid)
99 }
100
101 pub fn open_pid(pid: i32) -> Result<Process, ProcessError> {
105 let mut dummy_data = [0u8; 1];
108 let iov = iovec {
109 iov_base: dummy_data.as_mut_ptr() as *mut libc::c_void,
110 iov_len: 1,
111 };
112
113 let has_proc_read = unsafe { process_vm_readv(pid, &iov, 1, &iov, 1, 0) } > 0;
114
115 let memory = OpenOptions::new()
116 .read(true)
117 .write(true)
118 .open(format!("/proc/{pid}/mem"))
119 .map_err(|error| {
120 if error.kind() == ErrorKind::PermissionDenied {
121 ProcessError::PermissionDenied(pid)
122 } else {
123 ProcessError::FileOpenError(pid)
124 }
125 })?;
126
127 Ok(Process {
128 pid,
129 memory,
130 mode: if has_proc_read {
131 MemoryMode::Syscall
132 } else {
133 MemoryMode::File
134 },
135 })
136 }
137
138 pub fn set_mode(&mut self, mode: MemoryMode) {
140 self.mode = mode;
141 }
142
143 pub fn is_running(&self) -> bool {
145 Path::new(&format!("/proc/{}/mem", self.pid)).exists()
146 }
147
148 pub fn pid(&self) -> i32 {
150 self.pid
151 }
152
153 pub fn read<T: AnyBitPattern>(&self, address: usize) -> Result<T, MemoryError> {
158 let mut buffer = vec![0u8; std::mem::size_of::<T>()];
159 if self.mode == MemoryMode::File {
160 self.memory
161 .read_at(&mut buffer, address as u64)
162 .map_err(Process::map_mem_error)?;
163 } else {
164 let local_iov = iovec {
165 iov_base: buffer.as_mut_ptr() as *mut libc::c_void,
166 iov_len: buffer.len(),
167 };
168 let remote_iov = iovec {
169 iov_base: address as *mut libc::c_void,
170 iov_len: buffer.len(),
171 };
172
173 let bytes_read =
174 unsafe { process_vm_readv(self.pid, &local_iov, 1, &remote_iov, 1, 0) };
175 if bytes_read < 0 {
176 let os_error = Error::last_os_error().raw_os_error();
177 return Err(match os_error {
178 Some(EFAULT) => MemoryError::OutOfRange,
179 Some(ESRCH) => MemoryError::ProcessQuit,
180 Some(EPERM) => MemoryError::PermissionDenied,
181 _ => MemoryError::Unknown,
182 });
183 } else if (bytes_read as usize) < buffer.len() {
184 return Err(MemoryError::PartialTransfer(
185 bytes_read as usize,
186 buffer.len(),
187 ));
188 }
189 }
190
191 match bytemuck::try_from_bytes::<T>(&buffer).cloned() {
192 Ok(value) => Ok(value),
193 Err(_) => Err(MemoryError::InvalidData(std::any::type_name::<T>())),
194 }
195 }
196
197 pub fn write<T: NoUninit>(&self, address: usize, value: &T) -> Result<(), MemoryError> {
202 let mut buffer = bytemuck::bytes_of(value).to_vec();
203 if self.mode == MemoryMode::File {
204 self.memory
205 .write_at(&buffer, address as u64)
206 .map_err(Process::map_mem_error)?;
207 } else {
208 let local_iov = iovec {
209 iov_base: buffer.as_mut_ptr() as *mut libc::c_void,
210 iov_len: buffer.len(),
211 };
212 let remote_iov = iovec {
213 iov_base: address as *mut libc::c_void,
214 iov_len: buffer.len(),
215 };
216
217 let bytes_written =
218 unsafe { process_vm_writev(self.pid, &local_iov, 1, &remote_iov, 1, 0) };
219 if bytes_written < 0 {
220 let os_error = Error::last_os_error().raw_os_error();
221 return Err(match os_error {
222 Some(EFAULT) => MemoryError::OutOfRange,
223 Some(ESRCH) => MemoryError::ProcessQuit,
224 Some(EPERM) => MemoryError::PermissionDenied,
225 _ => MemoryError::Unknown,
226 });
227 } else if (bytes_written as usize) < buffer.len() {
228 return Err(MemoryError::PartialTransfer(
229 bytes_written as usize,
230 buffer.len(),
231 ));
232 }
233 }
234
235 Ok(())
236 }
237
238 pub fn read_bytes(&self, address: usize, count: usize) -> Result<Vec<u8>, MemoryError> {
244 let mut buffer = vec![0u8; count];
245 self.memory
246 .read_at(&mut buffer, address as u64)
247 .map_err(Process::map_mem_error)?;
248 Ok(buffer)
249 }
250
251 pub fn write_bytes(&self, address: usize, value: &[u8]) -> Result<(), MemoryError> {
257 self.memory
258 .write_at(value, address as u64)
259 .map_err(Process::map_mem_error)?;
260 Ok(())
261 }
262
263 pub fn read_terminated_string(&self, address: usize) -> Result<String, MemoryError> {
266 const MAX_BYTES: usize = 1024;
267 const SIZE: usize = 32;
268 let mut buffer = Vec::with_capacity(SIZE);
269 let mut current_address = address;
270 let mut bytes_read = 0;
271 loop {
272 let chunk = self.read_bytes(current_address, SIZE)?;
273 bytes_read += SIZE;
274
275 if let Some(null_pos) = chunk.iter().position(|&b| b == 0) {
276 buffer.extend_from_slice(&chunk[..null_pos]);
277 return Ok(String::from_utf8_lossy(&buffer).to_string());
278 }
279
280 buffer.extend_from_slice(&chunk);
281 current_address += SIZE;
282
283 if bytes_read >= MAX_BYTES {
284 return Err(MemoryError::StringTooLong);
285 }
286 }
287 }
288
289 pub fn read_string(&self, address: usize, length: usize) -> Result<String, MemoryError> {
291 let bytes = self.read_bytes(address, length)?;
292 String::from_utf8(bytes)
293 .map_err(|_| MemoryError::InvalidData(std::any::type_name::<String>()))
294 }
295
296 pub fn write_string<S: AsRef<str>>(&self, address: usize, value: S) -> Result<(), MemoryError> {
298 self.write_bytes(address, value.as_ref().as_bytes())
299 }
300
301 pub fn find_library<S: AsRef<str>>(&self, library: S) -> Result<usize, MemoryError> {
304 let maps =
305 File::open(format!("/proc/{}/maps", self.pid)).map_err(Process::map_mem_error)?;
306 for line in BufReader::new(maps).lines() {
307 let Ok(line) = line else {
308 continue;
309 };
310 let Some((line, file_name)) = line.rsplit_once('/') else {
311 continue;
312 };
313 if !file_name.contains(library.as_ref()) {
314 continue;
315 }
316 let Some((address, _)) = line.split_once('-') else {
317 return Err(MemoryError::Unknown);
318 };
319 let address = usize::from_str_radix(address, 16)
320 .map_err(|_| MemoryError::InvalidData(std::any::type_name::<usize>()))?;
321 return Ok(address);
322 }
323 Err(MemoryError::NotFound)
324 }
325
326 pub fn library_size(&self, address: usize) -> Result<usize, MemoryError> {
328 let header = self.read::<u32>(address)?;
330 if header != 0x464C457F && header != 0x7F454C46 {
331 return Err(MemoryError::OutOfRange);
332 }
333 let section_header_offset = self.read::<usize>(address + elf::SECTION_HEADER_OFFSET)?;
334 let section_header_entry_size =
335 self.read::<u16>(address + elf::SECTION_HEADER_ENTRY_SIZE)? as usize;
336 let section_header_num_entries =
337 self.read::<u16>(address + elf::SECTION_HEADER_NUM_ENTRIES)? as usize;
338
339 Ok(section_header_offset + section_header_entry_size * section_header_num_entries)
340 }
341
342 pub fn dump_library(&self, address: usize) -> Result<Vec<u8>, MemoryError> {
345 let header = self.read::<u32>(address)?;
347 if header != 0x464C457F && header != 0x7F454C46 {
348 return Err(MemoryError::OutOfRange);
349 }
350 let lib_size = self.library_size(address)?;
351 self.read_bytes(address, lib_size)
352 }
353
354 pub fn scan_pattern<S: AsRef<str>>(
367 &self,
368 pattern: S,
369 address: usize,
370 ) -> Result<usize, MemoryError> {
371 let pattern_string = pattern.as_ref();
372 let mut pattern = Vec::with_capacity(pattern_string.len());
373 let mut mask = Vec::with_capacity(pattern_string.len());
374
375 for c in pattern_string.split(' ') {
376 match u8::from_str_radix(c, 16) {
377 Ok(c) => {
378 pattern.push(c);
379 mask.push(1);
380 }
381 Err(_) => {
382 pattern.push(0);
383 mask.push(0);
384 }
385 }
386 }
387
388 let module = self.dump_library(address)?;
389 if module.len() < 500 {
390 return Err(MemoryError::InvalidLibrary);
391 }
392
393 let pattern_length = pattern.len();
394 let stop_index = module.len() - pattern_length;
395 'outer: for i in 0..stop_index {
396 for j in 0..pattern_length {
397 if mask[j] != 0 && module[i + j] != pattern[j] {
398 continue 'outer;
399 }
400 }
401 return Ok(address + i);
402 }
403 Err(MemoryError::NotFound)
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use crate::error::MemoryError;
410
411 use super::Process;
412
413 fn pid() -> i32 {
415 std::process::id() as i32
416 }
417
418 #[test]
419 fn create() {
420 assert!(Process::open_pid(pid()).is_ok());
421 }
422
423 #[test]
424 fn read() -> Result<(), MemoryError> {
425 let process = Process::open_pid(pid()).unwrap();
426 let buffer = [0x55u8];
427 let value = process.read::<u8>(buffer.as_ptr() as usize)?;
428 assert!(value == buffer[0]);
429 Ok(())
430 }
431
432 #[test]
433 fn write() -> Result<(), MemoryError> {
434 let process = Process::open_pid(pid()).unwrap();
435 let buffer = [0x55u8];
436 const VALUE: u8 = 0x66;
437 process.write::<u8>(buffer.as_ptr() as usize, &VALUE)?;
438 assert!(buffer[0] == VALUE);
439 Ok(())
440 }
441
442 #[test]
443 fn read_bytes() -> Result<(), MemoryError> {
444 let process = Process::open_pid(pid()).unwrap();
445 let buffer: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
446 let value = process.read_bytes(buffer.as_ptr() as usize, 4)?;
447 assert!(value == buffer);
448 Ok(())
449 }
450
451 #[test]
452 fn write_bytes() -> Result<(), MemoryError> {
453 let process = Process::open_pid(pid()).unwrap();
454 let buffer: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
455 const VALUE: [u8; 4] = [0x55, 0x66, 0x77, 0x88];
456 process.write_bytes(buffer.as_ptr() as usize, &VALUE)?;
457 assert!(buffer == VALUE);
458 Ok(())
459 }
460
461 #[test]
462 fn read_terminated_string() -> Result<(), MemoryError> {
463 let process = Process::open_pid(pid()).unwrap();
464 const STRING: &str = "Hello World";
465 let buffer = std::ffi::CString::new(STRING).unwrap();
466 let value = process.read_terminated_string(buffer.as_ptr() as usize)?;
467 assert!(value == *STRING);
468 Ok(())
469 }
470
471 #[test]
472 fn read_string() -> Result<(), MemoryError> {
473 let process = Process::open_pid(pid()).unwrap();
474 const STRING: &str = "Hello World";
475 let value = process.read_string(STRING.as_ptr() as usize, STRING.len())?;
476 assert!(value == STRING);
477 Ok(())
478 }
479
480 #[test]
481 fn scan_pattern() -> Result<(), MemoryError> {
482 let process = Process::open_pid(pid()).unwrap();
483 const STRING: &str = "Hello World";
484
485 let exe_path = std::env::current_exe().unwrap();
487 let exe_name = exe_path.file_name().unwrap().to_str().unwrap();
488 let lib = process.find_library(exe_name)?;
489
490 let pattern = STRING
492 .as_bytes()
493 .iter()
494 .map(|c| format!("{:02x}", c))
495 .collect::<Vec<String>>()
496 .join(" ");
497
498 let value = process.scan_pattern(pattern, lib)?;
499 assert!(value == STRING.as_ptr() as usize);
500 Ok(())
501 }
502}