it-swarm-tr.com

Linux altında / proc / $ pid / mem'den nasıl okurum?

Linux proc(5) adam sayfası bana /proc/$pid/mem “Bir işlemin belleğinin sayfalarına erişmek için kullanılabilir”. Ama bunu kullanmaya yönelik basit bir girişim sadece bana

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

cat neden kendi belleğini yazdıramıyor (/proc/self/mem)? Ve Shell'in belleğini yazdırmaya çalıştığımda bu garip “böyle bir işlem yok” hatası nedir (/proc/$$/mem, açıkçası süreç var)? /proc/$pid/mem, sonra?

/proc/$pid/maps

/proc/$pid/mem, İşlemdeki gibi eşlenen $ pid belleğinin içeriğini, yani sözde ofsetteki bayt x değerini gösterir. -file, işlemdeki x adresindeki bayt ile aynı. İşlemde bir adres eşlenmemişse, dosyadaki ilgili ofsetten okumak EIO (Giriş/çıkış hatası) döndürür. Örneğin, bir işlemdeki ilk sayfa hiçbir zaman eşlenmediği için (böylece bir NULL işaretçisinin kaydı silme işlemi gerçek belleğe yanlışlıkla erişmek yerine temiz bir şekilde başarısız olur), /proc/$pid/mem İlk baytını okumak her zaman bir I verir/O hatası.

İşlem belleğinin hangi bölümlerinin eşlendiğini bulmanın yolu /proc/$pid/maps Okumaktır. Bu dosya, eşlenen bölge başına şu şekilde görünen bir satır içerir:

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

İlk iki sayı, bölgenin sınırlarıdır (ilk bayt ve sondan sonraki bayt adresleri, hexa olarak). Bir sonraki sütun izinleri içerir, ardından bir dosya eşleme ise dosya (ofset, aygıt, inode ve ad) hakkında bazı bilgiler vardır. Daha fazla bilgi için proc(5) kılavuz sayfasına veya Linux/proc/id/maps'i anlama konusuna bakın.

İşte kendi hafızasının içeriğini döken bir kavram kanıtı.

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

Başka bir işlemin mem sözde dosyasından okumaya çalışırsanız, çalışmaz: ESRCH (Böyle bir işlem yok) hatası alırsınız.

/proc/$pid/mem (r--------) İzinleri, olması gerekenden daha liberal. Örneğin, bir setuid işleminin belleğini okuyamamalısınız. Ayrıca, işlem değiştirilirken bir işlemin belleğini okumaya çalışmak okuyucuya belleğin tutarsız bir görünümünü verebilir ve daha da kötüsü, Linux çekirdeğinin eski sürümlerini izleyebilecek yarış koşulları vardı ( bu lkml'ye göre) iplik , ancak ayrıntıları bilmiyorum). Bu nedenle ek kontroller gereklidir:

  • /proc/$pid/mem Öğesinden okumak isteyen işlem, PTRACE_ATTACH Bayrağıyla ptrace kullanarak işleme eklenmelidir. Hata ayıklayıcılar bir işlemde hata ayıklamaya başladığında bunu yapar; ayrıca bir işlemin sistemine strace ne yapar. Okuyucu /proc/$pid/mem Öğesinden okumayı bitirdiğinde, PTRACE_DETACH Bayrağıyla ptrace çağırarak ayrılmalıdır.
  • Gözlenen işlem çalışmamalıdır. Normalde ptrace(PTRACE_ATTACH, …) çağrılması hedef işlemi durduracaktır (STOP sinyali gönderir), ancak bir yarış koşulu vardır (sinyal iletimi eşzamansızdır), bu nedenle izleyici wait çağırmalıdır. ( ptrace(2) belgesinde belirtildiği gibi).

Kök olarak çalışan bir işlem, ptrace çağrılmasına gerek kalmadan herhangi bir işlemin belleğini okuyabilir, ancak gözlemlenen işlem durdurulmalıdır, aksi halde okuma işlemi ESRCH döndürür.

Linux çekirdek kaynağında, /proc İçinde işlem başına giriş sağlayan kod fs/proc/base.c ve /proc/$pid/mem Öğesinden okunacak işlevdedir. mem_read . Ek kontrol check_mem_permission tarafından gerçekleştirilir.

Bir işleme eklemek ve mem dosyasının bir yığınını okumak için bazı örnek C kodu (hata denetimi atlandı):

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

Başka bir iş parçacığına /proc/$pid/mem Dökümü için zaten bir kavram kanıtı komut dosyası gönderdim .

Bu komut (gdb'den) belleği güvenilir bir şekilde döker:

gcore pid

Çöplükler büyük olabilir, -o outfile geçerli dizininizde yeterli alan yoksa.

28
Tobu

cat /proc/$$/mem değişken $$, kendi pidini ekleyen bash tarafından değerlendirilir. Daha sonra farklı bir pideye sahip cat yürütür. Sonunda catbash hafızasının üst süreci olan anıyı okumaya çalışıyorsunuz. Ayrıcalıklı olmayan işlemler yalnızca kendi bellek alanlarını okuyabildiğinden, çekirdek tarafından reddedilir.

İşte bir örnek:

$ echo $$
17823

Bunu not et $$ 17823 olarak değerlendirilir. Bakalım bu hangi süreç.

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

Bu benim şu anki Kabuğum.

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

Tekrar burada $$, benim Kabuğum olan 17823 olarak değerlendirilir. cat Kabuğumun bellek alanını okuyamıyor.

12
bahamat

İşte C dilinde yazdığım küçük bir program:

Kullanımı:

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

Program, işlemin tüm eşlenmiş bellek bölgelerini bulmak için/proc/$ pid/maps komutunu kullanır ve daha sonra bu bölgeleri tek seferde bir sayfa/proc/$ pid/mem'den okur. bu sayfalar stdout'a veya IP adresine ve TCP bağlantı noktası belirttiğinize) yazılır.

Kod (Android'de test edilmiştir, süper kullanıcı izinleri gerektirir):

#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