Braque Bottle and Fishes c.1910–2 (Tate). /proc just like analytical cubism displays a process through many angles.

Journey into the machine, /proc

Anne
7 min readFeb 22, 2018

--

(Note all examples below are for ubuntu14.04 LTS in a vagrant box. gdb version 7.7 is used, gcc version 4.8 and python 3.4. I am using scripts stored on github)

/proc presentation

The proc file system is a virtual file system. It’s sometimes referred to as a process information pseudo-file system. It doesn’t contain ‘real’ files but runtime system information (e.g. system memory, devices mounted, hardware configuration, etc). It acts as an interface with the kernel. Looking into the subdirectories and files, one can not only read the information, but also change certain kernel parameters at runtime (and more, see below).

Here I will focus on reading process specific information. I will use a simple script (infinite_loop running continuously in another terminal than the one I am using to type commands) to give examples.

/proc contains one subfolder for each process currently running. This folder is named after the process pid. Below I give an exert of what it looks like for my toy script.

vagrant@vagrant-ubuntu-trusty-64:~$ ps aux | grep infinite
vagrant 23984 0.0 0.0 4324 356 pts/0 S+ 03:13 0:00 ./infinite_loop
vagrant 23986 0.0 0.1 10460 936 pts/3 S+ 03:13 0:00 grep --color=auto infinite
vagrant@vagrant-ubuntu-trusty-64:~$ ls -l /proc/23984
total 0
dr-xr-xr-x 2 vagrant vagrant 0 Feb 22 03:22 attr
-rw-r--r-- 1 vagrant vagrant 0 Feb 22 03:22 autogroup
-r-------- 1 vagrant vagrant 0 Feb 22 03:22 auxv
-r--r--r-- 1 vagrant vagrant 0 Feb 22 03:22 cgroup
--w------- 1 vagrant vagrant 0 Feb 22 03:22 clear_refs
-r--r--r-- 1 vagrant vagrant 0 Feb 22 03:13 cmdline
-rw-r--r-- 1 vagrant vagrant 0 Feb 22 03:22 comm
-rw-r--r-- 1 vagrant vagrant 0 Feb 22 03:22 coredump_filter
dr-x------ 2 vagrant vagrant 0 Feb 22 03:22 map_files
-r--r--r-- 1 vagrant vagrant 0 Feb 22 03:13 maps
-rw------- 1 vagrant vagrant 0 Feb 22 03:22 mem
-r--r--r-- 1 vagrant vagrant 0 Feb 22 03:22 mountinfo
-r--r--r-- 1 vagrant vagrant 0 Feb 22 03:22 mounts

All files and directories show a size of 0, but as I will read them, it will be obvious they are not empty.

I wanted in a script to compare the number of pids in ls /proc, and ps aux. The result, as expected is in both cases I get the same number of pids, with one pid present in one output and not the other: the pid of ls and ps processes respectively.

./compare_pids.py 
Number of pids for ls /proc: 98 and ps: 98
pids differ for 26812 and 26813

I will focus on /maps and /mem

/proc/[pid]/maps

It displays the currently mapped memory regions and their access permissions.

For my example:

vagrant@vagrant-ubuntu-trusty-64:~$ ps aux | grep infinite
vagrant 23984 0.0 0.0 4324 356 pts/0 S+ 03:13 0:00 ./infinite_loop
vagrant 23986 0.0 0.1 10460 936 pts/3 S+ 03:13 0:00 grep --color=auto infinite
vagrant@vagrant-ubuntu-trusty-64:~$ cat /proc/23984/maps
00400000-00401000 r-xp 00000000 08:01 395098 infinite_loop
00600000-00601000 r--p 00000000 08:01 395098 infinite_loop
00601000-00602000 rw-p 00001000 08:01 395098 infinite_loop
00708000-00729000 rw-p 00000000 00:00 0 [heap]
7f3fb0b7f000-7f3fb0d39000 r-xp 00000000 08:01 2006 /lib/x86_64-linux-gnu/libc-2.19.so
7f3fb0d39000-7f3fb0f39000 ---p 001ba000 08:01 2006 /lib/x86_64-linux-gnu/libc-2.19.so
7f3fb0f39000-7f3fb0f3d000 r--p 001ba000 08:01 2006 /lib/x86_64-linux-gnu/libc-2.19.so
7f3fb0f3d000-7f3fb0f3f000 rw-p 001be000 08:01 2006 /lib/x86_64-linux-gnu/libc-2.19.so
7f3fb0f3f000-7f3fb0f44000 rw-p 00000000 00:00 0
7f3fb0f44000-7f3fb0f67000 r-xp 00000000 08:01 2019 /lib/x86_64-linux-gnu/ld-2.19.so
7f3fb1154000-7f3fb1157000 rw-p 00000000 00:00 0
7f3fb1163000-7f3fb1166000 rw-p 00000000 00:00 0
7f3fb1166000-7f3fb1167000 r--p 00022000 08:01 2019 /lib/x86_64-linux-gnu/ld-2.19.so
7f3fb1167000-7f3fb1168000 rw-p 00023000 08:01 2019 /lib/x86_64-linux-gnu/ld-2.19.so
7f3fb1168000-7f3fb1169000 rw-p 00000000 00:00 0
7fffa32f7000-7fffa3318000 rw-p 00000000 00:00 0 [stack]
7fffa33c9000-7fffa33cb000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

