Il proc(5)
di Linux la pagina man mi dice che /proc/$pid/mem
“può essere utilizzato per accedere alle pagine della memoria di un processo”. Ma un semplice tentativo di usarlo mi dà solo
$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error
Perché cat
non è in grado di stampare la propria memoria (/proc/self/mem
)? E qual è questo strano errore "nessun processo del genere" quando provo a stampare la memoria della shell (/proc/$$/mem
, ovviamente il processo esiste)? Come posso leggere da /proc/$pid/mem
, allora?
Risposta accettata:
/proc/$pid/maps
/proc/$pid/mem
mostra il contenuto della memoria di $pid mappato allo stesso modo del processo, ovvero il byte all'offset x nello pseudo-file è lo stesso del byte all'indirizzo x nel processo. Se un indirizzo non è mappato nel processo, la lettura dall'offset corrispondente nel file restituisce EIO
(Errore ingresso/uscita). Ad esempio, poiché la prima pagina in un processo non viene mai mappata (quindi dereferenziando un NULL
il puntatore non riesce ad accedere in modo involontario alla memoria effettiva), leggendo il primo byte di /proc/$pid/mem
genera sempre un errore di I/O.
Il modo per scoprire quali parti della memoria di processo sono mappate è leggere /proc/$pid/maps
. Questo file contiene una riga per regione mappata, simile a questa:
08048000-08054000 r-xp 00000000 08:01 828061 /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap]
I primi due numeri sono i confini della regione (indirizzi del primo byte e del byte dopo l'ultimo, in esadecimale). La colonna successiva contiene le autorizzazioni, quindi ci sono alcune informazioni sul file (offset, dispositivo, inode e nome) se si tratta di una mappatura del file. Vedi il proc(5)
man page o Capire Linux /proc/id/maps per maggiori informazioni.
Ecco uno script proof-of-concept che scarica il contenuto della propria memoria.
#! /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
[Quello che segue è di interesse storico. Non si applica ai kernel attuali.]
Dalla versione 3.3 del kernel, puoi accedere a /proc/$pid/mem
normalmente fintanto che accedi, accedi solo agli offset mappati e hai il permesso di tracciarlo (stesse autorizzazioni di ptrace
per l'accesso in sola lettura). Ma nei kernel più vecchi c'erano alcune complicazioni aggiuntive.
Se provi a leggere da mem
pseudo-file di un altro processo, non funziona:ottieni un ESRCH
Errore (nessun processo del genere).
I permessi su /proc/$pid/mem
(r--------
) sono più liberali di quanto dovrebbe essere. Ad esempio, non dovresti essere in grado di leggere la memoria di un processo setuidico. Inoltre, provare a leggere la memoria di un processo mentre il processo lo sta modificando potrebbe dare al lettore una visione incoerente della memoria e, peggio, c'erano condizioni di gara che potevano tracciare versioni precedenti del kernel Linux (secondo questo thread lkml, anche se ho non conosco i dettagli). Quindi sono necessari ulteriori controlli:
- Il processo che vuole leggere da
/proc/$pid/mem
deve allegare al processo utilizzandoptrace
con ilPTRACE_ATTACH
bandiera. Questo è ciò che fanno i debugger quando iniziano a eseguire il debug di un processo; è anche ciò chestrace
fa alle chiamate di sistema di un processo. Una volta che il lettore ha finito di leggere da/proc/$pid/mem
, dovrebbe staccarsi chiamandoptrace
con ilPTRACE_DETACH
bandiera. - Il processo osservato non deve essere in esecuzione. Normalmente chiamando
ptrace(PTRACE_ATTACH, …)
interromperà il processo di destinazione (invia unSTOP
signal), ma c'è una race condition (l'erogazione del segnale è asincrona), quindi il tracciante dovrebbe chiamarewait
(come documentato inptrace(2)
).
Un processo in esecuzione come root può leggere la memoria di qualsiasi processo, senza dover chiamare ptrace
, ma il processo osservato deve essere interrotto, altrimenti la lettura restituirà ancora ESRCH
.
Nel sorgente del kernel Linux, il codice che fornisce le voci per processo in /proc
è in fs/proc/base.c
e la funzione per leggere da /proc/$pid/mem
è mem_read
. Il controllo aggiuntivo viene eseguito da check_mem_permission
.
Ecco un esempio di codice C da allegare a un processo e leggere un pezzo di mem
file (controllo errori omesso):
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);
Ho già pubblicato uno script proof-of-concept per il dump di /proc/$pid/mem
su un altro thread.