Как читать из /proc /$ pid /mem в Linux?

Linux proc(5) man page говорит мне, что /proc/$pid/mem œ можно использовать для доступа к страницам памяти процесса. Но простая попытка использовать его только дает мне

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

Почему код cat не может печатать собственную память (/proc/self/mem)? И какова эта странная ошибка «такой ошибки», когда я пытаюсь распечатать память оболочки (/proc/$$/mem, очевидно, что процесс существует)? Как я могу читать из /proc/$pid/mem, затем?

126 голосов | спросил Gilles 23 Jam1000000amSun, 23 Jan 2011 02:21:03 +030011 2011, 02:21:03

4 ответа


124

/proc/$pid/maps

/proc/$pid/mem показывает содержимое памяти $ pid, сопоставленное так же, как и в процессе, т. е. байт при смещении x в псевдо -file совпадает с байтом в адресе x в процессе. Если адрес не отображается в процессе, чтение из соответствующего смещения в файле возвращает EIO (Ошибка ввода /вывода). Например, поскольку первая страница в процессе никогда не отображается (так что разыменование указателя NULL происходит с ошибкой, а не непреднамеренно доступ к фактической памяти), чтение первого байта /proc/$pid/mem всегда дает ошибку ввода /вывода.

Чтобы узнать, какие части памяти процесса отображаются, нужно прочитать /proc/$pid/maps. Этот файл содержит одну строку для каждой отображаемой области, выглядящую следующим образом:

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

Первые два числа являются границами области (адреса первого байта и байта после последнего, в гекса). Следующий столбец содержит разрешения, тогда есть информация о файле (смещение, устройство, индекс и имя), если это сопоставление файлов. См. proc(5) или Понимание Linux /proc /id /maps для Дополнительная информация.

Вот сценарий доказательной концепции, который выгружает содержимое собственной памяти.

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

Если вы попытаетесь прочитать псевдофайл файла mem другого процесса, это не сработает: вы получите ошибку ESRCH (Нет такого процесса).

Разрешения на /proc/$pid/mem (r--------) более либеральны, чем должно быть. Например, вы не сможете прочитать память процесса setuid. Кроме того, пытаясь прочитать память процесса во время процесса изменения, это может дать читателю непоследовательное представление о памяти, и, что еще хуже, были условия гонки, которые могли бы отслеживать более старые версии ядра Linux (согласно этот поток lkml , хотя я не знаю деталей). Поэтому необходимы дополнительные проверки:

  • Процесс, который хочет читать из /proc/$pid/mem, должен присоединяться к процессу с помощью ptrace с флагом PTRACE_ATTACH. Это то, что делают отладчики, когда они начинают отлаживать процесс; это также означает strace для системных вызовов процесса. Когда читатель закончит чтение из /proc/$pid/mem, он должен отсоединиться, вызвав ptrace с флагом PTRACE_DETACH.
  • >
  • Наблюдаемый процесс не должен выполняться. Обычно вызов ptrace(PTRACE_ATTACH, …) останавливает целевой процесс (он отправляет сигнал STOP), но есть условие гонки (доставка сигнала асинхронна) поэтому трассировщик должен вызвать wait (как описано в ptrace(2) ).

Процесс, выполняемый как пользователь root, может считывать память любого процесса без необходимости вызова ptrace, но наблюдаемый процесс должен быть остановлен или чтение будет по-прежнему возвращать ESRCH.

В исходном ядре Linux код, содержащий записи для каждого процесса в /proc, находится в fs/proc/base.c , а функция для чтения из /proc/$pid/mem is mem_read , Дополнительную проверку выполняет check_mem_permission .

Вот пример кода C для присоединения к процессу и чтения фрагмента его файла mem (ошибка проверки опущена):

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

Я уже опубликовал сценарий доказательной концепции для демпинг /proc/$pid/mem в другой поток .

ответил Gilles 23 Jam1000000amSun, 23 Jan 2011 02:21:23 +030011 2011, 02:21:23
24

Эта команда (из gdb) надежно надевает память:

gcore pid

Дампы могут быть большими, используйте -o outfile, если в вашем текущем каталоге недостаточно места.

ответил Tobu 19 MarpmTue, 19 Mar 2013 15:17:51 +04002013-03-19T15:17:51+04:0003 2013, 15:17:51
9

Когда вы выполняете cat /proc/$$/mem, переменная $$ оценивается bash, которая вставляет свой собственный pid. Затем он выполняет cat, который имеет другой pid. В итоге вы получите cat, пытаясь прочитать память bash, ее родительский процесс. Поскольку непривилегированные процессы могут читать только собственное пространство памяти, это становится отрицательным ядром.

Вот пример:

$ echo $$
17823

Обратите внимание, что $$ оценивается до 17823. Давайте посмотрим, какой именно процесс.

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

Это моя текущая оболочка.

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

Здесь снова $$ оценивается до 17823, что является моей оболочкой. cat не может прочитать пространство памяти моей оболочки.

ответил bahamat 24 Jpm1000000pmMon, 24 Jan 2011 20:02:22 +030011 2011, 20:02:22
6

Вот небольшая программа, которую я написал в C:

Использование:

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

Программа использует /proc /$ pid /maps, чтобы найти все области отображенных областей процесса, а затем прочитать эти регионы из /proc /$ pid /mem, по одной странице за раз. эти страницы записываются в стандартный вывод или IP-адрес и TCP-порт, которые вы указали.

Код (протестирован на Android, требует прав суперпользователя):

 #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 27 SunEurope/Moscow2015-12-27T17:51:53+03:00Europe/Moscow12bEurope/MoscowSun, 27 Dec 2015 17:51:53 +0300 2015, 17:51:53

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132