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

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

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

$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error

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

答案1

/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]

前两个数字是区域的边界(第一个字节和最后一个字节的地址,以十六进制表示)。下一列包含权限,然后是有关文件的一些信息(偏移量、设备、索引节点和名称)(如果这是文件映射)。请参阅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()

/proc/$pid/mem

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

自从内核版本3.3/proc/$pid/mem,只要你访问的时候只在映射的偏移量处访问并且有权限追踪就可以正常访问(与ptrace用于只读访问)。但在较旧的内核中,存在一些额外的复杂情况。

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

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

  • 想要读取的进程/proc/$pid/mem必须附加到使用的进程ptracePTRACE_ATTACH旗帜。这就是调试器在开始调试进程时所做的事情;这也是strace对进程的系统调用执行的操作。一旦读取器完成读取/proc/$pid/mem,它应该通过ptrace使用PTRACE_DETACH标志调用来分离。
  • 观察到的进程不得正在运行。通常调用ptrace(PTRACE_ATTACH, …)将停止目标进程(它发送一个STOP信号),但是存在竞争条件(信号传递是异步的),因此跟踪器应该调用wait(如文档中所述)ptrace(2))。

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

在 Linux 内核源代码中,提供每进程条目的代码/proc位于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);

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

答案2

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

gcore pid

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

答案3

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

这是一个例子:

$ echo $$
17823

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

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

这是我现在的外壳。

$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process

这里再次$$评估为 17823,这是我的 shell。cat无法读取我的 shell 的内存空间。

答案4

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

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

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

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

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

然后你可以用

hexdump -Cv ./tmp/dump

相关内容