Wie lese ich unter Linux aus / proc / $ pid / mem?

142

Die Linux- proc(5)Manpage sagt mir, dass /proc/$pid/mem"verwendet werden kann, um auf die Seiten des Speichers eines Prozesses zuzugreifen". Aber ein einfacher Versuch, es zu benutzen, gibt mir nur

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

Warum kann catder eigene Speicher nicht gedruckt werden ( /proc/self/mem)? Und was ist dieser seltsame Fehler "no such process", wenn ich versuche, den Speicher der Shell zu drucken ( /proc/$$/memoffensichtlich ist der Prozess vorhanden)? Wie kann ich dann lesen /proc/$pid/mem?

Gilles
quelle
1
Es gibt mehrere andere Methoden, die zeigen, wie dies auf SF in diesem Q & A mit dem Titel gemacht wird: Den Speicher eines Linux-Prozesses in die Datei
ausgeben
up to date Antwort
pizdelect

Antworten:

140

/proc/$pid/maps

/proc/$pid/memzeigt den Inhalt des Speichers von $ pid, der auf die gleiche Weise wie im Prozess abgebildet wurde, dh das Byte am Offset x in der Pseudodatei ist das gleiche wie das Byte an der Adresse x im Prozess. Wenn eine Adresse dabei nicht zugeordnet wird, wird das Lesen des entsprechenden Offsets in der Datei zurückgegeben EIO(Eingabe- / Ausgabefehler). Da beispielsweise die erste Seite in einem Prozess niemals zugeordnet wird (so dass die Dereferenzierung eines NULLZeigers /proc/$pid/memfehlerfrei fehlschlägt, anstatt unbeabsichtigt auf den tatsächlichen Speicher zuzugreifen), führt das Lesen des ersten Bytes von immer zu einem E / A-Fehler.

Der Weg, um herauszufinden, welche Teile des Prozessspeichers abgebildet sind, ist zu lesen /proc/$pid/maps. Diese Datei enthält eine Zeile pro zugeordnetem Bereich und sieht folgendermaßen 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 Bytes nach dem letzten in Hexa). Die nächste Spalte enthält die Berechtigungen. Wenn es sich um eine Dateizuordnung handelt, werden einige Informationen zur Datei (Offset, Gerät, Inode und Name) angezeigt. Weitere Informationen finden Sie in der proc(5)Manpage oder unter Linux / proc / id / maps .

Hier ist ein Proof-of-Concept-Skript, das den Inhalt seines eigenen Speichers speichert.

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'r', 0)
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
        print chunk,  # dump contents to standard output
maps_file.close()
mem_file.close()

/proc/$pid/mem

Wenn Sie versuchen, aus der memPseudodatei eines anderen Prozesses zu lesen , funktioniert dies nicht: ESRCHEs wird ein Fehler (Kein solcher Prozess) angezeigt.

