RamInspector

Struct RamInspector 

Source
pub struct RamInspector { /* private fields */ }
Expand description

This is the primary interface used by the crate to search through, read, and modify an arbitrary processes’ memory and code.

Note that when an inspector is created for a process, the process will be paused until the inspector is dropped in order to ensure that we have exclusive access to the processes’ memory, unless it is manually resumed through a call to RamInspector::resume_process.

§Example Usage

//! This example changes the current text in Firefox's browser search bar from 
//! "Old search text" to "New search text". To run this example, open an instance
//! of Firefox and type "Old search text" in the search bar. If all goes well, when
//! you run this example as root, it should be replaced with "New search text",
//! although you may have to click on the search bar again in order for it to
//! render the new text.
 
fn main() {
    use raminspect::RamInspector;
    // Iterate over all running Firefox instances
    for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
        let mut inspector = match RamInspector::new(pid) {
            Ok(inspector) => inspector,
            Err(_) => continue,
        };
         
        for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
            if !memory_region.writable() {
                continue;
            }
 
            unsafe {
                // This is safe because modifying the text in the Firefox search bar will not crash
                // the browser or negatively impact system stability in any way.
 
                println!("Writing to process virtual address: 0x{:X}", proc_addr);
                inspector.queue_write(proc_addr, b"New search text");
            }
        }
 
        unsafe {
            // This is safe since the process is not currently resumed, which would possibly cause a data race.
            inspector.flush().unwrap();
        }
    }
}

Implementations§

Source§

impl RamInspector

Source

pub fn new(pid: i32) -> Result<Self, RamInspectError>

Creates a new inspector attached to the specified process ID. This will pause the target process until the inspector is dropped or until it’s manually resumed using RamInspector::resume_process.

Note that creating two inspectors simultaneously referring to the same process is not supported, and attempting to do so will return a RamInspectError of the kind RamInspectError::InspectorAlreadyExists. If you want to do this you should access the same inspector instead, synchronizing said access if you’re in a multithreaded environment.

Examples found in repository?
examples/replacemem.rs (line 50)
48    fn inspect_process(pid: i32, search_term: &str, replacement_term: &str) -> Result<(), RamInspectError> {
49        unsafe {
50            let mut inspector = RamInspector::new(pid)?;
51            for (result_addr, memory_region) in inspector.search_for_term(search_term.as_bytes())? {
52                if !memory_region.writable() {
53                    continue;
54                }
55
56                inspector.queue_write(result_addr, replacement_term.as_bytes());
57            }
58            
59            inspector.flush().unwrap();
60        }
61
62        Ok(())
63    }
More examples
Hide additional examples
examples/firefox_search.rs (line 12)
8fn main() {
9    use raminspect::RamInspector;
10    // Iterate over all running Firefox instances
11    for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
12        let mut inspector = match RamInspector::new(pid) {
13            Ok(inspector) => inspector,
14            Err(_) => continue,
15        };
16        
17        for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
18            if !memory_region.writable() {
19                continue;
20            }
21
22            unsafe {
23                // This is safe because modifying the text in the Firefox search bar will not crash
24                // the browser or negatively impact system stability in any way.
25
26                println!("Writing to process virtual address: 0x{:X}", proc_addr);
27                inspector.queue_write(proc_addr, b"New search text");
28            }
29        }
30
31        unsafe {
32            // This is safe since the process is not currently resumed, which would possibly cause a data race.
33            inspector.flush().unwrap();
34        }
35    }
36}
Source

pub fn resume_process(&self) -> Result<ResumeHandle, RamInspectError>

Resumes the target process, returning a handle that pauses the process again when dropped, assuming no other handles currently exist. Use this carefully, since writing to the processes’ memory while it’s resumed may cause data races with the processes’ code.

If multiple handles are created before all the others are dropped, the process will remain resumed until every one of its resume handles is dropped and dropping an individual handle while other handles for the process still exist will have no effect. This ensures correctness in multi-threaded contexts.

Source

pub unsafe fn execute_shellcode<F: FnMut(&mut RamInspector, usize) -> Result<(), RamInspectError>>( &mut self, shellcode: &[u8], callback: F, ) -> Result<(), RamInspectError>

Allows for the execution of arbitrary code in the context of the process. This is unsafe because there are no checks in place to ensure the provided code is safe. The provided code should also be completely position independent, since it could be loaded anywhere.

