这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
必须附加到使用的进程ptrace
与PTRACE_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/mem
是mem_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);
答案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