Volcado de la memoria usada por un proceso usando Bash
En este artículo se muestran unas útiles funciones en bash que permiten hacer volcados de memoria de procesos usando “dd” y “/proc/PID/maps”. Obteniendo todo lo que el proceso tiene mapeado en la memoria RAM. /proc/PID/maps hace referencia al espacio de direcciones virtual (VAS) de un proceso, contiendo las regiones de memoria actualmente asignadas. El comando pmap muestra la misma salida e incluso permite obtener información extra sobre el uso de bytes en memoria, apoyándose para ello en “/proc/PID/smaps”. Esto permite mostrar por ejemplo la cantidad total de bytes ocupados en memoria, distinguir qué parte está en la memoria swap, que cantidad es compartida con otros procesos, qué cantidad de bytes pertenecen a páginas no modificadas, etc. Antes de presentar las funciones en Bash veamos unos conceptos básicos sobre memoria virtual.
La memoria virtual permite a cada proceso reclamar la memoria física disponible que está presente en la máquina. Es decir, cada proceso actúa como si fuera el único proceso que se ejecuta en el sistema operativo. Este enfoque tiene muchas ventajas, por ejemplo, ofrece una experiencia mucho mejor para el desarrollador, ya que simplifica las nuevas solicitudes de memoria. Además, mejora la seguridad, ya que aísla los procesos para que no puedan interferir entre sí. Por último, aumenta el rendimiento porque cuando se produce un error, sólo afecta a un único proceso sin añadir sobrecarga al resto de procesos. Por otro lado si la cantidad total de memoria virtual excede la memoria real disponible (incluyendo el espacio de intercambio) se ejecutará el famoso “out-of-memory killer”.
Espacio de direcciones virtual (VAS) de un proceso consiste en todas las direcciones de memoria disponibles a las que un proceso puede referirse. Se trata de la memoria virtual, que es aproximadamente igual a la memoria física total que está instalada en la computadora. Esta está dividida en dos regiones, las direcciones del espacio del kernel y las del despacio de usuario. Las direcciones virtuales del espacio del kernel son referidas por los procesos que pasan del modo usuario al modo kernel. Por otro lado las direcciones virtuales del espacio de usuario contienen diferentes tipos de segmentos que contienen el código, los datos y las dependencias de los procesos.
Los segmentos del espacio de direcciones virtual de usuario (VAS), se conocen mapeos, son bloques contiguos de memoria cuyo contenido depende del tipo de segmento. Veamos los diferentes tipos:
- Segmentos de código (o segmento de texto), que contienen código ejecutable
- Segmentos de datos, que contienen datos de proceso como variables y constantes. Se dividen en segmentos de datos inicializados, segmentos de datos no inicializados y segmentos heap.
- Segmentos de pila, que pueden expandirse dinámicamente y contienen los parámetros de las funciones y las variables locales de las funciones
- Segmentos de bibliotecas compartidas, que contienen bibliotecas enlazadas (compartidas) que el proceso utiliza
cat /proc/18923/maps 55ddd3e14000-55ddd3e16000 r--p 00000000 08:01 805987379 /usr/bin/sleep 55ddd3e16000-55ddd3e19000 r-xp 00002000 08:01 805987379 /usr/bin/sleep 55ddd3e19000-55ddd3e1a000 r--p 00005000 08:01 805987379 /usr/bin/sleep 55ddd3e1b000-55ddd3e1c000 r--p 00006000 08:01 805987379 /usr/bin/sleep 55ddd3e1c000-55ddd3e1d000 rw-p 00007000 08:01 805987379 /usr/bin/sleep 55ddd4b2b000-55ddd4b4c000 rw-p 00000000 00:00 0 [heap] 7f628dc00000-7f628dee9000 r--p 00000000 08:01 268699512 /usr/lib/locale/locale-archive 7f628e07a000-7f628e07d000 rw-p 00000000 00:00 0 7f628e07d000-7f628e09f000 r--p 00000000 08:01 537347961 /usr/lib/libc.so.6 7f628e09f000-7f628e1fa000 r-xp 00022000 08:01 537347961 /usr/lib/libc.so.6 7f628e1fa000-7f628e251000 r--p 0017d000 08:01 537347961 /usr/lib/libc.so.6 7f628e251000-7f628e255000 r--p 001d4000 08:01 537347961 /usr/lib/libc.so.6 7f628e255000-7f628e257000 rw-p 001d8000 08:01 537347961 /usr/lib/libc.so.6 7f628e257000-7f628e264000 rw-p 00000000 00:00 0 7f628e27c000-7f628e27e000 rw-p 00000000 00:00 0 7f628e27e000-7f628e27f000 r--p 00000000 08:01 537360952 /usr/lib/ld-linux-x86-64.so.2 7f628e27f000-7f628e2a5000 r-xp 00001000 08:01 537360952 /usr/lib/ld-linux-x86-64.so.2 7f628e2a5000-7f628e2af000 r--p 00027000 08:01 537360952 /usr/lib/ld-linux-x86-64.so.2 7f628e2af000-7f628e2b1000 r--p 00031000 08:01 537360952 /usr/lib/ld-linux-x86-64.so.2 7f628e2b1000-7f628e2b3000 rw-p 00033000 08:01 537360952 /usr/lib/ld-linux-x86-64.so.2 7ffcf746a000-7ffcf748b000 rw-p 00000000 00:00 0 [stack] 7ffcf7549000-7ffcf754d000 r--p 00000000 00:00 0 [vvar] 7ffcf754d000-7ffcf754f000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
- Dirección inicial y dirección final del mapeo: 55ddd3e14000-55ddd3e16000 : La salida del comando cat se ordena en base a esas direcciones, de menor a mayor.
- Mode (permisos): r–p : Especifica qué acciones (lectura, escritura y ejecución) están disponibles en este mapeo y si es privado o compartido por varios procesos. Los permisos nos suelen indicar pistas de qué tipo de datos suelen estar mapeados. Por ejemplo si el segmento mapeado permite la modificación de datos es donde se almacenen variables. Si tiene permiso de ejecución es donde posiblemente esté el código ejecutable. Si hay permisos de solo lectura se puede tratar de variables globales o constantes. etc.
- Offset: 00002000 : Es el desplazamiento inicial en bytes dentro del archivo que se mapea. Esto sólo es visible para los mapeos de archivos, de no ser así el offset es 0.
- Los ids mayor:menor: 08:01 : Estos IDs representan el dispositivo en el que está el archivo mapeado en forma de id mayor y menor. En el ejemplo anterior, 08:01 representa el id mayor y menor del dispositivo que contiene el archivo insertado. Para las asignaciones que no son de archivos, esta columna muestra 00:00.
- ID de inodo del archivo mapeado: 805987379 : Sólo es válido para mapeos de archivos (ls -i /usr/bin/sleep daría como resultado80598737).
- La ruta del archivo para ese mapeo: En el caso de que no se trate de un mapeo de archivos, este campo está vacío.
A continuación se muestra la función en bash para realizar el volcado de memoria de un proceso sin incluir las librerías compartidas o ficheros mapeados a disco. Para ello usamos las entradas del fichero maps con el valor de inodo “ 0 ”, que indica que no hay ningún inodo asociado a la región de memoria. A esa memoria sin inodo y por tanto sin ruta a fichero se denomina “anónima”. Una vez obtenidas las direcciones, se obtienen todas las regiones y mediante dd se extraen en el directorio donde se ejecute el comando. Los ficheros tendrán el prefijo del PID_mem_ y posteriormente la región de memoria y la extensión .bin. Para leer dichos ficheros binarios se puede usar xxd o el comando strings. Si se quiere obtener el volcado incluyendo librerías compartidas y ficheros mapeados simplemente habría que quitar del código grep “ 0 ”. Pero hay que tener en cuenta que esto arrojaría excesiva información, en su mayoría irrelevante y generaría volcados de memoria excesivamente grandes en muchos casos.
Si se quiere pausar el proceso previamente antes de hacer el volcado pueden usarse los siguientes comandos.
kill -SIGSTOP PID # Congela el proceso. kill -SIGCONT PID # Descongela el proceso.
Volcado de memoria de un PID en concreto.
procdump() ( cat /proc/$1/maps | grep " 0 " | awk '{print $1}' | ( IFS="-" while read a b; do dd if=/proc/$1/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="$1_mem_$a.bin" done ) )
# Forma de uso. procdump 3456
Volcado de memoria de múltiples PIDs usando su nombre (basado en pidof).
procdump() ( for g in `pidof $1` do cat /proc/${g[@]}/maps | grep " 0 " | awk '{print $1}' | ( IFS="-" while read a b; do dd if=/proc/${g[@]}/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="${g[@]}_mem_$a.bin" done ) done )
# Forma de uso.
procdump nginx
A diferencia del anterior ejemplo que se limita a un solo PID, esta variante es útil con aplicaciones que suelen tener varios procesos, como Apache, Nginx, PHP, etc.
pidof nginx 107537 107536 1007 ps aux| grep -i nginx root 1007 0.0 0.4 23900 8852 ? Ss Jul07 0:00 nginx: master process /usr/bin/nginx -g pid /run/nginx.pid; error_log stderr; http 107536 0.0 0.4 24140 9592 ? S Aug01 0:02 nginx: worker process http 107537 0.0 0.5 24140 10364 ? S Aug01 1:17 nginx: worker process
Volcado de memoria de múltiples procesos hijo usando su PPID.
Esta variante es muy similar a la anterior, solo que ahora se usa “pgrep” para obtener los procesos hijos y también hace un volcado de ellos. Es útil en situaciones de hackeo de aplicaciones web. Por ejemplo una instancia Nginx ha sido vulnerada y se están ejecutando comandos. El volcado se realizará de todos los procesos hijos de Nginx (los denominados workers) y también el de todos los procesos hijos de dichos procesos. De esta manera, si un atacante está ejecutando comandos del sistema a partir del proceso Nginx (usando el usuario de Nginx), como puede ser una shell reversa con nc al exterior, se realizará también un volcado de la memoria de dicho comando nc.
procdump() ( # Parent process cat /proc/$1/maps | grep " 0 " | awk '{print $1}' | ( IFS="-" while read a b; do dd if=/proc/$1/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="$1_mem_$a.bin" done ) # Child processes for g in `pgrep -P $1 | xargs` do cat /proc/${g[@]}/maps | grep " 0 " | awk '{print $1}' | ( IFS="-" while read a b; do dd if=/proc/${g[@]}/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="${g[@]}_mem_$a.bin" done ) # If the child process in turn has other child processes, it also performs the dump if [[ $(pgrep -P ${g[@]} | xargs) ]]; then for h in `pgrep -P ${g[@]} | xargs` do cat /proc/${h[@]}/maps | grep " 0 " | awk '{print $1}' | ( IFS="-" while read a b; do dd if=/proc/${h[@]}/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="${g[@]}_${h[@]}_mem_$a.bin" done ) done fi done )
En este caso los ficheros pertenecientes al proceso hijo tiene la nomenclatura PPID_PID_mem_ y posteriormente la región de memoria y la extensión .bin. Para usarse, debe buscarse primeramente cual es el PID que nos interesa. En el caso de Nginx, sería el proceso con el usuario root que inicializa el resto de procesos hijos, en el ejemplo anterior sería el proceso 1007.
# Forma de uso. procdump 1007
El comando gcore puede crear también un dump de todas las asignaciones de memoria de un proceso.
# Ejeplo con el PID de tor 27241 gcore -a -o Tor_memory_dump 27241
NOTA: EL comando kill bajo ciertas circunstancias también podría crear volcados de memoria. Por ejemplo no funciona con binarios setuid/gid y algunos binarios pueden ignorar la señal SIGSEGV.
Enlaces de interés:
- memdump.py Implementación en Python pero incluyendo librerías compartidas y cualquier otro fichero mapeado en memoria por el proceso.
- ProcDump-for-Linux Permite hacer volcados de procesos directamente o bien programarlos en base a tiempo, umbrales de CPU, numero de descriptores de ficheros, arranque de determinados procesos, etc.