This function waits for a signal from the shellcode that it is finished executing, given by reading exactly one byte from the raminspect device file. It does not time out, so if you forget to send the signal you’ll have to terminate the hijacked process for this function to resume and the shellcode to finish executing.

The second argument is a callback that is called once the shellcode is finished executing that takes in a mutable reference to the inspector and the starting address of the loaded shellcode as arguments, before the old instructions are restored in memory. This can be useful if you want to retrieve information from the shellcode after it’s done executing.

Note that this restores the previous register state automatically, so you don’t have to save and restore registers in your shellcode manually if you’re writing it in assembly.

Source

pub fn allocate_buffer(&mut self, size: usize) -> Result<usize, RamInspectError>

Allocates a new buffer with the given size for the current process and returns the address of it. Currently this only works on x86-64, but PRs to expand it to work on other CPU architectures are welcome.

Note that due to the way this is implemented this function is fairly expensive. Don’t use this many times in a hot loop; try to make a few big allocations instead of many small ones for better performance.

Source

pub fn read_address( &mut self, addr: usize, out_buf: &mut [u8], ) -> Result<(), RamInspectError>

Fills the output buffer with memory read starting from the target address. This can fail if the target process was suddenly terminated or if the address used is not part of a readable memory region of the process.

Note that this may spuriously fail if the target address is part of a shared memory region (e.g. a memory mapped file), in which case you should always handle errors.

If you’re making large amounts of small reads, prefer RamInspector::read_bulk over this function, which only performs one I/O syscall.

Source

pub fn read_vec( &mut self, addr: usize, count: usize, ) -> Result<Vec<u8>, RamInspectError>

A convenience function that reads the specified amount of bytes from the target address and stores the output in a vector. This is shorthand for:

let mut out = vec![0; count];
inspector.read_address(addr, &mut out);
Source

pub fn read_bulk<T: AsMut<[u8]>, I: Iterator<Item = (usize, T)>>( &mut self, reads: I, ) -> Result<(), RamInspectError>

Performs many memory reads at once in one I/O syscall, taking in an iterator of address / output buffer pairs as an argument. This can be much faster than RamInspector::read_address if you’re making many small data reads, and should be preferred in that case. This has the same failure conditions as read_address.

Source

pub unsafe fn write_to_address( &mut self, addr: usize, buf: &[u8], ) -> Result<(), RamInspectError>

A convenience function for performing one write of arbitrary data to an arbitrary memory address. This does not flush the current write buffer, and is guaranteed to perform exactly one write.

If you’re making many writes, use RamInspector::queue_write in combination with RamInspector::flush instead. This has the same safety constraints as queue_write, and is just a thin wrapper around it.

Source

pub unsafe fn queue_write(&mut self, addr: usize, buf: &[u8])

Queues a write of the specified data to the specified memory address of the target process. Writes will fail if the target process unexpectedly terminated, if the specified address is not part of a writable region of the target processes’ memory, and if the end address (the start address plus the written buffers’ length) is not part of the same memory region.

This is unsafe since directly writing to an arbitrary address in an arbitrary processes’ memory is not memory safe at all; it is assumed that the caller knows what they’re doing.

Note that this has no effect until the RamInspector::flush method is called, for performance reasons.

Examples found in repository?
examples/replacemem.rs (line 56)
48    fn inspect_process(pid: i32, search_term: &str, replacement_term: &str) -> Result<(), RamInspectError> {
49        unsafe {
50            let mut inspector = RamInspector::new(pid)?;
51            for (result_addr, memory_region) in inspector.search_for_term(search_term.as_bytes())? {
52                if !memory_region.writable() {
53                    continue;
54                }
55
56                inspector.queue_write(result_addr, replacement_term.as_bytes());
57            }
58            
59            inspector.flush().unwrap();
60        }
61
62        Ok(())
63    }
More examples
Hide additional examples
examples/firefox_search.rs (line 27)
8fn main() {
9    use raminspect::RamInspector;
10    // Iterate over all running Firefox instances
11    for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
12        let mut inspector = match RamInspector::new(pid) {
13            Ok(inspector) => inspector,
14            Err(_) => continue,
15        };
16        
17        for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
18            if !memory_region.writable() {
19                continue;
20            }
21
22            unsafe {
23                // This is safe because modifying the text in the Firefox search bar will not crash
24                // the browser or negatively impact system stability in any way.
25
26                println!("Writing to process virtual address: 0x{:X}", proc_addr);
27                inspector.queue_write(proc_addr, b"New search text");
28            }
29        }
30
31        unsafe {
32            // This is safe since the process is not currently resumed, which would possibly cause a data race.
33            inspector.flush().unwrap();
34        }
35    }
36}
Source

