Bagaimana saya membaca dari / proc / $ pid / mem di Linux?

142

The Linux proc(5)halaman manual memberitahu saya bahwa /proc/$pid/mem“dapat digunakan untuk mengakses halaman memori proses ini”. Tetapi upaya langsung untuk menggunakannya hanya memberi saya

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

Mengapa tidak catdapat mencetak memorinya sendiri ( /proc/self/mem)? Dan apa kesalahan "proses seperti ini" yang aneh ketika saya mencoba mencetak memori shell ( /proc/$$/mem, jelas prosesnya ada)? Bagaimana saya bisa membaca /proc/$pid/mem?

Gilles
sumber
1
Ada beberapa metode lain yang menunjukkan cara melakukan ini di SF dalam tanya jawab berjudul: Dump memori proses linux ke file
slm
jawaban
pizdelect

Jawaban:

140

/proc/$pid/maps

/proc/$pid/memmemperlihatkan isi memori $ pid yang dipetakan dengan cara yang sama seperti dalam proses, yaitu byte pada offset x dalam file pseudo sama dengan byte pada alamat x dalam proses. Jika alamat tidak dipetakan dalam proses, bacalah dari offset yang sesuai dalam file yang dikembalikan EIO(Kesalahan input / output). Sebagai contoh, karena halaman pertama dalam suatu proses tidak pernah dipetakan (sehingga dereferencing NULLpointer gagal dengan bersih daripada mengakses memori yang sebenarnya), membaca byte pertama /proc/$pid/memselalu menghasilkan kesalahan I / O.

Cara untuk mengetahui bagian mana dari memori proses yang dipetakan adalah dengan membaca /proc/$pid/maps. File ini berisi satu baris per wilayah yang dipetakan, terlihat seperti ini:

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

Dua angka pertama adalah batas wilayah (alamat byte pertama dan byte setelah yang terakhir, dalam heksa). Kolom berikutnya berisi izin, lalu ada beberapa informasi tentang file (offset, perangkat, inode, dan nama) jika ini adalah pemetaan file. Lihat proc(5)halaman manual atau Memahami Linux / proc / id / maps untuk informasi lebih lanjut.

Berikut ini skrip proof-of-concept yang membuang konten dari ingatannya sendiri.

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

Jika Anda mencoba membaca dari file mempseudo dari proses lain, itu tidak berfungsi: Anda mendapatkan ESRCHkesalahan (Tidak ada proses seperti itu).

Izin pada /proc/$pid/mem( r--------) lebih liberal daripada yang seharusnya. Misalnya, Anda seharusnya tidak dapat membaca memori proses setuid. Lebih jauh lagi, mencoba membaca memori suatu proses ketika proses memodifikasi itu dapat memberikan pembaca pandangan yang tidak konsisten dari memori, dan lebih buruk lagi, ada kondisi ras yang dapat melacak versi Linux kernel yang lebih lama (menurut utas lkml ini , meskipun saya tidak tahu detailnya). Jadi diperlukan pemeriksaan tambahan:

  • Proses yang ingin dibaca /proc/$pid/memharus dilampirkan ke proses menggunakan ptracedengan PTRACE_ATTACHbendera. Inilah yang dilakukan para debugger ketika mereka memulai proses debug; itu juga apa yang stracedilakukan untuk panggilan sistem suatu proses. Setelah pembaca selesai membaca dari /proc/$pid/mem, pembaca harus melepaskan dengan memanggil ptracedengan PTRACE_DETACHbendera.
  • Proses yang diamati tidak boleh berjalan. Biasanya panggilan ptrace(PTRACE_ATTACH, …)akan menghentikan proses target (mengirimkan STOPsinyal), tetapi ada kondisi balapan (pengiriman sinyal asinkron), sehingga pelacak harus menelepon wait(seperti yang didokumentasikan dalam ptrace(2)).

Sebuah proses yang berjalan sebagai root dapat membaca memori proses apa pun, tanpa perlu memanggil ptrace, tetapi proses yang diamati harus dihentikan, atau pembacaan akan tetap kembali ESRCH.

Dalam sumber kernel Linux, kode menyediakan entri per-proses di /procdalam fs/proc/base.c, dan fungsi untuk membaca dari /proc/$pid/memadalah mem_read. Pemeriksaan tambahan dilakukan oleh check_mem_permission.

Berikut ini beberapa contoh kode C untuk dilampirkan ke suatu proses dan membaca sebagian dari memfilenya (pengecekan kesalahan dihilangkan):

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

Saya sudah memasang skrip proof-of-concept untuk dibuang /proc/$pid/memdi utas lain .

