raminspect/
lib.rs

1//! raminspect is a crate that allows for the inspection and manipulation of the memory and code of 
2//! a running process on a Linux system. It provides functions for finding and replacing search terms 
3//! in a processes' memory, as well as an interface that allows for the injection of arbitrary shellcode 
4//! running in the processes' context. All of this requires root privileges, for obvious reasons.
5
6// Starting from v0.3.0, we use libc and alloc instead of std and nix to support 
7// architectures like 32-bit RISCV which don't have standard library support. This 
8// complicates the code quite a bit but it's a price I'm willing to pay for cross-
9// platform support.
10
11#![no_std]
12extern crate alloc;
13
14use libc::*;
15use core::sync::atomic::Ordering;
16use core::sync::atomic::AtomicUsize;
17
18use alloc::vec;
19use alloc::format;
20use alloc::vec::Vec;
21use alloc::sync::Arc;
22use alloc::vec::IntoIter;
23use alloc::string::String;
24/// Used for cleaner handling of errors from calling libc functions
25
26trait IntoResult: Sized {
27    fn into_result(self, error: RamInspectError) -> Result<Self, RamInspectError>;
28}
29
30macro_rules! impl_into_result_for_num {
31    ($num_ty:ty) => {
32        impl IntoResult for $num_ty {
33            fn into_result(self, error: RamInspectError) -> Result<Self, RamInspectError> {
34                if self < 0 {
35                    Err(error)
36                } else {
37                    Ok(self)
38                }
39            }
40        }
41    }
42}
43
44impl_into_result_for_num!(i32);
45impl_into_result_for_num!(i64);
46impl_into_result_for_num!(isize);
47
48impl<T> IntoResult for *mut T {
49    fn into_result(self, error: RamInspectError) -> Result<Self, RamInspectError> {
50        if self.is_null() {
51            Err(error)
52        } else {
53            Ok(self)
54        }
55    }
56}
57
58/// A wrapper around a raw file descriptor that closes itself when
59/// dropped. This exists to prevent leaks.
60
61struct FileWrapper {
62    descriptor: i32
63}
64
65impl FileWrapper {
66    fn open(path: &str, mode: i32, on_err: RamInspectError) -> Result<Self, RamInspectError> {
67        // Assert that the provided string is null terminated
68        assert!(path.ends_with('\0'));
69
70        Ok(Self {
71            descriptor: unsafe {
72                // This is safe because we already asserted that the path is null-terminated
73                open(path.as_ptr() as _, mode).into_result(on_err)?
74            }
75        })
76    }
77}
78
79impl Drop for FileWrapper {
80    fn drop(&mut self) {
81        unsafe {
82            close(self.descriptor);
83        }
84    }
85}
86
87/// A packet sent to the backend kernel module through an 'ioctl' call 
88/// that requests the current instruction pointer of an application.
89
90#[repr(C)]
91struct InstructionPointerRequest {
92    pid: i32,
93    instruction_pointer: u64,
94}
95
96// ioctl command definitions
97const RESTORE_REGS: c_ulong = 0x40047B03;
98const GET_INST_PTR: c_ulong = 0xC0107B02;
99const WAIT_FOR_FINISH: c_ulong = 0x40047B00;
100const TOGGLE_EXEC_WRITE: c_ulong = 0x40047B01;
101
102/// Finds a list of all processes containing a given search term in their 
103/// program name. This makes figuring out the process ID of the process 
104/// you want to inspect or inject shellcode into easier.
105
106pub fn find_processes(name_contains: &str) -> Vec<i32> {
107    let mut results = Vec::new();
108    const MAX_LINE_LENGTH: usize = 4096;
109
110    unsafe {
111        // Iterate over all process IDs in the /proc directory
112        let dirp = opendir("/proc\0".as_ptr() as _);
113        if dirp.is_null() { return results; }
114
115        loop {
116            let entry_ptr = readdir(dirp);
117            if entry_ptr.is_null() { break; }
118
119            // Convert the array of C chars representing the directory name to a Rust string slice.
120            let name_bytes: [u8; 256] = core::mem::transmute(core::ptr::read(entry_ptr).d_name);
121            let end_of_str = name_bytes.iter().position(|byte| *byte == 0).unwrap();
122            let name = core::str::from_utf8(&name_bytes[..end_of_str]).unwrap();
123
124            // Make sure it's a PID's proc directory before continuing.
125            let pid = match name.parse::<i32>() {
126                Ok(pid) => pid,
127                Err(_) => continue,
128            };
129
130            let path = format!("/proc/{}/cmdline\0", pid);
131            let fd = open(path.as_ptr() as _, O_RDONLY);
132            if fd < 0 { continue; }
133
134            let mut buf = vec![0; MAX_LINE_LENGTH];
135            if read(fd, buf.as_mut_ptr() as _, buf.len()) < 0 {
136                close(fd);
137                continue;
138            }
139
140            // The first occurence of a null byte in /proc/pid/cmdline delineates the end of
141            // the processes' command invocation name. See the corresponding section on this 
142            // page for more information: https://man7.org/linux/man-pages/man5/proc.5.html
143
144            let executable_name = core::str::from_utf8(
145                &buf[..buf.iter().position(|byte| *byte == 0).unwrap_or(buf.len())]
146            ).unwrap();
147
148            if executable_name.contains(name_contains) {
149                results.push(pid);
150            }
151
152            close(fd);
153        }
154    }
155    
156    results
157}
158
159/// This is the primary interface used by the crate to search through, read, and modify an
160/// arbitrary processes' memory and code.
161/// 
162/// Note that when an inspector is created for a process, the process will be paused until
163/// the inspector is dropped in order to ensure that we have exclusive access to the
164/// processes' memory, unless it is manually resumed through a call to 
165/// [`RamInspector::resume_process`].
166/// 
167/// # Example Usage
168/// 
169/// ```rust
170/// //! This example changes the current text in Firefox's browser search bar from 
171/// //! "Old search text" to "New search text". To run this example, open an instance
172/// //! of Firefox and type "Old search text" in the search bar. If all goes well, when
173/// //! you run this example as root, it should be replaced with "New search text",
174/// //! although you may have to click on the search bar again in order for it to
175/// //! render the new text.
176/// 
177/// fn main() {
178///     use raminspect::RamInspector;
179///     // Iterate over all running Firefox instances
180///     for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
181///         let mut inspector = match RamInspector::new(pid) {
182///             Ok(inspector) => inspector,
183///             Err(_) => continue,
184///         };
185///         
186///         for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
187///             if !memory_region.writable() {
188///                 continue;
189///             }
190/// 
191///             unsafe {
192///                 // This is safe because modifying the text in the Firefox search bar will not crash
193///                 // the browser or negatively impact system stability in any way.
194/// 
195///                 println!("Writing to process virtual address: 0x{:X}", proc_addr);
196///                 inspector.queue_write(proc_addr, b"New search text");
197///             }
198///         }
199/// 
200///         unsafe {
201///             // This is safe since the process is not currently resumed, which would possibly cause a data race.
202///             inspector.flush().unwrap();
203///         }
204///     }
205/// }
206/// ```
207
208pub struct RamInspector {
209    pid: i32,
210    max_iovs: usize,
211    proc_maps_file: *mut FILE,
212    resume_count: Arc<AtomicUsize>,
213    write_requests: Vec<(usize, Vec<u8>)>,
214}
215
216// This is safe because the pointer inside can only be accessed through methods 
217// that take a mutable reference to the inspector, and therefore all accesses 
218// to it must be synchronized.
219
220unsafe impl Send for RamInspector {}
221unsafe impl Sync for RamInspector {}
222
223#[non_exhaustive]
224#[derive(Clone, Copy)]
225/// The error type for this library. The variants have self-explanatory names.
226
227pub enum RamInspectError {
228    ProcessTerminated,
229    FailedToOpenProcMaps,
230    FailedToPauseProcess,
231    FailedToResumeProcess,
232
233    FailedToReadMem,
234    FailedToWriteMem,
235    FailedToOpenDeviceFile,
236    FailedToAllocateBuffer,
237    InspectorAlreadyExists,
238}
239
240use core::fmt;
241use core::fmt::Debug;
242use core::fmt::Formatter;
243impl Debug for RamInspectError {
244    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
245        formatter.write_str(match self {
246            RamInspectError::InspectorAlreadyExists => "A `RamInspector` instance already exists for the specified process ID. \
247                                                        Note: If you're in a multi-threaded environment, instead of creating \
248                                                        multiple inspectors you can try accessing one inspector through a \
249                                                        mutex.",
250
251            RamInspectError::FailedToOpenDeviceFile => "Failed to open the raminspect device file! Are you sure the kernel module is currently inserted? If it is, are you running as root?",
252            RamInspectError::FailedToOpenProcMaps => "Failed to access the target processes' memory maps! Are you sure you're running as root? If you are, is the target process running?",
253            RamInspectError::FailedToWriteMem => "Failed to write to the specified memory address! Are you sure the address is in a writable region of the processes' memory?",
254            RamInspectError::FailedToReadMem => "Failed to read from the specified memory address! Are you sure the address is in a readable region of the processes' memory?",
255            RamInspectError::FailedToResumeProcess => "Failed to resume the target process! Are you sure it is currently running?",
256            RamInspectError::FailedToPauseProcess => "Failed to pause the target process! Are you sure it is currently running?",
257            RamInspectError::FailedToAllocateBuffer => "Failed to allocate the specified buffer.",
258            RamInspectError::ProcessTerminated => "The target process unexpectedly terminated.",
259        })
260    }
261}
262
263use core::fmt::Display;
264impl Display for RamInspectError {
265    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
266        Debug::fmt(self, formatter)
267    }
268}
269
270use spin::Mutex;
271static INSPECTED_PIDS: Mutex<Vec<i32>> = Mutex::new(Vec::new());
272
273impl RamInspector {
274    /// Creates a new inspector attached to the specified process ID. This will pause the target process until
275    /// the inspector is dropped or until it's manually resumed using [`RamInspector::resume_process`].
276    /// 
277    /// Note that creating two inspectors simultaneously referring to the same process is not supported, and attempting 
278    /// to do so will return a [`RamInspectError`] of the kind [`RamInspectError::InspectorAlreadyExists`]. If you want 
279    /// to do this you should access the same inspector instead, synchronizing said access if you're in a multithreaded 
280    /// environment.
281    
282    pub fn new(pid: i32) -> Result<Self, RamInspectError> {
283        unsafe {
284            if INSPECTED_PIDS.lock().contains(&pid) {
285                return Err(RamInspectError::InspectorAlreadyExists);
286            }
287
288            INSPECTED_PIDS.lock().push(pid);
289            let maps_path = format!("/proc/{}/maps\0", pid);
290            let proc_maps_file = fopen(maps_path.as_ptr() as _, "r\0".as_ptr() as _).into_result(
291                RamInspectError::FailedToOpenProcMaps
292            )?;
293    
294            // Pause the target process with a SIGSTOP signal
295            if let Err(error) = kill(pid, SIGSTOP).into_result(RamInspectError::FailedToPauseProcess) {
296                fclose(proc_maps_file);
297                return Err(error);
298            }
299
300            let max_iovs = sysconf(_SC_IOV_MAX);
301
302            if max_iovs < 0 {
303                fclose(proc_maps_file);
304                panic!("Unsupported kernel version or platform.");
305            }
306
307            Ok(RamInspector {
308                pid,
309                proc_maps_file,
310                write_requests: Vec::new(),
311                max_iovs: max_iovs as usize,
312                resume_count: Arc::new(AtomicUsize::new(0)),
313            })
314        }
315    }
316
317    /// Resumes the target process, returning a handle that pauses the process again when dropped,
318    /// assuming no other handles currently exist. Use this carefully, since writing to the 
319    /// processes' memory while it's resumed may cause data races with the processes' code.
320    /// 
321    /// If multiple handles are created before all the others are dropped, the process will remain 
322    /// resumed until every one of its resume handles is dropped and dropping an individual handle 
323    /// while other handles for the process still exist will have no effect. This ensures 
324    /// correctness in multi-threaded contexts.
325    
326    pub fn resume_process(&self) -> Result<ResumeHandle, RamInspectError> {
327        if self.resume_count.fetch_add(1, Ordering::SeqCst) == 0 {
328            unsafe {
329                kill(self.pid, SIGCONT).into_result(RamInspectError::FailedToResumeProcess)?;
330            }
331        }
332
333        Ok(ResumeHandle {
334            pid: self.pid,
335            count: Arc::clone(&self.resume_count),
336        })
337    }
338
339    /// Allows for the execution of arbitrary code in the context of the process. This is unsafe
340    /// because there are no checks in place to ensure the provided code is safe. The provided
341    /// code should also be completely position independent, since it could be loaded anywhere.
342    /// 
343    /// This function waits for a signal from the shellcode that it is finished executing, given
344    /// by reading exactly one byte from the raminspect device file. It does not time out, so if
345    /// you forget to send the signal you'll have to terminate the hijacked process for this 
346    /// function to resume and the shellcode to finish executing.
347    /// 
348    /// The second argument is a callback that is called once the shellcode is finished executing
349    /// that takes in a mutable reference to the inspector and the starting address of the loaded 
350    /// shellcode as arguments, before the old instructions are restored in memory. This can be
351    /// useful if you want to retrieve information from the shellcode after it's done executing.
352    /// 
353    /// Note that this restores the previous register state automatically, so you don't have to 
354    /// save and restore registers in your shellcode manually if you're writing it in assembly.
355    
356    pub unsafe fn execute_shellcode<F: FnMut(&mut RamInspector, usize) -> Result<(), RamInspectError>>(
357        &mut self,
358        shellcode: &[u8],
359        mut callback: F,
360    ) -> Result<(), RamInspectError> {
361        let device_fd_wrapper = FileWrapper::open("/dev/raminspect\0", O_RDWR, RamInspectError::FailedToOpenDeviceFile)?;
362        let device_fd = device_fd_wrapper.descriptor;
363
364        // Temporarily make the code of the process writable so we can modify it.
365        ioctl(device_fd, TOGGLE_EXEC_WRITE, self.pid as c_ulong).into_result(RamInspectError::ProcessTerminated)?;
366
367        // Get process instruction pointer. ptrace and /proc/stat don't work here, at least on my machine, so we
368        // rely on the kernel module to do it for us instead.
369
370        let mut inst_ptr_request = InstructionPointerRequest {
371            pid: self.pid,
372            instruction_pointer: 0,
373        };
374
375        ioctl(device_fd, GET_INST_PTR, &mut inst_ptr_request).into_result(RamInspectError::ProcessTerminated)?;
376        let instruction_pointer = inst_ptr_request.instruction_pointer as usize;
377        
378        // Save the old code and load the new code
379        let old_code = self.read_vec(instruction_pointer, shellcode.len())?;
380        self.write_to_address(instruction_pointer, shellcode)?;
381
382        // Resume the process and wait for the code to finish executing
383        kill(self.pid, SIGCONT).into_result(RamInspectError::ProcessTerminated)?;
384        ioctl(device_fd, WAIT_FOR_FINISH, self.pid as c_ulong).into_result(RamInspectError::ProcessTerminated)?;
385
386        // Then pause the process again and call the callback
387        kill(self.pid, SIGSTOP).into_result(RamInspectError::ProcessTerminated)?;
388        callback(self, instruction_pointer)?;
389
390        // Restore the old code and registers
391        self.write_to_address(instruction_pointer, &old_code)?;
392        ioctl(device_fd, RESTORE_REGS, self.pid as c_ulong).into_result(RamInspectError::ProcessTerminated)?;
393
394        // Leaving the target code as writable when it was originally read-only would present 
395        // a fairly big security issue, so we make the modified regions read-only again after 
396        // we're done by performing another ioctl.
397        
398        ioctl(device_fd, TOGGLE_EXEC_WRITE, self.pid as c_ulong).into_result(RamInspectError::ProcessTerminated)?;
399        Ok(())
400    }
401
402    /// Allocates a new buffer with the given size for the current process and returns the address
403    /// of it. Currently this only works on x86-64, but PRs to expand it to work on other CPU
404    /// architectures are welcome.
405    /// 
406    /// Note that due to the way this is implemented this function is fairly expensive. Don't use this many 
407    /// times in a hot loop; try to make a few big allocations instead of many small ones for better 
408    /// performance.
409    
410    pub fn allocate_buffer(&mut self, size: usize) -> Result<usize, RamInspectError> {
411        assert!(cfg!(target_arch = "x86_64"), "`allocate_buffer` is currently only supported on x86-64.");
412        let mut shellcode: Vec<u8> = include_bytes!("../alloc-blob.bin").to_vec();
413        let alloc_size_offset = shellcode.len() - 8;
414        let out_ptr_offset = shellcode.len() - 16;
415
416        shellcode[alloc_size_offset..alloc_size_offset + 8].copy_from_slice(
417            &size.to_le_bytes()
418        );
419        
420        unsafe {
421            let mut addr_bytes = [0; 8];
422            self.execute_shellcode(&shellcode, |this, inst_ptr| {
423                this.read_address(inst_ptr + out_ptr_offset, &mut addr_bytes)
424            })?;
425
426            Ok(u64::from_le_bytes(addr_bytes) as usize)
427        }
428    }
429
430    /// Fills the output buffer with memory read starting from the target address. This can fail
431    /// if the target process was suddenly terminated or if the address used is not part of a
432    /// readable memory region of the process. 
433    /// 
434    /// Note that this may spuriously fail if the target address is part of a shared memory region 
435    /// (e.g. a memory mapped file), in which case you should always handle errors.
436    /// 
437    /// If you're making large amounts of small reads, prefer [`RamInspector::read_bulk`] over
438    /// this function, which only performs one I/O syscall.
439    
440    pub fn read_address(&mut self, addr: usize, out_buf: &mut [u8]) -> Result<(), RamInspectError> {
441        self.read_bulk(core::iter::once((addr, out_buf)))
442    }
443
444    /// A convenience function that reads the specified amount of bytes from the target address
445    /// and stores the output in a vector. This is shorthand for:
446    /// 
447    /// ```rust
448    /// let mut out = vec![0; count];
449    /// inspector.read_address(addr, &mut out);
450    /// ```
451    
452    pub fn read_vec(&mut self, addr: usize, count: usize) -> Result<Vec<u8>, RamInspectError> {
453        let mut out = vec![0; count];
454        self.read_address(addr, &mut out)?;
455        Ok(out)
456    }
457
458    // Used internally to simplify bulk reads and writes of data that use iovecs
459    unsafe fn exec_iov_op(&self, local_iovs: Vec<iovec>, remote_iovs: Vec<iovec>, iov_op: unsafe extern "C" fn(
460        pid_t,
461        *const iovec, c_ulong,
462        *const iovec, c_ulong, c_ulong
463    ) -> isize, err: RamInspectError) -> Result<(), RamInspectError> {
464        assert_eq!(local_iovs.len(), remote_iovs.len());
465
466        let mut i = 0;
467        while i < local_iovs.len() {
468            let end_index = (i + self.max_iovs).min(local_iovs.len());
469            let num_iovs = (end_index - i) as _;
470
471            iov_op(
472                self.pid,
473                local_iovs[i..end_index].as_ptr(), num_iovs,
474                remote_iovs[i..end_index].as_ptr(), num_iovs, 0,
475            ).into_result(err)?;
476            i += self.max_iovs;
477        }
478
479        Ok(())
480    }
481
482    /// Performs many memory reads at once in one I/O syscall, taking in an iterator of address / output
483    /// buffer pairs as an argument. This can be much faster than [`RamInspector::read_address`] if 
484    /// you're making many small data reads, and should be preferred in that case. This has the
485    /// same failure conditions as `read_address`.
486    
487    pub fn read_bulk<T: AsMut<[u8]>, I: Iterator<Item = (usize, T)>>(
488        &mut self,
489        reads: I,
490    ) -> Result<(), RamInspectError> {
491        let mut local_iovs = Vec::with_capacity(reads.size_hint().0);
492        let mut remote_iovs = Vec::with_capacity(reads.size_hint().0);
493
494        for (address, mut buf) in reads {
495            let buf = buf.as_mut();
496            local_iovs.push(iovec {
497                iov_len: buf.len(),
498                iov_base: buf.as_mut_ptr() as _,
499            });
500
501            remote_iovs.push(iovec {
502                iov_len: buf.len(),
503                iov_base: address as _,
504            });
505        }
506
507        unsafe {
508            self.exec_iov_op(
509                local_iovs, remote_iovs, 
510                process_vm_readv, RamInspectError::FailedToReadMem,
511            )?;
512        }
513
514        Ok(())
515    }
516
517    /// A convenience function for performing one write of arbitrary data to an arbitrary memory address. 
518    /// This does not flush the current write buffer, and is guaranteed to perform exactly one write.
519    /// 
520    /// If you're making many writes, use [`RamInspector::queue_write`] in combination with [`RamInspector::flush`]
521    /// instead. This has the same safety constraints as `queue_write`, and is just a thin wrapper around it.
522    
523    pub unsafe fn write_to_address(&mut self, addr: usize, buf: &[u8]) -> Result<(), RamInspectError> {
524        let mut old_buffer = Vec::new(); 
525        core::mem::swap(&mut self.write_requests, &mut old_buffer);
526        
527        self.queue_write(addr, buf);
528        let res = self.flush();
529
530        self.write_requests = old_buffer;
531        res
532    }
533
534    /// Queues a write of the specified data to the specified memory address of the target process. 
535    /// Writes will fail if the target process unexpectedly terminated, if the specified address is 
536    /// not part of a writable region of the target processes' memory, and if the end address (the 
537    /// start address plus the written buffers' length) is not part of the same memory region.
538    /// 
539    /// This is unsafe since directly writing to an arbitrary address in an arbitrary processes' 
540    /// memory is not memory safe at all; it is assumed that the caller knows what they're doing.
541    /// 
542    /// Note that this has no effect until the [`RamInspector::flush`] method is called, for
543    /// performance reasons.
544    
545    pub unsafe fn queue_write(&mut self, addr: usize, buf: &[u8]) {
546        self.write_requests.push((addr, buf.to_vec()));
547    }
548
549    /// Flushes the current buffer of writes, performing all of them in one I/O syscall. This is unsafe 
550    /// for the same reasons that `queue_write` is unsafe, and is called automatically upon dropping
551    /// the inspector. See [`RamInspector::queue_write`] for more information.
552    
553    pub unsafe fn flush(&mut self) -> Result<(), RamInspectError> {
554        let local_iovs = self.write_requests.iter().map(|(_addr, buf)| iovec {
555            iov_base: buf.as_ptr() as _,
556            iov_len: buf.len(),
557        }).collect::<Vec<iovec>>();
558
559        let remote_iovs = self.write_requests.iter().map(|(addr, buf)| iovec {
560            iov_base: (*addr) as _,
561            iov_len: buf.len(),
562        }).collect::<Vec<iovec>>();
563        
564        self.exec_iov_op(local_iovs, remote_iovs, process_vm_writev, RamInspectError::FailedToWriteMem)?;
565        self.write_requests.clear();
566        Ok(())
567    }
568
569    /// A function that returns an iterator over the target processes' memory regions, generated by reading its
570    /// /proc/maps file. See the documentation of [`MemoryRegion`] for more information.
571    
572    pub fn regions(&mut self) -> IntoIter<MemoryRegion> {
573        unsafe {
574            fseek(self.proc_maps_file, 0, SEEK_SET);
575        }
576
577        // For more details about what this calculation in particular means, see the section
578        // for /proc/pid/maps at: https://man7.org/linux/man-pages/man5/proc.5.html
579
580        const MAX_INODE_DIGITS: usize = 16;
581        const MAX_PATH_LENGTH: usize = 4096;
582        const MAX_LINE_LENGTH: usize = "ffffffffffffffff-ffffffffffffffff rwxp ffffffff ff:ff ".len() + 
583                                       MAX_INODE_DIGITS + "      ".len() + MAX_PATH_LENGTH;
584
585        let mut regions = Vec::new();
586        let mut line: [u8; MAX_LINE_LENGTH] = [0; MAX_LINE_LENGTH];
587        while unsafe { !fgets(line.as_mut_ptr() as _, line.len() as i32, self.proc_maps_file).is_null() } {
588            let line_str = core::str::from_utf8(
589                &line[..line.iter().position(|byte| *byte == 0).unwrap_or(line.len())]
590            ).unwrap().trim();
591
592            // Skip any bad or unneeded memory regions
593            if line_str.ends_with("(deleted)") || line_str.ends_with("[vvar]")  || line_str.ends_with("[vdso]")  || line_str.ends_with("[vsyscall]") {
594                continue;    
595            }
596
597            let mut chars = line_str.chars();
598            // The lines read from /proc/PID/maps conform to the following format:
599            //
600            // HEX_START_ADDR-HEX_END_ADDR rwx(p or s)... etc
601            //
602            // Where rwx describes whether or not the described memory region can be read from, written 
603            // to, and executed. If not the corresponding character will be dashed out. For example, 
604            // read-only executable memory areas would show an r-x in the string and write-only 
605            // non-executable ones would show a -w-. 
606            //
607            // The next character following this (the p or s) describes whether or not the specified region 
608            // is private or shared, and cannot be dashed out.
609
610            let start_addr_string = (&mut chars).take_while(char::is_ascii_hexdigit).collect::<String>();
611            let end_addr_string = (&mut chars).take_while(char::is_ascii_hexdigit).collect::<String>();
612            let start_addr = usize::from_str_radix(&start_addr_string, 16).unwrap();
613            let end_addr = usize::from_str_radix(&end_addr_string, 16).unwrap();
614            assert!(end_addr > start_addr);
615
616            regions.push(MemoryRegion {
617                start_addr,
618                length: end_addr - start_addr,
619                readable: chars.next().unwrap() == 'r',
620                writeable: chars.next().unwrap() == 'w',
621                executable: chars.next().unwrap() == 'x',
622                shared: chars.next().unwrap() == 's',
623            });
624
625            line = [0; MAX_LINE_LENGTH];
626        }
627
628        regions.into_iter()
629    }
630
631    /// Searches the target processes' memory for the specified data, and returns a list of
632    /// addresses of found search results and the memory regions that they are contained in. 
633    /// This will fail if the process terminated unexpectedly, but it should succeed in 
634    /// basically any other case.
635    
636    pub fn search_for_term(&mut self, search_term: &[u8]) -> Result<Vec<(usize, MemoryRegion)>, RamInspectError> {
637        if search_term.is_empty() {
638            return Ok(Vec::new());
639        }
640
641        let mut out = Vec::new();
642        for region in self.regions().filter(|region| region.readable) {
643            if region.len() < search_term.len() {
644                continue;
645            }
646            
647            if let Ok(data) = region.get_contents(self) {
648                for i in 0..data.len() - search_term.len() {
649                    if data[i..].starts_with(search_term) {
650                        out.push((region.start_addr + i, region.clone()));
651                    }
652                }
653            }
654        }
655
656        Ok(out)
657    }
658}
659
660impl Drop for RamInspector {
661    fn drop(&mut self) {
662        unsafe {
663            // Flush all buffers.
664            let _ = self.flush();
665
666            // Free allocated resources.
667            fclose(self.proc_maps_file);
668
669            // Resume the target process on drop with a SIGCONT. We ignore errors here
670            // since there's no guarantee that the process is still running, so trying
671            // to send a signal to it might fail.
672            kill(self.pid, SIGCONT);
673
674            // Open up the PID for a new inspector.
675            let mut pids = INSPECTED_PIDS.lock();
676            let pos = pids.iter().position(|pid| *pid == self.pid).unwrap();
677            pids.swap_remove(pos);
678        }
679    }
680}
681
682/// A description of a memory region spanning any given address 
683/// range with information about its start address, its access 
684/// permissions (i.e. whether it's readable, writable, and/or 
685/// executable), and whether or not it's shared or private.
686/// 
687/// You can obtain an iterator over all of a processes' memory
688/// regions using the [`RamInspector::regions`] method.
689
690#[derive(Debug, Clone)]
691pub struct MemoryRegion {
692    start_addr: usize,
693    length: usize,
694
695    executable: bool,
696    writeable: bool,
697    readable: bool,
698    shared: bool,
699}
700
701impl MemoryRegion {
702    /// Attempts to read the contents of the memory region. This fails if the memory region is
703    /// not readable, and may spuriously fail if the memory region is shared (in which case
704    /// you should always handle errors).
705    
706    pub fn get_contents(&self, inspector: &mut RamInspector) -> Result<Vec<u8>, RamInspectError> {
707        inspector.read_vec(self.start_addr, self.length)
708    }
709
710    /// Gets the start address of the memory region.
711    pub fn start_addr(&self) -> usize {
712        self.start_addr
713    }
714
715    /// Gets the length of the memory region.
716    pub fn len(&self) -> usize {
717        self.length
718    }
719
720    /// Gets the end address of the memory region. This is equivalent to
721    /// adding the length to the start address.
722    
723    pub fn end_addr(&self) -> usize {
724        self.start_addr + self.length
725    }
726
727    /// Checks if the memory region is readable.
728    pub fn readable(&self) -> bool {
729        self.readable
730    }
731
732    /// Checks if the memory region is shared.
733    pub fn shared(&self) -> bool {
734        self.shared
735    }
736
737    /// Checks if the memory region is writable.
738    pub fn writable(&self) -> bool {
739        self.writeable
740    }
741
742    /// Checks if the memory region is executable.
743    pub fn executable(&self) -> bool {
744        self.executable
745    }
746
747    /// Checks whether or not the memory region is both readable and writable.
748    pub fn is_readwrite(&self) -> bool {
749        self.readable && self.writeable
750    }
751}
752
753/// A handle obtained by calling the [`RamInspector::resume_process`] method that 
754/// re-pauses the target process when dropped, assuming no other handles for the
755/// process currently exist. See the docs of that method for more information.
756
757#[must_use]
758pub struct ResumeHandle {
759    pid: i32,
760    count: Arc<AtomicUsize>,
761}
762
763impl Drop for ResumeHandle {
764    fn drop(&mut self) {
765        if self.count.fetch_sub(1, Ordering::SeqCst) == 1 {
766            unsafe {
767                kill(self.pid, SIGSTOP);
768            }
769        }
770    }
771}