Die Berechtigungen für /proc/$pid/mem( r--------) sind liberaler als dies der Fall sein sollte. Beispielsweise sollte es nicht möglich sein, den Speicher eines Setuid-Prozesses zu lesen. Der Versuch, den Arbeitsspeicher eines Prozesses zu lesen, während dieser geändert wird , kann dem Leser eine inkonsistente Sicht auf den Arbeitsspeicher geben. Schlimmer noch, es gab Race-Bedingungen, die ältere Versionen des Linux-Kernels nachvollziehen können (laut diesem lkml-Thread , obwohl ich Ich kenne die Details nicht. Daher sind zusätzliche Überprüfungen erforderlich:

  • Der Prozess, aus dem gelesen werden soll, /proc/$pid/memmuss ptracemit dem PTRACE_ATTACHFlag an den Prozess angehängt werden. Dies tun Debugger, wenn sie mit dem Debuggen eines Prozesses beginnen. Es ist auch das, was stracemit den Systemaufrufen eines Prozesses geschieht. Sobald der Leser das Lesen beendet hat /proc/$pid/mem, sollte er sich durch Aufrufen ptracemit der PTRACE_DETACHFlagge lösen .
  • Der beobachtete Prozess darf nicht laufen. Normalerweise ptrace(PTRACE_ATTACH, …)stoppt der Aufruf den Zielprozess (es wird ein STOPSignal gesendet), aber es liegt eine Race-Bedingung vor (die Signalübermittlung ist asynchron), sodass der Tracer einen Aufruf durchführen sollte wait(wie in dokumentiert ptrace(2)).

Ein Prozess, der als root ausgeführt wird, kann den Speicher eines beliebigen Prozesses lesen, ohne dass ein Aufruf erforderlich ptraceist. Der beobachtete Prozess muss jedoch gestoppt werden, oder der Lesevorgang wird weiterhin ausgeführt ESRCH.

In den Linux - Kernel - Quellen und bietet der Code pro Prozess Einträge in /procin ist fs/proc/base.c, und die Funktion zum Lesen von /proc/$pid/memist mem_read. Die zusätzliche Prüfung erfolgt durch check_mem_permission.

Hier ist ein Beispiel-C-Code, der an einen Prozess angehängt und einen Teil der memDatei gelesen werden kann (Fehlerprüfung weggelassen):

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 zum Speichern /proc/$pid/memin einem anderen Thread veröffentlicht .

Gilles
quelle
2
@abc Nein, aus der Lektüre /proc/$pid/memdirekt (ob mit catoder ddoder irgendetwas anderes) nicht funktioniert. Lies meine Antwort.
Gilles
4
Er liest aus /proc/self/mem. Ein Prozess kann seinen eigenen Speicherbereich ganz gut lesen. Er liest den Speicherbereich eines anderen Prozesses, der benötigt wird PTRACE_ATTACH.
Gilles
2
Beachten Sie, dass Sie bei neueren Linux-Kerneln PTRACE_ATTACH nicht benötigen. Diese Änderung ist mit dem process_vm_readv()Systemaufruf (Linux 3.2) verbunden.
ysdx
2
Hm, mit Linux 4.14.8 funktioniert das für mich: Starten Sie einen lang laufenden Prozess, der damit beschäftigt ist, Ausgaben nach / dev / null zu schreiben. Dann ist ein anderer Prozess in der Lage, einige Bytes von / proc / $ otherpid / mem zu öffnen, zu suchen und zu lesen (dh bei einigen Offsets, auf die über den Hilfsvektor verwiesen wird) - ohne den Prozess anhängen / trennen oder anhalten / starten zu müssen. Funktioniert, wenn der Prozess unter demselben Benutzer und für den Root-Benutzer ausgeführt wird. Dh ich kann ESRCHin diesem Szenario keinen Fehler liefern .
Maxschlepzig
1
@maxschlepzig Ich denke, das ist die Änderung, die von ysdx im obigen Kommentar erwähnt wurde.
Gilles
28

Dieser Befehl (von gdb) sichert den Speicher zuverlässig:

gcore pid

Speicherauszüge können groß sein. Verwenden -o outfileSie diese Option, wenn in Ihrem aktuellen Verzeichnis nicht genügend Speicherplatz vorhanden ist.

Tobu
quelle
12

Bei der Ausführung wird cat /proc/$$/memdie Variable $$von bash ausgewertet, wobei eine eigene pid eingefügt wird. Es wird dann ausgeführt, catwelches eine andere PID hat. Am Ende müssen Sie catversuchen, den Speicher des bashübergeordneten Prozesses zu lesen . Da nicht privilegierte Prozesse nur ihren eigenen Speicherbereich lesen können, wird dies vom Kernel abgelehnt.

Hier ist ein Beispiel:

$ echo $$
17823

Beachten Sie, dass $$17823 ausgewertet wird. 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

Hier nochmal $$17823 auswertet, das ist meine Shell. catIch kann den Speicherplatz meiner Shell nicht lesen.

Bahamat
quelle
Am Ende versuchen Sie, die Erinnerung an das zu lesen, was $pidist. Wie ich in meiner Antwort erläutere, müssen Sie die Erinnerung an einen anderen Prozess nachvollziehen, um ihn lesen zu können.
Gilles
Welches wird bash sein. Ich habe nicht gesagt, dass deine Antwort falsch ist. Ich antwortete nur mit laienhafteren Worten: "Warum funktioniert das nicht?".
Bahamat
@bahamat: Denkst du darüber nach, $$wann du schreibst (und liest) $pid?
Gilles
Ja ... er fing an zu fragen $$und $pidam Ende zu setzen. Ich habe es in meinem Kopf transponiert, ohne es zu merken. Meine gesamte Antwort sollte sich beziehen $$, nicht $pid.
Bahamat
@ Bahamat: Ist die Frage jetzt klarer? (Übrigens sehe ich deine Kommentare nur, wenn du "@Gilles" verwendest. Ich habe gerade deine Bearbeitung gesehen und bin gekommen, um sie zu sehen.)
Gilles
7

Hier ist ein kleines Programm, das ich in C geschrieben habe:

Verwendungszweck:

memdump <pid>
memdump <pid> <ip-address> <port>

Das Programm verwendet / proc / $ pid / maps, um alle zugeordneten Speicherbereiche des Prozesses zu finden, und liest diese Bereiche dann seitenweise aus / proc / $ pid / mem. Diese Seiten werden auf stdout oder die von Ihnen angegebene IP-Adresse und den angegebenen TCP-Port geschrieben.

Code (auf Android getestet, Superuser-Berechtigungen erforderlich):

#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 specified\n");
            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 specified\n");
                return;
            }
            serverSocket = socket(AF_INET, SOCK_STREAM, 0);
            if (serverSocket == -1)
            {
                printf("Could not create socket\n");
                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 server\n");
                return;
            }
        }
        char line[256];
        while (fgets(line, 256, pMapsFile) != NULL)
        {
            unsigned long start_address;
            unsigned long end_address;
            sscanf(line, "%08lx-%08lx\n", &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);
    }
}
Tal Aloni
quelle
5
Fügen Sie eine Erklärung Ihres Codes hinzu. Ihr einziger Kommentar ist irgendwie sinnlos: write to stdoutdirekt darüber fwrite(..., stdout). Siehe programmers.stackexchange.com/questions/119600/…
muru
Sie sagten, Sie haben es nur auf Android getestet, und ich wollte nur bestätigen, dass es unter Linux 4.4.0-28 x86_64 wie erwartet funktioniert
Apricot Boy,
Ich bekomme eine Menge Daten wie / @ 8 l / @ l auf stdout, die keine Ahnung haben warum? Kompiliert unter Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU / Linux Thread-Modell: posix gcc Version 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us
ceph3us, die gebräuchliche Verwendung ist es, die Daten in eine Datei umzuleiten (zB memdump <pid>> /sdcard/memdump.bin)
Tal Aloni