pub unsafe fn flush(&mut self) -> Result<(), RamInspectError>

Flushes the current buffer of writes, performing all of them in one I/O syscall. This is unsafe for the same reasons that queue_write is unsafe, and is called automatically upon dropping the inspector. See RamInspector::queue_write for more information.

Examples found in repository?
examples/replacemem.rs (line 59)
48    fn inspect_process(pid: i32, search_term: &str, replacement_term: &str) -> Result<(), RamInspectError> {
49        unsafe {
50            let mut inspector = RamInspector::new(pid)?;
51            for (result_addr, memory_region) in inspector.search_for_term(search_term.as_bytes())? {
52                if !memory_region.writable() {
53                    continue;
54                }
55
56                inspector.queue_write(result_addr, replacement_term.as_bytes());
57            }
58            
59            inspector.flush().unwrap();
60        }
61
62        Ok(())
63    }
More examples
Hide additional examples
examples/firefox_search.rs (line 33)
8fn main() {
9    use raminspect::RamInspector;
10    // Iterate over all running Firefox instances
11    for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
12        let mut inspector = match RamInspector::new(pid) {
13            Ok(inspector) => inspector,
14            Err(_) => continue,
15        };
16        
17        for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
18            if !memory_region.writable() {
19                continue;
20            }
21
22            unsafe {
23                // This is safe because modifying the text in the Firefox search bar will not crash
24                // the browser or negatively impact system stability in any way.
25
26                println!("Writing to process virtual address: 0x{:X}", proc_addr);
27                inspector.queue_write(proc_addr, b"New search text");
28            }
29        }
30
31        unsafe {
32            // This is safe since the process is not currently resumed, which would possibly cause a data race.
33            inspector.flush().unwrap();
34        }
35    }
36}
Source

pub fn regions(&mut self) -> IntoIter<MemoryRegion>

A function that returns an iterator over the target processes’ memory regions, generated by reading its /proc/maps file. See the documentation of MemoryRegion for more information.

Source

pub fn search_for_term( &mut self, search_term: &[u8], ) -> Result<Vec<(usize, MemoryRegion)>, RamInspectError>

Searches the target processes’ memory for the specified data, and returns a list of addresses of found search results and the memory regions that they are contained in. This will fail if the process terminated unexpectedly, but it should succeed in basically any other case.

Examples found in repository?
examples/replacemem.rs (line 51)
48    fn inspect_process(pid: i32, search_term: &str, replacement_term: &str) -> Result<(), RamInspectError> {
49        unsafe {
50            let mut inspector = RamInspector::new(pid)?;
51            for (result_addr, memory_region) in inspector.search_for_term(search_term.as_bytes())? {
52                if !memory_region.writable() {
53                    continue;
54                }
55
56                inspector.queue_write(result_addr, replacement_term.as_bytes());
57            }
58            
59            inspector.flush().unwrap();
60        }
61
62        Ok(())
63    }
More examples
Hide additional examples
examples/firefox_search.rs (line 17)
8fn main() {
9    use raminspect::RamInspector;
10    // Iterate over all running Firefox instances
11    for pid in raminspect::find_processes("/usr/lib/firefox/firefox") {
12        let mut inspector = match RamInspector::new(pid) {
13            Ok(inspector) => inspector,
14            Err(_) => continue,
15        };
16        
17        for (proc_addr, memory_region) in inspector.search_for_term(b"Old search text").unwrap() {
18            if !memory_region.writable() {
19                continue;
20            }
21
22            unsafe {
23                // This is safe because modifying the text in the Firefox search bar will not crash
24                // the browser or negatively impact system stability in any way.
25
26                println!("Writing to process virtual address: 0x{:X}", proc_addr);
27                inspector.queue_write(proc_addr, b"New search text");
28            }
29        }
30
31        unsafe {
32            // This is safe since the process is not currently resumed, which would possibly cause a data race.
33            inspector.flush().unwrap();
34        }
35    }
36}

Trait Implementations§

Source§

impl Drop for RamInspector

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Send for RamInspector

Source§

impl Sync for RamInspector

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.