Linux下如何从/proc/$pid/mem读取?

Gil*_*il' 155 linux process kernel memory proc

Linux的proc(5)手册页告诉我,/proc/$pid/mem“可用于访问进程的内存的页面”。但是直接尝试使用它只会给我

$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error
Run Code Online (Sandbox Code Playgroud)

为什么不能cat打印自己的内存(/proc/self/mem)?当我尝试打印 shell 的内存时,这个奇怪的“没有这样的进程”错误是什么(/proc/$$/mem显然进程存在)?那我怎么读/proc/$pid/mem呢?

Gil*_*il' 164

/proc/$pid/maps

/proc/$pid/mem显示$pid 内存的内容与进程中的映射方式相同,即伪文件中偏移x处的字节与进程中地址x处的字节相同。如果在进程中未映射地址,则从文件中的相应偏移量读取返回EIO(输入/输出错误)。例如,由于进程中的第一页永远不会被映射(因此取消引用NULL指针会完全失败,而不是无意中访问实际内存),因此读取 的第一个字节/proc/$pid/mem总是会产生 I/O 错误。

找出进程内存的哪些部分被映射的方法是读取/proc/$pid/maps. 该文件每个映射区域包含一行,如下所示:

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0          [heap]
Run Code Online (Sandbox Code Playgroud)

前两个数字是区域的边界(第一个字节和最后一个字节的地址,以十六进制表示)。下一列包含权限,如果这是文件映射,则有一些有关文件的信息(偏移量、设备、inode 和名称)。有关详细信息,请参阅proc(5)手册页或了解 Linux /proc/id/maps

这是一个转储其自身内存内容的概念验证脚本。

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'rb', 0)
output_file = open("self.dump", 'wb')
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        output_file.write(chunk)  # dump contents to standard output
maps_file.close()
mem_file.close()
output_file.close()
Run Code Online (Sandbox Code Playgroud)

/proc/$pid/mem

[以下是出于历史兴趣。它不适用于当前的内核。]

内核的 3.3 版开始/proc/$pid/mem只要您仅访问它在映射的偏移量处访问它并且您有权跟踪它(与ptrace只读访问相同的权限),您就可以正常访问。但是在较旧的内核中,还有一些额外的并发症。

如果您尝试从mem另一个进程的伪文件中读取,则它不起作用:您会收到ESRCH(没有这样的进程)错误。

/proc/$pid/mem( r--------)上的权限比应有的情况更宽松。例如,您不应该能够读取 setuid 进程的内存。此外,在进程修改内存时尝试读取进程的内存可能会给读者一个不一致的内存视图,更糟糕的是,存在可以跟踪旧版本 Linux 内核的竞争条件(根据这个 lkml 线程,尽管我不知道具体情况)。所以需要额外的检查:

  • 想要从中读取数据的过程中/proc/$pid/mem必须要重视使用过程中ptrace具有PTRACE_ATTACH标志。这就是调试器在开始调试进程时所做的;这也是对strace进程的系统调用的作用。一旦读者读完的/proc/$pid/mem,它应该通过调用分离ptracePTRACE_DETACH标志。
  • 被观察的进程不得正在运行。通常调用ptrace(PTRACE_ATTACH, …)会停止目标进程(它发送一个STOP信号),但是存在竞争条件(信号传递是异步的),所以跟踪器应该调用wait(如 中所述ptrace(2))。

以 root 身份运行的进程可以读取任何进程的内存,而无需调用ptrace,但必须停止被观察的进程,否则读取仍将返回ESRCH

在 Linux 内核源代码中,提供每个进程条目的代码/proc是 in fs/proc/base.c,读取的函数/proc/$pid/memmem_read。附加检查由 执行check_mem_permission

下面是一些示例 C 代码,用于附加到进程并读取其mem文件块(省略错误检查):

