it-swarm-id.com

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

Linux proc(5) man page memberitahu saya bahwa /proc/$pid/mem “Dapat digunakan untuk mengakses halaman memori suatu proses”. 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 cat tidak dapat 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 dari /proc/$pid/mem, kemudian?

/proc/$pid/maps

/proc/$pid/mem Menunjukkan isi memori $ pid yang dipetakan dengan cara yang sama seperti dalam proses, yaitu byte pada offset x di pseudo -file sama dengan byte pada alamat x dalam proses. Jika alamat tidak dipetakan dalam proses, membaca dari offset yang sesuai dalam file mengembalikan EIO (Kesalahan input/output). Misalnya, karena halaman pertama dalam suatu proses tidak pernah dipetakan (sehingga penunjuk referensi NULL gagal dengan bersih daripada mengakses memori yang sebenarnya), membaca byte pertama /proc/$pid/mem Selalu menghasilkan I/O kesalahan.

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 adalah skrip proof-of-concept yang membuang konten 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 mem pseudo dari proses lain, itu tidak berfungsi: Anda mendapatkan kesalahan ESRCH (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 lama dari kernel Linux (menurut lkml ini utas , meskipun saya tidak tahu detailnya). Jadi diperlukan pemeriksaan tambahan:

  • Proses yang ingin dibaca dari /proc/$pid/mem Harus dilampirkan ke proses menggunakan ptrace dengan bendera PTRACE_ATTACH. Inilah yang dilakukan para debugger ketika mereka memulai proses debug; itu juga apa yang strace untuk panggilan sistem suatu proses. Setelah pembaca selesai membaca dari /proc/$pid/mem, Pembaca harus melepaskan dengan memanggil ptrace dengan bendera PTRACE_DETACH.
  • Proses yang diamati tidak boleh berjalan. Biasanya memanggil ptrace(PTRACE_ATTACH, …) akan menghentikan proses target (mengirimkan sinyal STOP), tetapi ada kondisi balapan (pengiriman sinyal tidak sinkron), sehingga pelacak harus memanggil 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 masih akan mengembalikan ESRCH.

Di sumber kernel Linux, kode yang menyediakan entri per-proses dalam /proc Ada di fs/proc/base.c , dan fungsi untuk membaca dari /proc/$pid/mem Adalah - mem_read . Pemeriksaan tambahan dilakukan oleh check_mem_permission .

Berikut ini beberapa contoh kode C untuk dilampirkan ke suatu proses dan membaca sebagian dari file mem (pemeriksaan 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 memposting skrip proof-of-concept untuk membuang /proc/$pid/mem Di utas lain .

Perintah ini (dari gdb) membuang memori dengan andal:

gcore pid

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

28
Tobu

Ketika Anda menjalankan cat /proc/$$/mem variabel $$ dievaluasi oleh bash yang memasukkan pidnya sendiri. Itu kemudian mengeksekusi cat yang memiliki pid berbeda. Anda berakhir dengan cat mencoba membaca memori bash, proses induknya. Karena proses tidak istimewa hanya dapat membaca ruang memori mereka sendiri, ini ditolak oleh kernel.

Ini sebuah contoh:

$ echo $$
17823

Perhatikan bahwa $$ 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

Disini lagi $$ mengevaluasi ke 17823, yang merupakan Shell saya. cat tidak dapat membaca ruang memori Shell saya.

12
bahamat

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 TCP port 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);
    }
}
8
Tal Aloni