/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 pseudosouboru je stejný jako bajt na adrese x v průběhu. Pokud není adresa v procesu namapována, čtení z odpovídajícího offsetu v souboru vrátí EIO
(Chyba vstupu/výstupu). Například, protože první stránka v procesu není nikdy mapována (takže dereferencování NULL
ukazatel selže čistě, místo aby neúmyslně přistupoval ke skutečné paměti), čte první bajt z /proc/$pid/mem
vždy způsobí chybu I/O.
Způsob, jak zjistit, jaké části paměti procesu 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 oblasti (adresy prvního bajtu a bajtu za posledním, v hexa). Další sloupec obsahuje oprávnění, pak jsou zde nějaké informace o souboru (offset, zařízení, inode a název), pokud se jedná o mapování souboru. Viz proc(5)
man page nebo Understanding Linux /proc/id/maps, kde najdete další informace.
Zde je skript proof-of-concept, 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", 'rb', 0)
output_file = open("self.dump", 'wb')
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
output_file.write(chunk) # dump contents to standard output
maps_file.close()
mem_file.close()
output_file.close()
/proc/$pid/mem
[Následuje pro historické zajímavosti. Neplatí pro aktuální jádra.]
Od verze 3.3 jádra máte přístup k /proc/$pid/mem
normálně, pokud k němu přistupujete pouze s namapovanými offsety a máte oprávnění je sledovat (stejná oprávnění jako ptrace
pro přístup pouze pro čtení). Ale ve starších jádrech se objevily další komplikace.
Pokud se pokusíte číst od mem
pseudo-soubor jiného procesu, nefunguje to:dostanete ESRCH
(Žádný takový proces) chyba.
Oprávnění na /proc/$pid/mem
(r--------
) jsou liberálnější, než by mělo být. Například byste neměli být schopni číst paměť setuid procesu. Kromě toho pokus o načtení paměti procesu, zatímco proces ji upravuje, mohl čtenáři poskytnout nekonzistentní pohled na paměť, a co je horší, existovaly rasové podmínky, které mohly sledovat starší verze jádra Linuxu (podle tohoto vlákna lkml, i když jsem neznám podrobnosti). Jsou tedy nutné další kontroly:
- Proces, který chce číst z
/proc/$pid/mem
musí se připojit k procesu pomocíptrace
sPTRACE_ATTACH
vlajka. To je to, co debuggery dělají, když začnou ladit proces; je to také to, costrace
provádí systémová volání procesu. Jakmile čtečka dokončí čtení z/proc/$pid/mem
, měl by se odpojit volánímptrace
sPTRACE_DETACH
vlajka. - Pozorovaný proces nesmí běžet. Normálně volá
ptrace(PTRACE_ATTACH, …)
zastaví cílový proces (odešleSTOP
signál), ale je zde podmínka závodu (doručování signálu je asynchronní), takže sledovač by měl zavolatwait
(jak je zdokumentováno vptrace(2)
).
Proces běžící jako root může číst paměť libovolného procesu, aniž by musel volat ptrace
, ale pozorovaný proces musí být zastaven, jinak bude čtení stále vracet ESRCH
.
Ve zdrojovém kódu linuxového jádra kód poskytující položky pro jednotlivé procesy v /proc
je v fs/proc/base.c
a funkce ke čtení z /proc/$pid/mem
je mem_read
. Dodatečnou kontrolu provádí check_mem_permission
.
Zde je ukázkový kód C, který lze připojit k procesu a přečíst jeho část mem
soubor (kontrola chyb 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);
Již jsem zveřejnil skript proof-of-concept pro dumping /proc/$pid/mem
v jiném vlákně.
Tento příkaz (z gdb) spolehlivě vypíše paměť:
gcore pid
Výpisy mohou být velké, použijte -o outfile
pokud váš aktuální adresář nemá dostatek místa.
Když spustíte cat /proc/$$/mem
proměnnou $$
je vyhodnocován pomocí bash, který vkládá vlastní pid. Poté provede cat
který má jiný pid. Skončíte s cat
pokoušíte se přečíst paměť bash
, jeho nadřazený proces. Protože 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 $$
vyhodnotí na 17823. Podívejme se, který 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 aktuální shell.
$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process
Zde opět $$
vyhodnotí na 17823, což je můj shell. cat
nemohu číst paměťový prostor mého shellu.