/proc/$pid/maps
/proc/$pid/mem
pokazuje zawartość pamięci $ pid zmapowanej w taki sam sposób jak w procesie, tzn. bajt o przesunięciu x w pseudopliku jest taki sam jak bajt o adresie x w procesie. Jeśli adres nie jest odwzorowywany w procesie, odczyt z odpowiedniego przesunięcia w pliku zwraca EIO
(błąd wejścia / wyjścia). Na przykład, ponieważ pierwsza strona w procesie nigdy nie jest odwzorowywana (tak, że wyłuskowanie NULL
wskaźnika kończy się niepowodzeniem, zamiast niezamierzonego dostępu do rzeczywistej pamięci), odczytanie pierwszego bajtu /proc/$pid/mem
zawsze powoduje błąd we / wy.
Aby dowiedzieć się, które części pamięci procesu są mapowane, należy przeczytać /proc/$pid/maps
. Ten plik zawiera jedną linię na region odwzorowany, wygląda następująco:
08048000-08054000 r-xp 00000000 08:01 828061 /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap]
Pierwsze dwie liczby to granice regionu (adresy pierwszego bajtu i bajtu po ostatnim, w heksie). Następna kolumna zawiera uprawnienia, a następnie informacje o pliku (przesunięcie, urządzenie, i-węzeł i nazwa), jeśli jest to mapowanie pliku. Aby uzyskać więcej informacji, zobacz proc(5)
stronę podręcznika man lub Understanding Linux / proc / id / maps .
Oto skrypt sprawdzający koncepcję, który zrzuca zawartość własnej pamięci.
#! /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
Jeśli spróbujesz odczytać z mem
pseudopliku innego procesu, to nie działa: pojawia się ESRCH
błąd (Brak takiego procesu).
Uprawnienia do /proc/$pid/mem
( r--------
) są bardziej liberalne niż powinno być. Na przykład nie powinieneś być w stanie odczytać pamięci procesu setuid. Co więcej, próba odczytania pamięci procesu, gdy proces się modyfikuje, może dać czytelnikowi niespójny obraz pamięci, a co gorsza, istnieją warunki wyścigu, które mogą śledzić starsze wersje jądra Linuxa (zgodnie z tym wątkiem lkml , chociaż ja nie znam szczegółów). Potrzebne są więc dodatkowe kontrole:
- Proces, który chce czytać z
/proc/$pid/mem
musi dołączyć do procesu z wykorzystaniem ptrace
z PTRACE_ATTACH
flagą. To właśnie robią debuggery, gdy zaczynają debugować proces; dotyczy to również strace
wywołań systemowych procesu. Gdy czytnik zakończy czytanie /proc/$pid/mem
, powinien się odłączyć, dzwoniąc ptrace
z PTRACE_DETACH
flagą.
- Obserwowany proces nie może być uruchomiony. Zwykle wywołanie
ptrace(PTRACE_ATTACH, …)
zatrzyma proces docelowy (wysyła STOP
sygnał), ale występuje warunek wyścigu (dostarczanie sygnału jest asynchroniczne), więc znacznik powinien wywołać wait
(zgodnie z dokumentacją ptrace(2)
).
Proces działający jako root może odczytywać pamięć dowolnego procesu, bez konieczności wywoływania ptrace
, ale obserwowany proces musi zostać zatrzymany, inaczej odczyt będzie nadal zwracany ESRCH
.
W źródle jądra Linux, kod zapewniając wpisy na procesie w /proc
jest fs/proc/base.c
, a funkcja odczytu z /proc/$pid/mem
jest mem_read
. Dodatkowa kontrola jest wykonywana przez check_mem_permission
.
Oto przykładowy kod C do dołączenia do procesu i odczytania fragmentu jego mem
pliku (pominięto sprawdzanie błędów):
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);
Opublikowałem już skrypt sprawdzający koncepcję, który pozwala zrzucić /proc/$pid/mem
inny wątek .