/proc/$pid/maps
/proc/$pid/mem
zeigt den Inhalt des Speichers von $pid, der auf die gleiche Weise abgebildet wird wie im Prozess, d.h. das Byte am Offset x in der Pseudodatei ist dasselbe wie das Byte an der Adresse x dabei. Wenn eine Adresse dabei nicht zugeordnet wird, liefert das Lesen aus dem entsprechenden Offset in der Datei EIO
(Ein-/Ausgabefehler). Da beispielsweise die erste Seite in einem Prozess nie gemappt wird (so dass die Dereferenzierung eines NULL
Pointer schlägt sauber fehl, anstatt unbeabsichtigt auf den tatsächlichen Speicher zuzugreifen), und liest das erste Byte von /proc/$pid/mem
ergibt immer einen E/A-Fehler.
Um herauszufinden, welche Teile des Prozessspeichers zugeordnet sind, lesen Sie /proc/$pid/maps
. Diese Datei enthält eine Zeile pro zugeordneter Region und sieht so aus:
08048000-08054000 r-xp 00000000 08:01 828061 /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap]
Die ersten beiden Zahlen sind die Grenzen der Region (Adressen des ersten Bytes und des überletzten Bytes in Hexa). Die nächste Spalte enthält die Berechtigungen, dann gibt es einige Informationen über die Datei (Offset, Gerät, Inode und Name), wenn es sich um eine Dateizuordnung handelt. Siehe proc(5)
Manpage oder Linux verstehen /proc/id/maps für weitere Informationen.
Hier ist ein Proof-of-Concept-Skript, das den Inhalt seines eigenen Speichers ausgibt.
#! /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
[Das Folgende dient dem historischen Interesse. Es gilt nicht für aktuelle Kernel.]
Seit Version 3.3 des Kernels können Sie auf /proc/$pid/mem
zugreifen Normalerweise, solange Sie darauf zugreifen, greifen Sie nur auf gemappte Offsets zu und Sie haben die Berechtigung, es zu verfolgen (gleiche Berechtigungen wie ptrace
für Nur-Lese-Zugriff). Aber in älteren Kerneln gab es einige zusätzliche Komplikationen.
Wenn Sie versuchen, aus dem mem
zu lesen Pseudo-Datei eines anderen Prozesses, es funktioniert nicht:Sie erhalten einen ESRCH
(Kein solcher Prozess) Fehler.
Die Berechtigungen auf /proc/$pid/mem
(r--------
) liberaler sind, als es der Fall sein sollte. Beispielsweise sollten Sie nicht in der Lage sein, den Speicher eines setuid-Prozesses zu lesen. Darüber hinaus könnte der Versuch, den Speicher eines Prozesses zu lesen, während der Prozess ihn ändert, dem Leser eine inkonsistente Ansicht des Speichers geben, und schlimmer noch, es gab Race-Conditions, die ältere Versionen des Linux-Kernels verfolgen könnten (laut diesem lkml-Thread, obwohl ich kenne die Details nicht). Daher sind zusätzliche Überprüfungen erforderlich:
- Der Prozess, der von
/proc/$pid/mem
lesen möchte muss mitptrace
an den Prozess angehängt werden mit demPTRACE_ATTACH
Flagge. Dies ist, was Debugger tun, wenn sie mit dem Debuggen eines Prozesses beginnen; es ist auch wasstrace
macht mit den Systemaufrufen eines Prozesses. Sobald der Leser ab/proc/$pid/mem
fertig gelesen hat , sollte es durch Aufrufen vonptrace
getrennt werden mit demPTRACE_DETACH
Flagge. - Der beobachtete Prozess darf nicht laufen. Ruft normalerweise
ptrace(PTRACE_ATTACH, …)
an stoppt den Zielprozess (es sendet einenSTOP
Signal), aber es gibt eine Wettlaufbedingung (Signalübermittlung ist asynchron), also sollte der Tracerwait
aufrufen (wie inptrace(2)
dokumentiert ).
Ein Prozess, der als Root läuft, kann den Speicher jedes Prozesses lesen, ohne ptrace
aufrufen zu müssen , aber der beobachtete Prozess muss gestoppt werden, oder der Lesevorgang gibt immer noch ESRCH
zurück .
In der Linux-Kernel-Quelle der Code, der prozessbezogene Einträge in /proc
bereitstellt ist in fs/proc/base.c
, und die Funktion zum Lesen von /proc/$pid/mem
ist mem_read
. Die zusätzliche Prüfung erfolgt durch check_mem_permission
.
Hier ist ein Beispiel-C-Code, den Sie an einen Prozess anhängen und einen Teil von mem
lesen können Datei (Fehlerprüfung entfällt):
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);
Ich habe bereits ein Proof-of-Concept-Skript für das Dumping von /proc/$pid/mem
gepostet in einem anderen Thread.
Dieser Befehl (von gdb) sichert den Speicher zuverlässig:
gcore pid
Dumps können groß sein, verwenden Sie -o outfile
wenn Ihr aktuelles Verzeichnis nicht genug Platz hat.
Wenn Sie cat /proc/$$/mem
ausführen die Variable $$
wird von bash ausgewertet, die ihre eigene PID einfügt. Es führt dann cat
aus die eine andere PID hat. Am Ende erhalten Sie cat
versucht, den Speicher von bash
zu lesen , seinen übergeordneten Prozess. Da nicht-privilegierte Prozesse nur ihren eigenen Speicherplatz lesen können, wird dies vom Kernel verweigert.
Hier ist ein Beispiel:
$ echo $$
17823
Beachten Sie, dass $$
ergibt 17823. Mal sehen, welcher Prozess das ist.
$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat 17823 17822 0 13:51 pts/0 00:00:00 -bash
Es ist meine aktuelle Shell.
$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process
Auch hier wieder $$
ergibt 17823, was meine Shell ist. cat
kann den Speicherplatz meiner Shell nicht lesen.