Come posso leggere da / proc / $ pid / mem su Linux?


142

La pagina man di Linuxproc(5) mi dice che /proc/$pid/mem"può essere usato per accedere alle pagine della memoria di un processo". Ma un semplice tentativo di usarlo mi dà solo

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

Perché non è in catgrado di stampare la propria memoria ( /proc/self/mem)? E qual è questo strano errore "nessun tale processo" quando provo a stampare la memoria della shell ( /proc/$$/mem, ovviamente il processo esiste)? Come posso leggere /proc/$pid/memallora?


1
Ci sono molti altri metodi che mostrano come eseguire questa operazione su SF in questo D&R intitolato: Scaricare la memoria di un processo linux su file
slm

Risposte:


140

/proc/$pid/maps

/proc/$pid/memmostra il contenuto della memoria di $ pid mappato nello stesso modo del processo, cioè il byte all'offset x nello pseudo-file è uguale al byte all'indirizzo x nel processo. Se un indirizzo non è mappato nel processo, viene restituita la lettura dall'offset corrispondente nel file EIO(errore di input / output). Ad esempio, poiché la prima pagina di un processo non viene mai mappata (in modo che il dereferenziamento di un NULLpuntatore non riesca in modo pulito anziché accedere involontariamente alla memoria effettiva), la lettura del primo byte /proc/$pid/memproduce sempre un errore I / O.

Il modo per scoprire quali parti della memoria di processo sono mappate è leggere /proc/$pid/maps. Questo file contiene una riga per area mappata, simile al seguente:

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0          [heap]

I primi due numeri sono i confini della regione (indirizzi del primo byte e del byte dopo l'ultimo, in hexa). La colonna successiva contiene le autorizzazioni, quindi ci sono alcune informazioni sul file (offset, dispositivo, inode e nome) se si tratta di un mapping di file. Vedi la proc(5)pagina man o Comprensione di Linux / proc / id / maps per maggiori informazioni.

Ecco uno script di prova di concetto che scarica il contenuto della propria memoria.

#! /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

Se provi a leggere dallo mempseudo-file di un altro processo, non funziona: viene visualizzato un ESRCHerrore (Nessun processo di questo tipo).

Le autorizzazioni su /proc/$pid/mem( r--------) sono più liberali di quanto dovrebbe essere il caso. Ad esempio, non dovresti essere in grado di leggere la memoria di un processo setuid. Inoltre, provare a leggere la memoria di un processo mentre il processo lo sta modificando potrebbe dare al lettore una visione incoerente della memoria, e peggio ancora, c'erano condizioni di razza che potevano rintracciare versioni precedenti del kernel Linux (secondo questo thread lkml , sebbene io non conosco i dettagli). Quindi sono necessari ulteriori controlli:

  • Il processo da cui si vuole leggere /proc/$pid/memdeve collegarsi al processo usando ptracecon il PTRACE_ATTACHflag. Questo è ciò che fanno i debugger quando iniziano a eseguire il debug di un processo; è anche ciò che stracefa alle chiamate di sistema di un processo. Una volta che il lettore ha finito di leggere /proc/$pid/mem, dovrebbe staccarsi chiamando ptracecon la PTRACE_DETACHbandiera.
  • Il processo osservato non deve essere in esecuzione. Normalmente la chiamata ptrace(PTRACE_ATTACH, …)interrompe il processo di destinazione (invia un STOPsegnale), ma esiste una condizione di competizione (la consegna del segnale è asincrona), quindi il tracciante dovrebbe chiamare wait(come documentato in ptrace(2)).

Un processo in esecuzione come root può leggere la memoria di qualsiasi processo, senza la necessità di chiamare ptrace, ma il processo osservato deve essere arrestato o la lettura tornerà comunque ESRCH.

Nel sorgente del kernel Linux, il codice che fornisce le voci per processo in /procè attivo fs/proc/base.ce la funzione da cui leggere /proc/$pid/memè mem_read. Il controllo aggiuntivo viene eseguito da check_mem_permission.

Ecco un codice C di esempio da allegare a un processo e leggere un blocco del suo memfile (controllo degli errori omesso):

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);

Ho già pubblicato uno script di prova di concetto per il dumping /proc/$pid/memsu un altro thread .


