it-swarm.it

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

La Linux proc(5) man page mi dice che /proc/$pid/mem "Può essere utilizzato 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é cat non è in grado 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 da /proc/$pid/mem, poi?

/proc/$pid/maps

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

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 proc(5) man page 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 pseudo-file mem di un altro processo, non funziona: viene visualizzato un errore ESRCH (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 lkml thread , anche se non conosco i dettagli). Quindi sono necessari ulteriori controlli:

  • Il processo che vuole leggere da /proc/$pid/mem Deve essere collegato al processo usando ptrace con il flag PTRACE_ATTACH. Questo è ciò che fanno i debugger quando iniziano a eseguire il debug di un processo; è anche ciò che strace fa alle chiamate di sistema di un processo. Una volta che il lettore ha finito di leggere da /proc/$pid/mem, Dovrebbe staccarsi chiamando ptrace con il flag PTRACE_DETACH.
  • Il processo osservato non deve essere in esecuzione. Normalmente chiamando ptrace(PTRACE_ATTACH, …) interromperà il processo target (invia un segnale STOP), ma c'è 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 interrotto o la lettura restituirà comunque ESRCH.

Nel sorgente del kernel Linux, il codice che fornisce voci per processo in /proc È in fs/proc/base.c e la funzione da leggere da /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 relativo al file mem (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/mem Su un altro thread .

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

gcore pid

I dump possono essere di grandi dimensioni, utilizzare -o outfile se la tua directory corrente non ha abbastanza spazio.

28
Tobu

Quando esegui cat /proc/$$/mem la variabile $$ viene valutato da bash che inserisce il proprio pid. Quindi esegue cat che ha un pid diverso. Si finisce con cat che prova a leggere la memoria di bash, il suo 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

Nota 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 attuale Shell.

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

Ancora qui $$ restituisce 17823, che è la mia Shell. cat non riesce a leggere lo spazio di memoria della mia Shell.

12
bahamat

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 sono scritte su stdout o sull'indirizzo IP e TCP specificata.

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);
    }
}
8
Tal Aloni