Gilles
sumber
2
@abc ada, membaca dari /proc/$pid/memlangsung (baik dengan catatau ddatau apa pun) tidak bekerja. Baca jawabanku.
Gilles
4
@abc Dia membaca dari /proc/self/mem. Suatu proses dapat membaca ruang memorinya sendiri dengan baik, itu membaca ruang memori proses lain yang membutuhkan PTRACE_ATTACH.
Gilles
2
Perhatikan bahwa dengan kernel Linux terbaru, Anda tidak perlu ke PTRACE_ATTACH. Perubahan ini disertai dengan process_vm_readv()panggilan sistem (Linux 3.2).
ysdx
2
Hm, dengan Linux 4.14.8 ini tidak bekerja untuk saya: mulai proses berjalan lama yang sibuk menulis output ke / dev / null. Kemudian proses lain dapat membuka, mencari dan membaca beberapa byte dari / proc / $ otherpid / mem (yaitu pada beberapa offset yang dirujuk melalui vektor bantu) - tanpa harus memasang ptrace-attach / detach atau menghentikan / memulai proses. Bekerja jika proses berjalan di bawah pengguna yang sama dan untuk pengguna root. Yaitu saya tidak dapat menghasilkan ESRCHkesalahan dalam skenario ini.
maxschlepzig
1
@maxschlepzig Saya kira itu perubahan yang disebutkan oleh ysdx di komentar di atas.
Gilles
28

Perintah ini (dari gdb) membuang memori dengan andal:

gcore pid

Dump bisa besar, gunakan -o outfilejika direktori Anda saat ini tidak memiliki cukup ruang.

Tobu
sumber
12

Ketika Anda menjalankan cat /proc/$$/memvariabel $$dievaluasi oleh oleh bash yang menyisipkan pid sendiri. Ini kemudian mengeksekusi catyang memiliki pid berbeda. Anda berakhir dengan catmencoba membaca memori bash, proses induknya. Karena proses yang tidak istimewa hanya dapat membaca ruang memori mereka sendiri, ini ditolak oleh kernel.

Ini sebuah contoh:

$ echo $$
17823

Catatan yang $$mengevaluasi ke 17823. Mari kita lihat proses mana yang.

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

Ini shell saya saat ini.

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

Di sini lagi $$mengevaluasi ke 17823, yang merupakan shell saya. cattidak dapat membaca ruang memori shell saya.

bahamat
sumber
Anda akhirnya mencoba membaca memori apa pun $piditu. Seperti yang saya jelaskan dalam jawaban saya, membaca memori dari proses yang berbeda mengharuskan Anda untuk memotongnya.
Gilles
Yang akan menjadi bash. Saya tidak mengatakan jawaban Anda salah. Saya hanya menjawab dalam istilah yang lebih awam, "mengapa ini tidak berhasil".
bahamat
@ Bahahamat: Apakah Anda memikirkan $$kapan Anda menulis (dan membaca) $pid?
Gilles
Ya ... ia mulai meminta mengacu $$dan menempatkan $piddi akhir. Saya memindahkannya di kepala saya tanpa menyadarinya. Seluruh jawaban saya seharusnya merujuk $$, bukan $pid.
bahamat
@ Bahahamat: Apakah pertanyaannya lebih jelas sekarang? (BTW saya tidak melihat komentar Anda kecuali Anda menggunakan "@Gilles", saya kebetulan melihat hasil edit Anda dan datang untuk melihat.)
Gilles
7

Berikut ini adalah program kecil yang saya tulis di C:

Pemakaian:

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

Program ini menggunakan / proc / $ pid / maps untuk menemukan semua wilayah memori yang dipetakan dari proses, dan kemudian membaca wilayah tersebut dari / proc / $ pid / mem, satu halaman sekaligus. halaman tersebut ditulis ke stdout atau alamat IP dan port TCP yang Anda tentukan.

Kode (diuji pada Android, memerlukan izin pengguna super):

#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
sumber
5
Tambahkan beberapa penjelasan tentang kode Anda. Satu-satunya komentar Anda agak tidak berguna: write to stdouttepat di atas fwrite(..., stdout). Lihat programmers.stackexchange.com/questions/119600/...
muru
Anda bilang Anda hanya mengujinya di Android, jadi saya hanya ingin mengonfirmasi, itu berfungsi dengan baik di Linux 4.4.0-28 x86_64, seperti yang Anda harapkan
apricot boy
saya mendapatkan banyak data seperti / @ 8 l / @ l pada stdout yang tidak pernah berakhir ide mengapa? dikompilasi di Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU / Linux Model thread: posix gcc versi 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us
ceph3us, penggunaan umum adalah untuk mengirim data ke file (mis. memdump <pid>> /sdcard/memdump.bin)
Tal Aloni