it-swarm-vi.com

Làm cách nào để tôi đọc từ / Proc / $ pid / mem trong Linux?

Linux proc(5) man page cho tôi biết rằng /proc/$pid/mem Có thể được sử dụng để truy cập vào các trang của bộ nhớ của một quá trình. Nhưng một nỗ lực đơn giản để sử dụng nó chỉ mang lại cho tôi

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

Tại sao không cat có thể in bộ nhớ của chính nó (/proc/self/mem)? Và điều lạ lùng này là gì không có lỗi xử lý như vậy khi tôi cố gắng in bộ nhớ của Shell (/proc/$$/mem, rõ ràng quá trình tồn tại)? Làm thế nào tôi có thể đọc từ /proc/$pid/mem, sau đó?

144

/proc/$pid/maps

/proc/$pid/mem Hiển thị nội dung trong bộ nhớ của $ pid được ánh xạ giống như trong quy trình, nghĩa là byte tại offset x trong giả -file giống như byte tại địa chỉ x trong quy trình. Nếu một địa chỉ không được ánh xạ trong quá trình, đọc từ phần bù tương ứng trong tệp sẽ trả về EIO (Lỗi đầu vào/đầu ra). Ví dụ: vì trang đầu tiên trong quy trình không bao giờ được ánh xạ (do đó, việc hủy bỏ con trỏ NULL không thành công thay vì truy cập bộ nhớ thực tế), đọc byte đầu tiên của /proc/$pid/mem Luôn mang lại I/Lỗi O.

Cách để tìm ra phần nào của bộ nhớ quy trình được ánh xạ là đọc /proc/$pid/maps. Tệp này chứa một dòng trên mỗi vùng được ánh xạ, trông như thế này:

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

Hai số đầu tiên là ranh giới của vùng (địa chỉ của byte đầu tiên và byte sau cuối, trong hexa). Cột tiếp theo chứa các quyền, sau đó có một số thông tin về tệp (offset, thiết bị, inode và tên) nếu đây là ánh xạ tệp. Xem proc(5) trang man hoặc Hiểu Linux/Proc/id/maps để biết thêm thông tin.

Đây là một kịch bản bằng chứng khái niệm loại bỏ nội dung của bộ nhớ của chính nó.

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

Nếu bạn cố đọc từ tệp giả mem của quy trình khác, thì nó không hoạt động: bạn gặp lỗi ESRCH (Không có quy trình như vậy).

Các quyền trên /proc/$pid/mem (r--------) Tự do hơn so với những gì nên có. Ví dụ: bạn không thể đọc bộ nhớ của quy trình setuid. Hơn nữa, cố gắng đọc bộ nhớ của một tiến trình trong khi quá trình đang sửa đổi nó có thể cung cấp cho người đọc một cái nhìn không nhất quán về bộ nhớ, và tệ hơn, có những điều kiện chủng tộc có thể theo dõi các phiên bản cũ hơn của nhân Linux (theo lkml này chủ đề , mặc dù tôi không biết chi tiết). Vì vậy, kiểm tra bổ sung là cần thiết:

  • Quá trình muốn đọc từ /proc/$pid/mem Phải đính kèm vào quy trình bằng cách sử dụng ptrace với cờ PTRACE_ATTACH. Đây là những gì trình gỡ lỗi làm khi họ bắt đầu gỡ lỗi một quy trình; đó cũng là những gì strace thực hiện các cuộc gọi hệ thống của quy trình. Khi người đọc đã đọc xong từ /proc/$pid/mem, Người đọc sẽ tách ra bằng cách gọi ptrace với cờ PTRACE_DETACH.
  • Quá trình quan sát không được chạy. Thông thường gọi ptrace(PTRACE_ATTACH, …) sẽ dừng quá trình đích (nó sẽ gửi tín hiệu STOP), nhưng có một điều kiện cuộc đua (phân phối tín hiệu không đồng bộ), vì vậy người theo dõi nên gọi wait (như được ghi lại trong ptrace(2) ).

Một tiến trình đang chạy như root có thể đọc bất kỳ bộ nhớ của tiến trình nào, mà không cần gọi ptrace, nhưng quá trình quan sát phải được dừng lại, hoặc đọc vẫn sẽ trả về ESRCH.

Trong nguồn nhân Linux, mã cung cấp các mục nhập theo quy trình trong /proc Nằm trong fs/proc/base.c và chức năng đọc từ /proc/$pid/mem Là - mem_read . Việc kiểm tra bổ sung được thực hiện bởi check_mem_permission .

Đây là một số mã C mẫu để đính kèm vào một quy trình và đọc một đoạn của tệp mem (kiểm tra lỗi bị bỏ qua):

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

Tôi đã đăng một tập lệnh bằng chứng về việc bán phá giá /proc/$pid/mem Trên một chủ đề khác .

146

Lệnh này (từ gdb) kết xuất bộ nhớ một cách đáng tin cậy:

gcore pid

Bãi chứa có thể lớn, sử dụng -o outfile nếu thư mục hiện tại của bạn không có đủ chỗ.

28
Tobu

Khi bạn thực thi cat /proc/$$/mem biến $$ được đánh giá bởi bash chèn pid của chính nó. Sau đó, nó thực thi cat có pid khác. Bạn kết thúc với cat cố gắng đọc bộ nhớ của bash, tiến trình cha của nó. Vì các tiến trình không đặc quyền chỉ có thể đọc không gian bộ nhớ của riêng chúng, điều này bị hạt nhân từ chối.

Đây là một ví dụ:

$ echo $$
17823

Lưu ý rằng $$ ước tính đến 17823. Hãy xem đó là quá trình nào.

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

Đó là Shell hiện tại của tôi.

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

Nữa này $$ ước tính đến 17823, đó là Shell của tôi. cat không thể đọc không gian bộ nhớ của Shell.

12
bahamat

Đây là một chương trình nhỏ tôi đã viết trong C:

Sử dụng:

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

Chương trình sử dụng/Proc/$ pid/maps để tìm tất cả các vùng bộ nhớ được ánh xạ của quá trình, sau đó đọc các vùng đó từ/Proc/$ pid/mem, mỗi lần một trang. các trang đó được ghi vào thiết bị xuất chuẩn hoặc địa chỉ IP và TCP bạn đã chỉ định.

Mã (được thử nghiệm trên Android, yêu cầu quyền siêu người dùng):

#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