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


142

、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ますか?


1
題したこのQ&AにSFにこれを実行する方法を示し、他のいくつかの方法がありますLinuxのプロセスのファイルにメモリダンプ
SLMの

最新の回答
pizdelect

回答:


140

/proc/$pid/maps

/proc/$pid/memは、プロセスと同じ方法でマップされた$ pidのメモリの内容を示します。つまり、擬似ファイルのオフセットxのバイトは、プロセスのアドレス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つの数字は、領域の境界です(最初のバイトのアドレスと、最後のバイトのアドレス(16進数))。次の列には権限が含まれており、ファイルマッピングの場合、ファイルに関する情報(オフセット、デバイス、iノード、名前)があります。詳細については、proc(5)manページまたは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/memr--------)の許可は、本来あるべきものよりも自由です。たとえば、setuidプロセスのメモリを読み取ることはできません。さらに、プロセスの変更中にプロセスのメモリを読み取ろうとすると、リーダーにメモリの一貫性のないビューが表示される可能性があり、さらに悪いことに、古いバージョンのLinuxカーネル(このlkmlスレッドによると、詳細がわからない)。したがって、追加のチェックが必要です。

  • 読み取り元/proc/$pid/memのプロセスptraceは、PTRACE_ATTACHフラグを使用してプロセスにアタッチする必要があります。これは、プロセスのデバッグを開始するときにデバッガーが行うことです。またstrace、プロセスのシステムコールに対しても行います。リーダーは、からの読み取りを完了すると、フラグ/proc/$pid/memを使用ptraceして呼び出してデタッチする必要がありPTRACE_DETACHます。
  • 観察されたプロセスが実行されていてはなりません。通常、呼び出しptrace(PTRACE_ATTACH, …)はターゲットプロセスを停止します(STOPシグナルを送信します)が、競合状態(シグナルの配信は非同期)があるため、トレーサーはwait(をptrace(2)参照)を呼び出す必要があります。

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

Linuxカーネルソースでは、プロセスごとのエントリを提供するコード/procはにありfs/proc/base.c、読み込む関数/proc/$pid/memmem_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別のスレッドにダンプするための概念実証スクリプトをすでに投稿しました


2
ノー@abc、からの読み込み/proc/$pid/mem(とするかどうかを直接catまたはdd動作していないか、または何か他のもの)。私の答えを読んでください。
ジル

4
@abc彼はから読んでい/proc/self/memます。プロセスは自身のメモリ空間をうまく読み込むことができ、それはを必要とする別のプロセスのメモリ空間を読み込んでいますPTRACE_ATTACH
ジル

2
最近のLinuxカーネルでは、PTRACE_ATTACHをする必要がないことに注意してください。この変更は、process_vm_readv()システムコール(Linux 3.2)に付属しています。
ysdx

2
ええと、Linux 4.14.8では、これは機能します。出力を/ dev / nullに書き込むのに忙しい長時間実行プロセスを開始します。その後、別のプロセスが/ proc / $ otherpid / memからいくつかのバイトを開き、シークし、読み取ることができます(つまり、補助ベクトルを介して参照されるオフセットで)-プロセスをptrace-attach / detachまたはstop / startする必要がありません。プロセスが同じユーザーでrootユーザーに対して実行される場合に機能します。つまりESRCH、このシナリオでエラーを生成することはできません。
maxschlepzig

1
@maxschlepzigこれは、上記のコメントでysdxが言及した変更だと思います。
ジル

28

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

gcore pid

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


12

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


あなたは何でもの記憶を読み取ろうとすることになります$pid。私の答えで説明したように、別のプロセスのメモリを読み取るには、それを追跡する必要があります。
ジル

これはbashになります。私はあなたの答えが間違っていると言っていませんでした。私は「なぜこのようにしないのか」というより一般的な言葉で答えていました。
バハマ

@bahamat:$$書く(そして読む)ことを考えています$pidか?
ジル

はい...彼は、参照して最後に$$置くことを尋ね始めました$pid。気付かずに頭の中で転置しました。私の答え全体ではなく$$、を参照する必要があり$pidます。
バハマ

@bahamat:質問は今より明確ですか?(ところで、「@ Gilles」を使用しない限り、コメントは表示されません。たまたまあなたの編集を見ただけで、表示されるようになりました。)
Gilles

7

Cで書いた小さなプログラムを次に示します。

使用法:

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

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

5
コードの説明を追加します。あなたの唯一のコメントは無意味です:write to stdoutすぐ上fwrite(..., stdout)です。Programmers.stackexchange.com/questions/119600/…を
muru

あなたはAndroidでしかテストしていないと言ったので、確認したいだけです。Linux4.4.0-28 x86_64でうまく動作します。期待どおり
アプリコットボーイ

stdoutで / @8 l / @ lのような大量のデータを取得します。Linux 4.9.0-3-amd64#1 SMP Debian 4.9.25-1(2017-05-02)x86_64 GNU / Linuxスレッドモデルでコンパイル:posix gccバージョン6.3.0 20170516(Debian 6.3.0-18)
ceph3us

ceph3usは、一般的な使用は、パイプにファイルにデータ(例えばmemdump <PID>> /sdcard/memdump.bin)
タルアロニ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.