ฉันจะอ่านจาก / proc / $ pid / mem ภายใต้ Linux ได้อย่างไร


142

ลินุกซ์proc(5)หน้าคนบอกฉันว่า/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
มีวิธีการอื่น ๆ อีกหลายวิธีที่แสดงวิธีการทำเช่นนี้ใน SF ในคำถาม & คำตอบนี้: ถ่ายโอนหน่วยความจำของกระบวนการ linux ไปยังไฟล์
slm

คำตอบ:


140

/proc/$pid/maps

/proc/$pid/memแสดงเนื้อหาของหน่วยความจำของ $ pid ที่แมปแบบเดียวกับในกระบวนการเช่นไบต์ที่ออฟเซ็ตxในไฟล์หลอกจะเหมือนกับไบต์ที่แอดเดรสxในกระบวนการ หากที่อยู่นั้นไม่ได้ถูกแมปในกระบวนการการอ่านจากออฟเซ็ตที่สอดคล้องกันในไฟล์จะส่งคืนEIO(ข้อผิดพลาดอินพุต / เอาต์พุต) ตัวอย่างเช่นเนื่องจากหน้าแรกในกระบวนการไม่เคยถูกแมป (ดังนั้นการยกเลิกการอ้างอิงNULLตัวชี้จึงล้มเหลวอย่างสมบูรณ์แทนที่จะเข้าถึงหน่วยความจำจริงโดยไม่ได้ตั้งใจ) การอ่านไบต์แรกของ/proc/$pid/memข้อผิดพลาด I / O จะเกิดขึ้นเสมอ

/proc/$pid/mapsวิธีการที่จะหาสิ่งที่ชิ้นส่วนของหน่วยความจำกระบวนการแมปคือการอ่าน ไฟล์นี้มีหนึ่งบรรทัดต่อภูมิภาคที่แมปซึ่งมีลักษณะดังนี้:

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

ตัวเลขสองตัวแรกคือขอบเขตของภูมิภาค (ที่อยู่ของไบต์แรกและไบต์หลังสุดเป็น hexa) คอลัมน์ถัดไปมีสิทธิ์แล้วมีข้อมูลบางอย่างเกี่ยวกับไฟล์ (ออฟเซ็ตอุปกรณ์ inode และชื่อ) หากเป็นการแมปไฟล์ ดูproc(5)man page หรือทำความเข้าใจกับ 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 นอกจากนี้พยายามอ่านหน่วยความจำของกระบวนการในขณะที่กระบวนการที่มีการปรับเปลี่ยนก็จะให้ผู้อ่านมุมมองที่ไม่สอดคล้องกันของหน่วยความจำและเลวร้ายยิ่งมีสภาพการแข่งขันที่สามารถติดตามรุ่นเก่าของลินุกซ์ (ตามหัวข้อ lkml นี้แต่ฉัน ไม่ทราบรายละเอียด) ดังนั้นจึงจำเป็นต้องมีการตรวจสอบเพิ่มเติม:

  • กระบวนการที่ต้องการอ่านจาก/proc/$pid/memต้องแนบกับกระบวนการที่ใช้ptraceกับPTRACE_ATTACHแฟล็ก นี่คือสิ่งที่ผู้ debuggers ทำเมื่อพวกเขาเริ่มการดีบักกระบวนการ นอกจากนี้ยังเป็นสิ่งที่straceจะเรียกระบบของกระบวนการ เมื่อผู้อ่านอ่านเสร็จแล้ว/proc/$pid/memควรถอดออกด้วยการโทรptraceด้วยPTRACE_DETACHค่าสถานะ
  • กระบวนการที่สังเกตต้องไม่ทำงาน โดยปกติการโทรptrace(PTRACE_ATTACH, …)จะหยุดกระบวนการเป้าหมาย (ส่งSTOPสัญญาณ) แต่มีสภาพการแข่งขัน (การส่งสัญญาณแบบอะซิงโครนัส) ดังนั้นผู้ติดตามควรเรียกwait(ตามที่บันทึกไว้ในptrace(2))

กระบวนการทำงานเป็นรากสามารถอ่านหน่วยความจำของกระบวนการใด ๆ โดยไม่จำเป็นต้องเรียกแต่กระบวนการสังเกตต้องหยุดการทำงานหรือการอ่านจะยังคงกลับมาptraceESRCH

ในแหล่งเคอร์เนลรหัสให้รายการต่อกระบวนการในการ/procอยู่ในfs/proc/base.cและฟังก์ชั่นในการอ่านจากเป็น/proc/$pid/mem การตรวจสอบเพิ่มเติมที่ดำเนินการโดยmem_readcheck_mem_permission

