desenv-web-rp.com

Como leio / proc / $ pid / mem no Linux?

A página de manual Linux proc(5) me diz que /proc/$pid/mem "Pode ser usado para acessar as páginas da memória de um processo". Mas uma tentativa direta de usá-lo só me dá

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

Por que cat não pode imprimir sua própria memória (/proc/self/mem)? E qual é esse erro estranho de “tal processo” quando tento imprimir a memória do Shell (/proc/$$/mem, obviamente, o processo existe)? Como posso ler de /proc/$pid/mem, então?

144

/proc/$pid/maps

/proc/$pid/mem Mostra o conteúdo da memória do $ pid mapeado da mesma maneira que no processo, ou seja, o byte no deslocamento x no pseudo -file é o mesmo que o byte no endereço x no processo. Se um endereço não estiver mapeado no processo, a leitura do deslocamento correspondente no arquivo retornará EIO (erro de entrada/saída). Por exemplo, como a primeira página de um processo nunca é mapeada (para que o cancelamento de referência a um ponteiro NULL falhe de maneira limpa em vez de acessar a memória real sem intenção), a leitura do primeiro byte de /proc/$pid/mem Sempre gera um I/O erro.

A maneira de descobrir quais partes da memória do processo são mapeadas é ler /proc/$pid/maps. Este arquivo contém uma linha por região mapeada, assim:

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

Os dois primeiros números são os limites da região (endereços do primeiro byte e o byte depois do último, em hexa). A próxima coluna contém as permissões, e há algumas informações sobre o arquivo (deslocamento, dispositivo, inode e nome) se este for um mapeamento de arquivo. Consulte a página do manual proc(5) ou Entendendo o Linux/proc/id/maps para obter mais informações.

Aqui está um script de prova de conceito que despeja o conteúdo de sua própria memória.

#! /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 você tentar ler o pseudo-arquivo mem de outro processo, ele não funcionará: você receberá um erro ESRCH (Não existe esse processo).

As permissões em /proc/$pid/mem (r--------) São mais liberais do que deveria ser o caso. Por exemplo, você não deve conseguir ler a memória de um processo setuid. Além disso, tentar ler a memória de um processo enquanto o processo está modificando pode dar ao leitor uma visão inconsistente da memória e, pior ainda, havia condições de corrida que poderiam rastrear versões mais antigas do kernel Linux (de acordo com este lkml thread , embora eu não conheça os detalhes). Portanto, são necessárias verificações adicionais:

  • O processo que deseja ler de /proc/$pid/mem Deve ser anexado ao processo usando ptrace com o sinalizador PTRACE_ATTACH. É isso que os depuradores fazem quando começam a depurar um processo; é também o que strace faz às chamadas do sistema de um processo. Depois que o leitor terminar de ler a partir de /proc/$pid/mem, Ele deve se desconectar chamando ptrace com o sinalizador PTRACE_DETACH.
  • O processo observado não deve estar em execução. Chamar normalmente ptrace(PTRACE_ATTACH, …) interromperá o processo de destino (envia um sinal STOP), mas há uma condição de corrida (a entrega do sinal é assíncrona), portanto o rastreador deve chamar wait (conforme documentado em ptrace(2) ).

Um processo em execução como root pode ler a memória de qualquer processo, sem precisar chamar ptrace, mas o processo observado deve ser interrompido ou a leitura ainda retornará ESRCH.

Na fonte do kernel Linux, o código que fornece entradas por processo em /proc Está em fs/proc/base.c , e a função para ler em /proc/$pid/mem É - mem_read . A verificação adicional é realizada por check_mem_permission .

Aqui está um exemplo de código C para anexar a um processo e ler um pedaço do arquivo mem (verificação de erro omitida):

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

Eu já postei um script de prova de conceito para despejar /proc/$pid/mem Em outro thread .

146

Este comando (do gdb) despeja a memória de forma confiável:

gcore pid

Os despejos podem ser grandes, use -o outfile se o diretório atual não tiver espaço suficiente.

28
Tobu

Quando você executa cat /proc/$$/mem a variável $$ é avaliado pelo bash, que insere seu próprio pid. Em seguida, ele executa cat que possui um pid diferente. Você acaba com cat tentando ler a memória de bash, seu processo pai. Como processos não privilegiados podem apenas ler seu próprio espaço de memória, isso é negado pelo kernel.

Aqui está um exemplo:

$ echo $$
17823

Observe que $$ avalia para 17823. Vamos ver qual processo é esse.

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

É o meu Shell atual.

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

Aqui novamente $$ avalia para 17823, que é meu Shell. cat não consegue ler o espaço de memória do meu Shell.

12
bahamat

Aqui está um pequeno programa que escrevi em C:

Uso:

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

O programa usa/proc/$ pid/maps para encontrar todas as regiões de memória mapeadas do processo e, em seguida, lê essas regiões em/proc/$ pid/mem, uma página de cada vez. essas páginas são gravadas no stdout ou no endereço IP e na porta TCP que você especificou.

Código (testado no Android, requer permissões de superusuário):

#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