#process-memory #unix #memory-access #attach #reading #utility #plan

memmod

Unix系统上的一个用于连接到进程并访问其内存的工具

7个稳定版本

2.0.0 2023年5月12日
1.4.1 2023年5月9日

#427 in Unix API

MIT许可证

22KB
416 行代码

Memmod

这是一个用于简化连接到、从进程中读取和写入的库。将来,它可能会支持更高级的功能(如注入代码、扫描等)。

目前,它只支持Unix。我有一台Windows开发机器,一旦我对它的稳定性满意并且它准备好投入生产,我将添加Windows支持。

连接到进程

连接到进程有两种方式:使用带有PID的 Process::new,和使用带有名称的 Process::find[_strict]。这两种方法都需要root权限,因为它们会立即连接到进程。

如果您想通过名称查找进程,请使用 Process::find。这将寻找名称中包含所提供名称的进程。这可能很危险:如果它遇到第一个匹配项,它将匹配 cat file.txt | grep <name>。要执行严格的相等性检查,请使用 Process::find_strict提示:如果您想找到进程的确切名称,请尝试获取其PID,然后运行 cat /proc/<pid>/status。第一行以全名结束。

以下是连接到进程的示例

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}");
    }
}

读取/写入进程的内存

读取进程内存有两种方式:使用 Process::read_word[_offset],和使用 ProcessReader

第一种方法从进程读取一个单词(一个isize)。_offset变体首先将进程的基址加到地址上。然而,以这种方式读取数据可能很笨拙且令人讨厌,因此还提供了一个ProcessReader类型,该类型实现了Read并处理单个字节。您可以使用Process::reader[_offset]来创建一个。

默认情况下,每次读取时,读取器将通过内存前进;这可以通过类似builder-pattern的ProcessReader::no_advance方法(这也可以应用于已创建的读取器)来禁用。之后,读取器将“冻结”在其当前地址处,并始终从同一内存片段中读取。

以下是从进程读取的示例

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.
    };
}

要向进程的内存写入,操作完全相同(当然要替换正确的函数)。有一个ProcessWriter结构体,它实现了Write,并且具有与读取器相同的语义。但是,当您销毁一个ProcessWriter时,它尝试刷新您已写入的数据。如果失败,这将导致一个讨厌的panic!始终在销毁写入器之前调用flush

跟随指针链

如果您不知道指针链是什么,请谷歌搜索multi-level pointers cheat engine

还有一个通过Process::pointer_chain来跟随指针链的实用程序。它遵循传统的语义(取消引用地址,添加偏移量,重复)。

许可和贡献

这是在非常自由的MIT许可下授权的。如果有人愿意接受这个项目并在此基础上扩展,甚至从中获利,我将非常高兴。

至于贡献,任何贡献都受欢迎(但并不一定被接受)。除非另有说明,否则贡献在MIT许可下授权。不符合MIT的贡献可能会被拒绝(抱歉,但一个项目使用多个许可太多)。

依赖关系

~1.5MB
~35K SLoC