2
@abc No, la lettura /proc/$pid/memdiretta (con cato ddo altro) non funziona. Leggi la mia risposta
Gilles

4
@abc Sta leggendo da /proc/self/mem. Un processo può leggere bene il proprio spazio di memoria, sta leggendo lo spazio di memoria di un altro processo che richiede PTRACE_ATTACH.
Gilles

2
Si noti che con i kernel Linux recenti non è necessario PTRACE_ATTACH. Questa modifica viene fornita con la process_vm_readv()chiamata di sistema (Linux 3.2).
ysdx,

2
Hm, con Linux 4.14.8 questo funziona per me: avvia un lungo processo che è occupato a scrivere l'output su / dev / null. Quindi un altro processo è in grado di aprire, cercare e leggere alcuni byte da / proc / $ otherpid / mem (cioè in alcuni offset a cui fa riferimento il vettore ausiliario) - senza dover ptrace-attach / detach o arrestare / avviare il processo. Funziona se il processo viene eseguito con lo stesso utente e per l'utente root. Vale a dire che non posso dare un ESRCHerrore in questo scenario.
Maxschlepzig,

1
@maxschlepzig Immagino che sia la modifica menzionata da ysdx nel commento sopra.
Gilles,

28

Questo comando (da gdb) scarica la memoria in modo affidabile:

gcore pid

I dump possono essere di grandi dimensioni, utilizzare -o outfilese la directory corrente non dispone di spazio sufficiente.


12

Quando si esegue cat /proc/$$/memla variabile $$viene valutata da bash che inserisce il proprio pid. Quindi esegue catun pid diverso. Si finisce per catprovare a leggere la memoria del bashsuo processo genitore. Poiché i processi non privilegiati possono solo leggere il proprio spazio di memoria, questo viene negato dal kernel.

Ecco un esempio:

$ echo $$
17823

Si noti che $$restituisce 17823. Vediamo quale processo è.

$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat  17823 17822  0 13:51 pts/0    00:00:00 -bash

È la mia shell attuale.

$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process

Qui di nuovo $$restituisce 17823, che è la mia shell. catnon riesco a leggere lo spazio di memoria della mia shell.


Finisci per provare a leggere il ricordo di qualunque cosa $pidsia. Come spiego nella mia risposta, leggere il ricordo di un processo diverso richiede di affrontarlo.
Gilles,

Che sarà bash. Non stavo dicendo che la tua risposta era sbagliata. Stavo solo rispondendo in termini più profani "perché non funziona".
Bahamat,

@bahamat: stai pensando $$quando scrivi (e leggi) $pid?
Gilles,

Sì ... ha iniziato chiedendo di riferirsi $$e $pidalla fine. L'ho trasposto nella mia testa senza accorgermene. Tutta la mia risposta dovrebbe fare riferimento $$, no $pid.
Bahamat,

@bahamat: la domanda ora è più chiara? (A proposito non vedo i tuoi commenti a meno che tu non usi "@Gilles", mi è capitato di vedere la tua modifica e sono venuto a vedere.)
Gilles

7

Ecco un piccolo programma che ho scritto in C:

Uso:

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

Il programma utilizza / proc / $ pid / maps per trovare tutte le aree di memoria mappate del processo, quindi leggere quelle regioni da / proc / $ pid / mem, una pagina alla volta. quelle pagine vengono scritte su stdout o sull'indirizzo IP e sulla porta TCP specificati.

Codice (testato su Android, richiede autorizzazioni per superutente):

#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);
    }
}

5
Aggiungi una spiegazione del tuo codice. Il tuo unico commento è un po 'inutile: write to stdoutimmediatamente sopra fwrite(..., stdout). Vedi programmers.stackexchange.com/questions/119600/…
muru

Hai detto di averlo testato solo su Android, quindi volevo solo confermare, funziona bene su Linux 4.4.0-28 x86_64, come ti aspetteresti
ragazzo albicocca

ricevo un mucchio di dati come / @ 8 l / @ l su stdout che non ha mai idea del perché? compilato su Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU / Linux Modello thread: posix gcc versione 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us

ceph3us, l'uso comune è quello di convogliare i dati in un file (ad es. memdump <pid>> /sdcard/memdump.bin)
Tal Aloni,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.