7 stable releases
2.0.0 | May 12, 2023 |
---|---|
1.4.1 | May 9, 2023 |
#427 in Unix APIs
22KB
416 lines
Memmod
This is a library to ease attaching to, reading from, and writing to a process. In the future, it will hopefully support more advanced procedures (such as injecting code, scanning, etc.).
Currently, it only supports Unix. I have access to a Windows dev machine, and plan on adding Windows support once I'm satisfied it's stable and ready for production.
Attaching to a process
There are two ways to attach to a process: Process::new
with a PID, and Process::find[_strict]
with a name. Both of these methods require root priveleges, since they attach to the process immediately.
If you want to find a process by name, use Process::find
. This will look for a process whose name includes the one provided. This can be dangerous: it will match cat file.txt | grep <name>
if it comes across that first. To perform a strict equality check, use Process::find_strict
. Tip: if you want to find the exact name of a process, try getting its PID, then running cat /proc/<pid>/status
. The first line ends in the full name.
Here's an example of attaching to a process:
use memmod::Process;
fn main() {
let proc = match Process::find("vscodium") {
Ok(proc) => proc,
Err(e) => {
eprintln!("Failed to attach: {e}");
return;
}
};
// When the process gets dropped, it will detach. Detaching
// fom a process automatically resumes it.
//
// To handle errrors when detaching, use `Process::detach`:
if let Err(e) = proc.detach() {
eprintln!("Failed to detach: {e}");
}
}
Reading/writing a process' memory
There are two ways to read a process' memory: using Process::read_word[_offset]
, and using a ProcessReader
.
The first method reads one word (an isize
) from the process. The _offset
variation adds the base address of the process to the address first. However, reading data this way can be clunky and annoying, so a ProcessReader
type is also provided, which implements Read
and handles individual bytes. You can create one using Process::reader[_offset]
.
By default, the reader will advance through memory with each read; this can be disabled with the builder-pattern-like ProcessReader::no_advance
method (this can also be called on an already-created reader). Afterwards, the reader will be "frozen" at its current address, and will always read from the same slice of memory.
Here's an example of reading from a process:
use memmod::Process;
fn main() {
let mut proc = Process::new(1234)
.expect("Failed to attach to the process");
// Reads 4 bytes on a 32-bit machine,
// and 8 bytes on a 64-bit machine.
let word: isize = proc.read_word(0xdeadbeef)
.expect("Failed to read word from process");
// Readers contain a mutable reference to the
// process, so they must be dropped after using.
let data = {
let mut data = Vec::new();
let mut buf = [0u8; 8];
let mut reader = proc.reader(0xbadf00d, 8);
while buf[0] == 0 {
// `buf` is the same size as the reader,
// so we don't have to worry about how
// much data was read.
//
// If `buf` had been 7 bytes, `reader` would
// have read one word (two on 32-bit), and
// discarded the last byte.
reader.read_exact(&mut buf)
.expect("Failed to read bytes from process");
data.extend_from_slice(&buf);
}
// `reader` gets dropped and we can use `proc` again.
};
}
To write to a process' memory, it's the exact same (substituting the proper methods, of course). There's a ProcessWriter
struct that implements Write
and has the same semantics as the reader. However, when you drop a ProcessWriter
, it tries to flush the data you've written. This will cause a nasty panic if it fails! Always flush
before dropping a writer.
Following pointer chains
If you don't know what pointer chains are, google multi-level pointers cheat engine
.
There's also a utility for following pointer chains, via Process::pointer_chain
. This follows traditional semantics (deref the address, add an offset, repeat).
Licensing and contribution
This is licensed under the very liberal MIT license. I would be only too glad if somebody took this project and expanded on it, even made money off of it.
As for contribution, any and all are welcome (but not necessarily accepted). Contributions are licensed under the MIT license unless otherwise specified. Contributions not under the MIT will probably be rejected (sorry, but multiple licenses for one project is too many).
Dependencies
~1.5MB
~35K SLoC