web-dev-qa-db-ja.com

Linuxで/ proc / $ pid / memから読み取るにはどうすればよいですか?

Linux proc(5) manページ は、/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、それで?

_/proc/$pid/maps_

_/proc/$pid/mem_は、プロセスと同じ方法でマップされた$ pidのメモリの内容を示します。つまり、疑似のオフセットxのバイトfileは、プロセスのアドレスxのバイトと同じです。プロセスでアドレスがマップされていない場合、ファイル内の対応するオフセットから読み取ると、EIO(入出力エラー)が返されます。たとえば、プロセスの最初のページはマップされないため(NULLポインターの逆参照が実際のメモリに意図せずにアクセスするのではなく正常に失敗するため)、_/proc/$pid/mem_の最初のバイトを読み取ると常にI/Oエラー。

プロセスメモリのどの部分がマップされているかを確認する方法は、_/proc/$pid/maps_を読み取ることです。このファイルには、次のように、マップされた領域ごとに1行が含まれています。

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

最初の2つの数値は、領域の境界です(最初のバイトと最後の後のバイトのアドレス、ヘキサで)。次の列には権限が含まれ、ファイルマッピングの場合は、ファイルに関するいくつかの情報(オフセット、デバイス、iノード、および名前)が表示されます。詳細については、 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_ATTACH_フラグとともに ptrace を使用してプロセスにアタッチする必要があります。これは、デバッガがプロセスのデバッグを開始するときに行うことです。 strace がプロセスのシステムコールに対して行うことでもあります。リーダーが_/proc/$pid/mem_からの読み取りを完了すると、_PTRACE_DETACH_フラグを指定してptraceを呼び出すことにより、デタッチする必要があります。
  • 監視対象のプロセスが実行されていてはなりません。通常ptrace(PTRACE_ATTACH, …)を呼び出すとターゲットプロセスが停止します(STOPシグナルが送信されます)が、競合状態があるため(シグナルの配信は非同期)、トレーサはwaitを呼び出す必要があります( ptrace(2) に記載されています)。

Rootとして実行されているプロセスは、ptraceを呼び出す必要なく、任意のプロセスのメモリを読み取ることができますが、監視されているプロセスを停止する必要があります。そうしないと、読み取りはESRCHを返します。

Linuxカーネルソースでは、_/proc_のプロセスごとのエントリを提供するコードは _fs/proc/base.c_ にあり、_/proc/$pid/mem_から読み取る関数は _mem_read_ 。追加のチェックは _check_mem_permission_ によって実行されます。

以下は、プロセスにアタッチしてmemファイルのチャンクを読み取るためのサンプルCコードです(エラーチェックは省略されています)。

_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_をダンプするための概念実証スクリプトをすでに投稿しています

このコマンドは(gdbから)メモリを確実にダンプします。

gcore pid

ダンプは大きくなる可能性があります。現在のディレクトリに十分なスペースがない場合は、-o outfileを使用してください。

28
Tobu

cat /proc/$$/memを実行すると、変数$$は独自のpidを挿入するbashによって評価されます。次に、pidが異なるcatを実行します。 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がシェルのメモリ空間を読み取れません。

12
bahamat

ここに私がCで書いた小さなプログラムがあります:

使用法:

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

プログラムは、/ proc/$ pid/mapsを使用してプロセスのすべてのマップされたメモリ領域を検索し、それらの領域を/ proc/$ pid/memから一度に1ページずつ読み取ります。これらのページは、標準出力または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);
    }
}
8
Tal Aloni