it-swarm-eu.dev

Jak mohu číst z / proc / $ pid / mem pod Linuxem?

Linux proc(5) man stránka mi říká, že /proc/$pid/mem „Lze použít pro přístup na stránky paměti procesu“. Přímý pokus o jeho použití mi však dá jen

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

Proč není cat schopen tisknout svou vlastní paměť (/proc/self/mem)? A jaká je tato podivná chyba „žádný takový proces“, když se pokouším vytisknout Shellovu paměť (/proc/$$/mem, proces samozřejmě existuje)? Jak mohu číst z /proc/$pid/mem, pak?

/proc/$pid/maps

/proc/$pid/mem Zobrazuje obsah paměti $ pid mapované stejným způsobem jako v procesu, tj. Bajt na offsetu x v pseudo -soubor je stejný jako bajt na adrese x v procesu. Pokud je adresa v procesu nezmapována, čtení ze odpovídajícího posunu v souboru vrací EIO (chyba vstupu/výstupu). Například protože první stránka v procesu není nikdy mapována (takže dereferencování ukazatele NULL selže spíše než neúmyslný přístup ke skutečné paměti), čtení prvního bajtu /proc/$pid/mem Vždy přinese I/O chyba.

Způsob, jak zjistit, které části procesní paměti jsou mapovány, je přečíst /proc/$pid/maps. Tento soubor obsahuje jeden řádek na mapovanou oblast a vypadá takto:

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

První dvě čísla jsou hranice regionu (adresy prvního bajtu a bajtu po poslední, v hexa). Následující sloupec obsahuje oprávnění, pak existují nějaké informace o souboru (offset, zařízení, inode a jméno), pokud se jedná o mapování souboru. Více informací naleznete na manuálové stránce proc(5) nebo Porozumění systému Linux/proc/id/maps .

Zde je skript pro ověření konceptu, který vypíše obsah své vlastní paměti.

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

Pokud se pokusíte číst z pseudo souboru mem jiného procesu, nefunguje to: dostanete chybu ESRCH (žádný takový proces).

Oprávnění k /proc/$pid/mem (r--------) Jsou liberálnější, než by tomu mělo být. Například byste neměli být schopni číst paměť procesu setuid. Kromě toho, pokusit se číst paměť procesu, zatímco proces se modifikuje, může dát čtenáři nejednotný pohled na paměť, a co je horší, byly závodní podmínky, které by mohly sledovat starší verze jádra Linuxu (podle tento lkml) vlákno , i když nevím podrobnosti). Jsou tedy nutné další kontroly:

  • Proces, který chce číst z /proc/$pid/mem, Se musí k procesu připojit pomocí ptrace s příznakem PTRACE_ATTACH. To je to, co debuggery dělají, když začnou ladit proces; je to také to, co strace provádí systémové volání procesu. Jakmile čtenář dokončí čtení z /proc/$pid/mem, Měl by se odpojit voláním ptrace s příznakem PTRACE_DETACH.
  • Pozorovaný proces nesmí běžet. Normální volání ptrace(PTRACE_ATTACH, …) zastaví cílový proces (vyšle signál STOP), ale je zde stav rasy (doručení signálu je asynchronní), takže sledovač by měl volat wait (jak je zdokumentováno v ptrace(2) ).

Proces spuštěný jako root může číst paměť libovolného procesu, aniž by bylo nutné volat ptrace, ale pozorovaný proces musí být zastaven, jinak se čtení vrátí ESRCH.

Ve zdroji jádra Linux je kód poskytující položky za proces v /proc V fs/proc/base.c a funkce, kterou je třeba číst z /proc/$pid/mem, Je - mem_read . Další kontrolu provádí check_mem_permission .

Zde je několik ukázkových kódů C, které se mají připojit k procesu a číst část jeho souboru mem (kontrola chyb je vynechána):

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

ž jsem zaúčtoval skript pro ověření konceptu pro dumping /proc/$pid/mem Do jiného vlákna .

Tento příkaz (z gdb) spolehlivě vypíše paměť:

gcore pid

Skládky mohou být velké, použijte -o outfile, pokud váš aktuální adresář nemá dostatek místa.

28
Tobu

Když spustíte cat /proc/$$/mem proměnná $$ je vyhodnoceno bash, který vloží vlastní pid. Potom provede cat, který má jinou pid. Nakonec se cat snaží číst paměť bash, jeho nadřazeného procesu. Jelikož neprivilegované procesy mohou číst pouze svůj vlastní paměťový prostor, jádro to odmítne.

Zde je příklad:

$ echo $$
17823

Všimněte si, že $$ vyhodnocuje na 17823. Uvidíme, jaký proces to je.

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

Je to můj současný Shell.

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

Zde opět $$ vyhodnocuje se do roku 17823, což je moje Shell. cat nedokáže přečíst paměťový prostor mého Shell.

12
bahamat

Tady je malý program, který jsem napsal v C:

Používání:

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

Program používá/proc/$ pid/maps k nalezení všech mapovaných paměťových oblastí procesu a poté tyto oblasti přečte z/proc/$ pid/mem, vždy po jedné stránce. tyto stránky jsou zapsány do stdout nebo IP adresy a TCP port, který jste zadali).

Kód (testováno na Androidu, vyžaduje oprávnění superuživatele):

#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