ต่อไปนี้เป็นตัวอย่างโค้ด C ที่จะแนบกับกระบวนการและอ่านmemไฟล์ของมัน(ละเว้นการตรวจสอบข้อผิดพลาด):

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หรือสิ่งอื่น) ไม่ทำงาน อ่านคำตอบของฉัน
Gilles

4
@abc /proc/self/memเขาอ่านจาก PTRACE_ATTACHกระบวนการสามารถอ่านพื้นที่หน่วยความจำของตัวเองได้ดีก็อ่านพื้นที่หน่วยความจำของกระบวนการอื่นที่ต้องใช้
Gilles

2
โปรดทราบว่าด้วยเคอร์เนล Linux ล่าสุดคุณไม่จำเป็นต้อง PTRACE_ATTACH การเปลี่ยนแปลงนี้มาพร้อมกับการprocess_vm_readv()เรียกของระบบ (Linux 3.2)
ysdx

2
หืมที่มี Linux 4.14.8 ใช้งานได้สำหรับฉัน: เริ่มกระบวนการที่ใช้เวลานานซึ่งกำลังยุ่งอยู่กับการเขียนเอาต์พุตไปยัง / dev / null จากนั้นกระบวนการอื่นสามารถเปิดค้นหาและอ่านไบต์บางส่วนจาก / proc / $ otherpid / mem (เช่นในบางช่วงเวลาที่อ้างอิงผ่านเวกเตอร์เสริม) - โดยไม่ต้องใช้ ptrace-attach / detach หรือหยุด / เริ่มต้นกระบวนการ ใช้งานได้หากกระบวนการทำงานภายใต้ผู้ใช้เดียวกันและสำหรับผู้ใช้รูท เช่นฉันไม่สามารถให้ESRCHข้อผิดพลาดในสถานการณ์นี้
maxschlepzig

1
@ maxschlepzig ฉันคิดว่านั่นคือการเปลี่ยนแปลงที่กล่าวถึงโดย ysdx ในความคิดเห็นด้านบน
Gilles

28

คำสั่งนี้ (จาก gdb) ดัมพ์หน่วยความจำอย่างน่าเชื่อถือ:

gcore pid

การถ่ายโอนข้อมูลอาจมีขนาดใหญ่ใช้-o outfileหากไดเรกทอรีปัจจุบันของคุณมีพื้นที่ไม่เพียงพอ


12

เมื่อคุณรันcat /proc/$$/memตัวแปร$$ถูกประเมินโดย bash ซึ่งแทรก pid ของตัวเอง จากนั้นจะดำเนินการ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คือ ตามที่ฉันอธิบายในคำตอบของฉันการอ่านหน่วยความจำของกระบวนการที่แตกต่างกันนั้นคุณต้องใช้เพื่อทำความเข้าใจ
Gilles

ซึ่งกำลังจะทุบตี ฉันไม่ได้บอกว่าคำตอบของคุณผิด ฉันแค่ตอบในเงื่อนไขของคนธรรมดามากขึ้น "ทำไมงานนี้ถึงไม่ได้"
bahamat

@bahamat: คุณคิดว่า$$เมื่อคุณเขียน (และอ่าน) $pid?
Gilles

ใช่ ... เขาเริ่มต้นจากการขอหมายถึง$$และใส่$pidในตอนท้าย ฉันเปลี่ยนมันในหัวของฉันโดยไม่รู้ตัว คำตอบทั้งหมดของฉันควรดูไม่ได้$$ $pid
bahamat

@bahamat: ตอนนี้คำถามชัดเจนขึ้นหรือไม่? (BTW ฉันไม่เห็นความคิดเห็นของคุณถ้าคุณใช้“@Gilles” ฉันเพิ่งเกิดขึ้นเพื่อดูการแก้ไขของคุณและมาดู.)
กิลส์

7

นี่เป็นโปรแกรมเล็ก ๆ ที่ฉันเขียนใน C:

การใช้งาน:

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

โปรแกรมใช้ / proc / $ pid / maps เพื่อค้นหาขอบเขตหน่วยความจำที่แมปทั้งหมดของกระบวนการและจากนั้นอ่านภูมิภาคเหล่านั้นจาก / proc / $ pid / mem ครั้งละหนึ่งหน้า หน้าเหล่านั้นจะถูกเขียนไปยัง stdout หรือที่อยู่ IP และพอร์ต TCP ที่คุณระบุ

รหัส (ทดสอบบน Android ต้องใช้สิทธิ์ superuser):

#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 เท่านั้นดังนั้นฉันแค่ต้องการยืนยันว่ามันใช้งานได้ดีบน Linux 4.4.0-28 x86_64 ตามที่คุณคาดหวัง
เด็กชายแอพพริคอต

ฉันได้รับข้อมูลมากมายเช่น / @ 8 l / @ lบน stdout ที่ไม่สิ้นสุดความคิดใด ๆ ว่าทำไม? รวบรวมบน 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)
Tal Aloni
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.