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
impl RamInspector
Sourcepub fn new(pid: i32) -> Result<Self, RamInspectError>
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?
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
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}Sourcepub fn resume_process(&self) -> Result<ResumeHandle, RamInspectError>
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.
Sourcepub unsafe fn execute_shellcode<F: FnMut(&mut RamInspector, usize) -> Result<(), RamInspectError>>(
&mut self,
shellcode: &[u8],
callback: F,
) -> Result<(), RamInspectError>
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.
Sourcepub fn allocate_buffer(&mut self, size: usize) -> Result<usize, RamInspectError>
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.
Sourcepub fn read_address(
&mut self,
addr: usize,
out_buf: &mut [u8],
) -> Result<(), RamInspectError>
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.
Sourcepub fn read_vec(
&mut self,
addr: usize,
count: usize,
) -> Result<Vec<u8>, RamInspectError>
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);Sourcepub fn read_bulk<T: AsMut<[u8]>, I: Iterator<Item = (usize, T)>>(
&mut self,
reads: I,
) -> Result<(), RamInspectError>
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.
Sourcepub unsafe fn write_to_address(
&mut self,
addr: usize,
buf: &[u8],
) -> Result<(), RamInspectError>
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.
Sourcepub unsafe fn queue_write(&mut self, addr: usize, buf: &[u8])
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?
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
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}Sourcepub unsafe fn flush(&mut self) -> Result<(), RamInspectError>
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?
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
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}Sourcepub fn regions(&mut self) -> IntoIter<MemoryRegion>
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.
Sourcepub fn search_for_term(
&mut self,
search_term: &[u8],
) -> Result<Vec<(usize, MemoryRegion)>, RamInspectError>
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?
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
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}