The man page gives a great detailed explanation of the different fields (man proc). Here is how it defines the different fields from left to right:

The address field is the address space in the process  that  the  mapping occupies. 
The perms field is a set of permissions:
r = read
w = write
x = execute
s = shared
p = private (copy on write)
The offset field is the offset into the file/whatever;
dev is the device (major:minor);
inode is the inode on that device. 0 indicates that no inode is associated with the memory region, as would be the case with BSS(uninitialized data).
The pathname field will usually be the file that is backing the mapping.
There are additional helpful pseudo-paths:
[stack] The initial process's (also known as the main thread's) stack.
[vdso] The virtual dynamically linked shared object.
[heap] The process's heap.

It looks like all the different address fields are continuous. This is because we observe the virtual memory layout of the process. For each process the kernel creates a mapping between the physical memory and the hardware.

Reading the above file, I was surprised my process (infinite_loop) is listed 3 times, same for the libc-2.19.so. Processes can be divided into several contiguous parts, with different permissions for each address segment. I need to look more into it to find a good explanation. This reference explains about how ELF files (standard binary files format on my machine) are divided in at least 3 regions:

•Code which is executed; this region is normally not writable;

•Data which is modified; this region is normally not executable;

•Data which is not used at run-time; since not needed it should not be loaded at startup.

Modern operating systems and processors can protect memory regions to allow and disallow reading, writing, and executing separately for each page of memory.

This maps file is used by gdb, the gdb commandinfo proc mappings displays it. Let’s try:

vagrant@vagrant-ubuntu-trusty-64:$ gdb attach -p 23984
...
Could not attach to process. If your uid matches the uid of the target process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
(gdb)

The error message above is pretty interesting with regards to our topic: it asks to modify a kernel parameter at runtime (/proc/sys) in order to complete the action (more details: https://www.kernel.org/doc/Documentation/security/Yama.txt)

vagrant@vagrant-ubuntu-trusty-64:~$ sudo gdb attach -p 23984
...
(gdb) info proc mappings
process 23984
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/vagrant/infinite_loop
0x600000 0x601000 0x1000 0x0 /home/vagrant/infinite_loop
0x601000 0x602000 0x1000 0x1000 /home/vagrant/infinite_loop
0x708000 0x729000 0x21000 0x0 [heap]
0x7f3fb0b7f000 0x7f3fb0d39000 0x1ba000 0x0 /lib/x86_64-linux-gnu/libc-2.19.so
0x7f3fb0d39000 0x7f3fb0f39000 0x200000 0x1ba000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7f3fb0f39000 0x7f3fb0f3d000 0x4000 0x1ba000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7f3fb0f3d000 0x7f3fb0f3f000 0x2000 0x1be000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7f3fb0f3f000 0x7f3fb0f44000 0x5000 0x0
0x7f3fb0f44000 0x7f3fb0f67000 0x23000 0x0 /lib/x86_64-linux-gnu/ld-2.19.so
0x7f3fb1154000 0x7f3fb1157000 0x3000 0x0
0x7f3fb1163000 0x7f3fb1166000 0x3000 0x0
0x7f3fb1166000 0x7f3fb1167000 0x1000 0x22000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7f3fb1167000 0x7f3fb1168000 0x1000 0x23000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7f3fb1168000 0x7f3fb1169000 0x1000 0x0
0x7fffa32f7000 0x7fffa3318000 0x21000 0x0 [stack]
0x7fffa33c9000 0x7fffa33cb000 0x2000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
(gdb)

Now there are the mappings, where can we access the memory?

/proc/[pid]/mem

The proc man page explains: This file can be used to access the pages of a process’s memory.

So let’s do it. Reading above, the heap is both readable and writable. Is it possible to open the mem file, locate the heap, inside the heap locate the string the infinite_loop is continuously writing and overwriting it?

That is what this python script is doing. It requires to be run as root to open the /mem file of another process. It will look for the infinite_loop process, then read its maps file, find the heap, open the mem file, and replace “World” with “Good Night”:

vagrant@vagrant-ubuntu-trusty-64:~$sudo ./read_write_heap.py 
[*] Maps: /proc/23984/Maps
01526000-01547000 rw-p 00000000 00:00 0 [heap]
[*] Found [heap]:
pathname = [heap]
addresses = 01526000-01547000
permissions = rw-p
offset = 00000000
inode = 0
[*] Addr start [01526000] | end [01547000]
[*] Mem: /proc/23984/mem
[*] Found World at 0x16
Overwriting the heap

In the other terminal

$ ./infinite_loop
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello Good Night
Hello Good Night

It is also possible to do it in gdb.

I set a breakpoint where printf resides (in order to find out the address I used objdump) In the previous gdb session:

(gdb) break *0x4005dd
Breakpoint 1 at 0x4005dd: file infinite_loop.c, line 13.
(gdb) p s2
$1 = 0x20ad010 "Hello World"
(gdb) p strcpy($1, "Good Night")
$2 = 34263056
(gdb) p s2
$3 = 0x20ad010 "Good Night"
(gdb) clear
(gdb) c

In the other terminal:

$ ./infinite_loop
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Good Night
Good Night
Good Night
Good Night
Good Night

References:

The Linux Programming Interface

For proc:

(https://www.tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html)

(http://www.tldp.org/LDP/lkmpg/2.6/html/x710.html)

https://www.kernel.org/doc/Documentation/filesystems/proc.txt

For maps and mem:

--

--