web-dev-qa-db-fra.com

Comment lire à partir de / proc / $ pid / mem sous Linux?

La page de manuel Linux proc(5) me dit que /proc/$pid/mem “Peut être utilisé pour accéder aux pages de la mémoire d'un processus”. Mais une simple tentative d'utilisation ne me donne

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

Pourquoi cat ne peut-il pas imprimer sa propre mémoire (/proc/self/mem)? Et quelle est cette étrange erreur "aucun processus de ce type" lorsque j'essaie d'imprimer la mémoire du shell (/proc/$$/mem, évidemment le processus existe)? Comment puis-je lire à partir de /proc/$pid/mem, puis?

/proc/$pid/maps

/proc/$pid/mem Affiche le contenu de la mémoire de $ pid mappé de la même manière que dans le processus, c'est-à-dire l'octet à l'offset x dans le pseudo -file est le même que l'octet à l'adresse x dans le processus. Si une adresse n'est pas mappée dans le processus, la lecture de l'offset correspondant dans le fichier renvoie EIO (erreur d'entrée/sortie). Par exemple, étant donné que la première page d'un processus n'est jamais mappée (de sorte que le déréférencement d'un pointeur NULL échoue proprement plutôt que d'accéder involontairement à la mémoire réelle), la lecture du premier octet de /proc/$pid/mem Donne toujours un I/O erreur.

La façon de découvrir quelles parties de la mémoire de processus sont mappées est de lire /proc/$pid/maps. Ce fichier contient une ligne par région mappée, ressemblant à ceci:

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

Les deux premiers nombres sont les limites de la région (adresses du premier octet et de l'octet après dernier, en hexa). La colonne suivante contient les autorisations, puis il y a des informations sur le fichier (décalage, périphérique, inode et nom) s'il s'agit d'un mappage de fichier. Consultez la page de manuel proc(5) ou Comprendre Linux/proc/id/maps pour plus d'informations.

Voici un script de validation de principe qui exporte le contenu de sa propre mémoire.

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

Si vous essayez de lire le pseudo-fichier mem d'un autre processus, cela ne fonctionne pas: vous obtenez une erreur ESRCH (aucun processus de ce type).

Les autorisations sur /proc/$pid/mem (r--------) Sont plus libérales que ce qui devrait être le cas. Par exemple, vous ne devriez pas pouvoir lire la mémoire d'un processus setuid. De plus, essayer de lire la mémoire d'un processus pendant que le processus le modifie pourrait donner au lecteur une vue incohérente de la mémoire, et pire, il y avait des conditions de concurrence qui pouvaient tracer des versions plus anciennes du noyau Linux (selon ce lkml thread , mais je ne connais pas les détails). Des vérifications supplémentaires sont donc nécessaires:

  • Le processus qui souhaite lire à partir de /proc/$pid/mem Doit être associé au processus à l'aide de ptrace avec l'indicateur PTRACE_ATTACH. C'est ce que font les débogueurs lorsqu'ils commencent à déboguer un processus; c'est aussi ce que strace fait aux appels système d'un processus. Une fois que le lecteur a terminé la lecture de /proc/$pid/mem, Il doit se détacher en appelant ptrace avec l'indicateur PTRACE_DETACH.
  • Le processus observé ne doit pas être en cours d'exécution. Normalement, l'appel de ptrace(PTRACE_ATTACH, …) arrêtera le processus cible (il envoie un signal STOP), mais il y a une condition de concurrence (la livraison du signal est asynchrone), donc le traceur doit appeler wait (comme indiqué dans ptrace(2) ).

Un processus exécuté en tant que root peut lire la mémoire de n'importe quel processus, sans avoir besoin d'appeler ptrace, mais le processus observé doit être arrêté, sinon la lecture renverra toujours ESRCH.

Dans la source du noyau Linux, le code fournissant des entrées par processus dans /proc Est dans fs/proc/base.c , et la fonction à lire à partir de /proc/$pid/mem Est - mem_read . La vérification supplémentaire est effectuée par check_mem_permission .

Voici un exemple de code C à attacher à un processus et à lire un morceau de son fichier mem (vérification d'erreur omise):

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

J'ai déjà posté un script de preuve de concept pour vider /proc/$pid/mem Sur un autre fil .

Cette commande (de gdb) vide la mémoire de manière fiable:

gcore pid

Les vidages peuvent être volumineux, utilisez -o outfile si votre répertoire actuel n'a pas assez de place.

28
Tobu

Lorsque vous exécutez cat /proc/$$/mem la variable $$ est évalué par par bash qui insère son propre pid. Il exécute ensuite cat qui a un pid différent. Vous vous retrouvez avec cat essayant de lire la mémoire de bash, son processus parent. Étant donné que les processus non privilégiés ne peuvent lire que leur propre espace mémoire, cela est refusé par le noyau.

Voici un exemple:

$ echo $$
17823

Notez que $$ est évalué à 17823. Voyons de quel processus il s'agit.

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

C'est mon Shell actuel.

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

Encore ici $$ est évalué à 17823, qui est mon Shell. cat ne peut pas lire l'espace mémoire de mon Shell.

12
bahamat

Voici un petit programme que j'ai écrit en C:

Usage:

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

Le programme utilise/proc/$ pid/maps pour trouver toutes les régions de mémoire mappées du processus, puis lit ces régions dans/proc/$ pid/mem, une page à la fois. ces pages sont écrites sur stdout ou l'adresse IP et le port TCP spécifié).

Code (testé sur Android, nécessite des autorisations de superutilisateur):

#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