sprintf(mem_file_name, "/proc/%d/mem", pid);
mem_fd = open(mem_file_name, O_RDONLY);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);
lseek(mem_fd, offset, SEEK_SET);
read(mem_fd, buf, _SC_PAGE_SIZE);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
Run Code Online (Sandbox Code Playgroud)

我已经发布了一个用于/proc/$pid/mem在另一个线程上转储的概念验证脚本

  • @abc 他正在阅读 `/proc/self/mem`。一个进程可以读取自己的内存空间就好了,它正在读取另一个需要`PTRACE_ATTACH`的进程的内存空间。 (4认同)
  • @abc 不,直接从 `/proc/$pid/mem` 读取(无论是使用 `cat` 还是 `dd` 或其他任何东西)都不起作用。阅读我的回答。 (2认同)
  • 请注意,使用最新的 Linux 内核,您不需要 PTRACE_ATTACH。这一变化伴随着“process_vm_readv()”系统调用(Linux 3.2)。 (2认同)
  • 嗯,对于 Linux 4.14.8,这对我有用:启动一个长时间运行的进程,该进程忙于将输出写入 /dev/null。然后另一个进程能够从 /proc/$otherpid/mem 中打开、查找和读取一些字节(即在通过辅助向量引用的某些偏移量处) - 无需 ptrace-attach/detach 或停止/启动进程。如果进程在同一用户下运行且为 root 用户运行,则有效。即在这种情况下我不能产生`ESRCH` 错误。 (2认同)

Tob*_*obu 28

此命令(来自 gdb)可靠地转储内存:

gcore pid
Run Code Online (Sandbox Code Playgroud)

转储可能很大,-o outfile如果您当前的目录没有足够的空间,请使用。


bah*_*mat 12

当您执行cat /proc/$$/mem该变量时$$,由插入其自己的 pid 的 bash 评估该变量。然后执行cat具有不同 pid 的程序。您最终cat尝试读取bash其父进程的内存。由于非特权进程只能读取自己的内存空间,因此内核拒绝了这一点。

下面是一个例子:

$ echo $$
17823
Run Code Online (Sandbox Code Playgroud)

请注意,$$计算结果为 17823。让我们看看是哪个进程。

$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat  17823 17822  0 13:51 pts/0    00:00:00 -bash
Run Code Online (Sandbox Code Playgroud)

这是我目前的外壳。

$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process
Run Code Online (Sandbox Code Playgroud)

这里再次$$计算为 17823,这是我的外壳。cat无法读取我的 shell 的内存空间。


Tal*_*oni 8

这是我用C写的一个小程序:

用法:

memdump <pid>
memdump <pid> <ip-address> <port>
Run Code Online (Sandbox Code Playgroud)

该程序使用 /proc/$pid/maps 查找进程的所有映射内存区域,然后从 /proc/$pid/mem 中读取这些区域,一次一页。这些页面被写入标准输出或您指定的 IP 地址和 TCP 端口。

代码(在Android上测试,需要超级用户权限):

memdump <pid>
memdump <pid> <ip-address> <port>
Run Code Online (Sandbox Code Playgroud)

  • 添加一些对您的代码的解释。你唯一的评论有点毫无意义:在 `fwrite(..., stdout)` 正上方的 `write to stdout`。见 http://programmers.stackexchange.com/questions/119600/beginners-guide-to-writing-comments (5认同)

sha*_*qed 7

使用 bash 执行读取也可以通过dd(1)

如果您使用的是有限且基本的 Unix 系统,该系统没有上面提到的一些命令(从python一路到memdump类似的东西)

您可以使用dd(1),它应该在最有限的 Unix 环境中可用。

从进程中转储前几个字节的示例:

$ dd if=/proc/1337/mem of=/tmp/dump bs=1 skip=$((0x400000)) count=128
Run Code Online (Sandbox Code Playgroud)

然后你可以用

hexdump -Cv ./tmp/dump
Run Code Online (Sandbox Code Playgroud)