procmem_linux/
lib.rs

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;
18/// errors for process handling and memory operations
19pub mod error;
20/// loaded library info
21pub mod library;
22
23/// mode used to read and write memory.
24#[derive(PartialEq)]
25pub enum MemoryMode {
26    /// use file i/o on `/proc/{pid}/mem`.
27    File,
28    /// use `process_vm_readv` and `process_vm_writev` syscalls.
29    #[cfg(feature = "syscall")]
30    Syscall,
31}
32
33/// represents a process handle for memory operations.
34pub 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    /// open a process given its executable name.
86    ///
87    /// this will use the first process with the given name.
88    ///
89    /// # example
90    /// ```rust
91    /// let process = Process::open_exe_name("bash").unwrap();
92    /// ```
93    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    /// open a process from its pid.
99    ///
100    /// determines availability of `process_vm_*` syscalls and chooses the right mode.
101    pub fn open_pid(pid: i32) -> Result<Process, ProcessError> {
102        // test whether process_vm_readv is a valid syscall
103        // call it with dummy data and see what happens
104        #[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    /// switch between `Syscall` and `File` mode at runtime.
136    #[cfg(feature = "syscall")]
137    pub fn set_mode(&mut self, mode: MemoryMode) {
138        self.mode = mode;
139    }
140
141    /// check if the process is still running and valid
142    pub fn is_running(&self) -> bool {
143        Path::new(&format!("/proc/{}/mem", self.pid)).exists()
144    }
145
146    /// get the pid of the target process.
147    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    /// read a value T from the specified address.
199    ///
200    /// the type must implement [`bytemuck::AnyBitPattern`].
201    /// in Syscall mode uses `process_vm_readv`, in File mode uses FileExt::read_at.
202    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    /// read a vec of T with `count` elements from the specified address.
214    ///
215    /// the type must implement [`bytemuck::AnyBitPattern`].
216    /// in Syscall mode uses `process_vm_readv`, in File mode uses FileExt::read_at.
217    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    /// write a value T to the specified address.
281    ///
282    /// returns number of bytes written.
283    ///
284    /// the type must implement [`bytemuck::NoUninit`].
285    /// in Syscall mode uses `process_vm_writev`, in File mode uses FileExt::write_at.
286    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    /// write a vec of T to the specified address.
292    ///
293    /// returns number of bytes written.
294    ///
295    /// the type must implement [`bytemuck::NoUninit`].
296    /// in Syscall mode uses `process_vm_writev`, in File mode uses FileExt::write_at.
297    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    /// reads `count` bytes starting at `address`, using File mode.
303    ///
304    /// process_vm_readv does not work for very large reads,
305    /// which is why File mode is always used.
306    /// it will not switch the mode for other reads and writes.
307    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    /// writes `count` bytes starting at `address`, using File mode.
316    ///
317    /// process_vm_writev does not work for very large writes,
318    /// which is why File mode is always used.
319    /// it will not switch the mode for other reads and writes.
320    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    /// reads a c-style null-terminated string starting at `address`
328    /// until a `0` byte.
329    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    /// reads a utf-8 encoded string starting at `address` with a given length.
354    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    /// writes any string-like starting at `address`
360    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    /// parses `/proc/{pid}/maps` to locate the base address of a loaded
369    /// library with name matching `library`.
370    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    /// returns the size of an elf library
407    pub fn elf_size(&self, library: &LibraryInfo) -> Result<usize, MemoryError> {
408        if library.offset() != 0 {
409            return Err(MemoryError::OutOfRange);
410        }
411        // check if elf header is present
412        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    /// dump a complete elf library.
427    ///
428    /// this will return a complete copy of the library, as it is loaded into memory.
429    ///
430    /// it will fail if the library is not a valid elf, or the library offset is not 0.
431    pub fn dump_library(&self, library: &LibraryInfo) -> Result<Vec<u8>, MemoryError> {
432        if library.offset() != 0 {
433            return Err(MemoryError::InvalidLibrary);
434        }
435        // check if elf header is present
436        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    /// scan a pattern in library at `address`, using `pattern`.
445    ///
446    /// the pattern accepted is a normal ida pattern.
447    ///
448    /// # example
449    ///
450    /// ```rust
451    /// let process = Process::open_exe_name("bash").unwrap();
452    /// process.scan_pattern("12 34 ? ? 56 78", 0x12345678);
453    /// ```
454    ///
455    /// this scans the ida pattern `12 34 ? ? 56 78`.
456    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    /// get own process pid.
504    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        // find loaded process elf
597        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        // convert hello world string to ida pattern
602        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}