How do I read from /proc/$pid/mem under Linux?
The Linux proc(5)
man page tells me that /proc/$pid/mem
“can be used to access the pages of a process’s memory”. But a straightforward attempt to use it only gives me
$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error
Why isn’t cat
able to print its own memory (/proc/self/mem
)? And what is this strange “no such process” error when I try to print the shell’s memory (/proc/$$/mem
, obviously the process exists)? How can I read from /proc/$pid/mem
, then?
/proc/$pid/maps
/proc/$pid/mem
shows the contents of $pid’s memory mapped the same way as in the process, i.e., the byte at offset x in the pseudo-file is the same as the byte at address x in the process. If an address is unmapped in the process, reading from the corresponding offset in the file returns EIO
(Input/output error). For example, since the first page in a process is never mapped (so that dereferencing a NULL
pointer fails cleanly rather than unintendedly accessing actual memory), reading the first byte of /proc/$pid/mem
always yield an I/O error.
The way to find out what parts of the process memory are mapped is to read /proc/$pid/maps
. This file contains one line per mapped region, looking like this:
08048000-08054000 r-xp 00000000 08:01 828061 /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap]
The first two numbers are the boundaries of the region (addresses of the first byte and the byte after last, in hexa). The next column contain the permissions, then there’s some information about the file (offset, device, inode and name) if this is a file mapping. See the proc(5)
man page or Understanding Linux /proc/id/maps for more information.
Here’s a proof-of-concept script that dumps the contents of its own memory.
#! /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
[The following is for historical interest. It does not apply to current kernels.]
Since version 3.3 of the kernel, you can access /proc/$pid/mem
normally as long as you access only access it at mapped offsets and you have permission to trace it (same permissions as ptrace
for read-only access). But in older kernels, there were some additional complications.
If you try to read from the mem
pseudo-file of another process, it doesn’t work: you get an ESRCH
(No such process) error.
The permissions on /proc/$pid/mem
(r--------
) are more liberal than what should be the case. For example, you shouldn’t be able to read a setuid process’s memory. Furthermore, trying to read a process’s memory while the process is modifying it could give the reader an inconsistent view of the memory, and worse, there were race conditions that could trace older versions of the Linux kernel (according to this lkml thread, though I don’t know the details). So additional checks are needed:
- The process that wants to read from
/proc/$pid/mem
must attach to the process usingptrace
with thePTRACE_ATTACH
flag. This is what debuggers do when they start debugging a process; it’s also whatstrace
does to a process’s system calls. Once the reader has finished reading from/proc/$pid/mem
, it should detach by callingptrace
with thePTRACE_DETACH
flag. - The observed process must not be running. Normally calling
ptrace(PTRACE_ATTACH, …)
will stop the target process (it sends aSTOP
signal), but there is a race condition (signal delivery is asynchronous), so the tracer should callwait
(as documented inptrace(2)
).
A process running as root can read any process’s memory, without needing to call ptrace
, but the observed process must be stopped, or the read will still return ESRCH
.
In the Linux kernel source, the code providing per-process entries in /proc
is in fs/proc/base.c
, and the function to read from /proc/$pid/mem
is mem_read
. The additional check is performed by check_mem_permission
.
Here’s some sample C code to attach to a process and read a chunk its of mem
file (error checking omitted):
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);
I’ve already posted a proof-of-concept script for dumping /proc/$pid/mem
on another thread.
When you execute cat /proc/$$/mem
the variable $$
is evaluated by by bash which inserts its own pid. It then executes cat
which has a different pid. You end up with cat
trying to read the memory of bash
, its parent process. Since non-privileged processes can only read their own memory space this gets denied by the kernel.
Here’s an example:
$ echo $$
17823
Note that $$
evaluates to 17823. Let’s see which process that is.
$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat 17823 17822 0 13:51 pts/0 00:00:00 -bash
It’s my current shell.
$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process
Here again $$
evaluates to 17823, which is my shell. cat
can’t read my shell’s memory space.
This command (from gdb) dumps memory reliably:
gcore pid
Dumps can be large, use -o outfile
if your current directory doesn’t have enough room.
Here is a small program I wrote in C:
Usage:
memdump <pid>
memdump <pid> <ip-address> <port>
The program uses /proc/$pid/maps to find all of the mapped memory regions of the process, and then read those regions from /proc/$pid/mem, one page at a time. those pages are written to stdout or the IP address and TCP port you specified.
Code (tested on Android, requires superuser permissions):
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void dump_memory_region(FILE* pMemFile, unsigned long start_address, long length, int serverSocket)
{
unsigned long address;
int pageLength = 4096;
unsigned char page[pageLength];
fseeko(pMemFile, start_address, SEEK_SET);
for (address=start_address; address < start_address + length; address += pageLength)
{
fread(&page, 1, pageLength, pMemFile);
if (serverSocket == -1)
{
// write to stdout
fwrite(&page, 1, pageLength, stdout);
}
else
{
send(serverSocket, &page, pageLength, 0);
}
}
}
int main(int argc, char **argv) {
if (argc == 2 || argc == 4)
{
int pid = atoi(argv[1]);
long ptraceResult = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
if (ptraceResult < 0)
{
printf("Unable to attach to the pid specifiedn");
return;
}
wait(NULL);
char mapsFilename[1024];
sprintf(mapsFilename, "/proc/%s/maps", argv[1]);
FILE* pMapsFile = fopen(mapsFilename, "r");
char memFilename[1024];
sprintf(memFilename, "/proc/%s/mem", argv[1]);
FILE* pMemFile = fopen(memFilename, "r");
int serverSocket = -1;
if (argc == 4)
{
unsigned int port;
int count = sscanf(argv[3], "%d", &port);
if (count == 0)
{
printf("Invalid port specifiedn");
return;
}
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1)
{
printf("Could not create socketn");
return;
}
struct sockaddr_in serverSocketAddress;
serverSocketAddress.sin_addr.s_addr = inet_addr(argv[2]);
serverSocketAddress.sin_family = AF_INET;
serverSocketAddress.sin_port = htons(port);
if (connect(serverSocket, (struct sockaddr *) &serverSocketAddress, sizeof(serverSocketAddress)) < 0)
{
printf("Could not connect to servern");
return;
}
}
char line[256];
while (fgets(line, 256, pMapsFile) != NULL)
{
unsigned long start_address;
unsigned long end_address;
sscanf(line, "%08lx-%08lxn", &start_address, &end_address);
dump_memory_region(pMemFile, start_address, end_address - start_address, serverSocket);
}
fclose(pMapsFile);
fclose(pMemFile);
if (serverSocket != -1)
{
close(serverSocket);
}
ptrace(PTRACE_CONT, pid, NULL, NULL);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
}
else
{
printf("%s <pid>n", argv[0]);
printf("%s <pid> <ip-address> <port>n", argv[0]);
exit(0);
}
}
Perform the read using bash can be done also with dd(1)
If you’re on a limited and basic Unix system which doesn’t have the some of the commands mentioned above (from python
all the way to memdump
and stuff like that)
You can use dd(1)
, which should be available in the most limited Unix environment.
Example for dumping the first few bytes from the process:
$ dd if=/proc/1337/mem of=/tmp/dump bs=1 skip=$((0x400000)) count=128
and then you can read it with
hexdump -Cv ./tmp/dump
you can read the mem file with the xxd
program
in this example i’m going to read the heap
of a program
$ cd /proc/<the-pid>
$ cat maps | grep heap
55fe7eec6000-55fe7eee7000 rw-p 00000000 00:00 0 [heap]
$
$ sudo xxd -s 0x55fe7eec6000 -l $((0x55fe7eee7000 - 0x55fe7eec6000)) mem | less
- flag s: seek offset position 0x05..6000
- flag l: is the length. The last position (0x5..7000) minus the start position (0x5..6000)
- mem: file
- | less: visualize the output with less