Saltar al contenido

PersistenciaEjecución

LaunchAgents y LaunchDaemons: persistencia en macOS

Los plists de launchd en LaunchAgents y LaunchDaemons definen qué inicia macOS al arrancar, al iniciar sesión o con horario: el principal punto de persistencia.

Ubicación
~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons
Prueba
Qué código está configurado para iniciarse automáticamente, con qué usuario, con qué disparador y desde cuándo
Marcas de tiempo
Sin marcas de tiempo internas; horas de archivo APFS (nanosegundos, UTC)
Acceso
Agentes de usuario: usuario propietario; /Library: root; se recomienda FDA para la herramienta de adquisición
Retención
Hasta que se borra el plist; las anulaciones y los registros BTM pueden sobrevivirle
Adquisición
Aftermath, mac_apt, UAC, Velociraptor

Qué es

launchd es el PID 1 de macOS. Inicia los daemons del sistema al arrancar y los agentes de cada usuario al iniciar sesión, a partir de definiciones de trabajos guardadas como property lists. Un plist de trabajo indica un Label, el programa que se ejecuta y los disparadores (al cargar, mantener vivo, por intervalo, por calendario, por cambios en rutas de archivos). El software legítimo y la mayor parte del malware para macOS usan exactamente el mismo mecanismo, por eso estas carpetas son la primera parada en cualquier revisión de persistencia.

Los daemons se ejecutan en el contexto del sistema (como root salvo que UserName indique otra cosa) sin necesidad de un usuario con sesión iniciada. Los agentes se ejecutan dentro de la sesión de un usuario.

Dónde se encuentra

RutaTipoEscrito pormacOS / protección
~/Library/LaunchAgentsAgente por usuarioEl propio usuario, sin necesidad de administradorTodas las versiones
/Library/LaunchAgentsAgente para todos los usuariosAdministrador (root)Todas las versiones
/Library/LaunchDaemonsDaemon del sistemaAdministrador (root)Todas las versiones
/System/Library/LaunchAgents, /System/Library/LaunchDaemonsTrabajos de AppleAppleVolumen de sistema sellado desde 11 Big Sur
/Library/Apple/System/Library/LaunchAgents, .../LaunchDaemonsTrabajos de Apple en el volumen DataAppleProtegido por SIP
<App>.app/Contents/Library/LaunchAgents, .../LaunchDaemonsTrabajos incluidos en la app y registrados con SMAppServiceDesarrollador13 Ventura+
/private/var/db/com.apple.xpc.launchd/disabled.plist, disabled.<UID>.plistAnulaciones de activación/desactivación de launchdlaunchctl enable/disableroot
/private/var/db/com.apple.xpc.launchd/loginitems.<UID>.plistElementos de inicio de Service ManagementSMLoginItemSetEnabledroot
/Library/StartupItemsElementos de arranque heredadosAdministradorMecanismo obsoleto

Revisa también /var/root/Library/LaunchAgents y cada carpeta personal de /Users.

Qué prueba

  • Que un programa se configuró para iniciarse al arrancar, al iniciar sesión, por intervalo, según un calendario, ante cambios de archivos o al montar un volumen.
  • La cuenta con la que se ejecuta el trabajo (un daemon sin UserName se ejecuta como root).
  • Que escribir en /Library/LaunchDaemons requería root, de modo que un daemon malicioso implica una escalada de privilegios.
  • Con el estado en ejecución (launchctl print), si el trabajo está cargado, su PID, el número de ejecuciones y el último código de salida.

Un plist por sí solo no prueba que el trabajo llegara a ejecutarse: puede estar desactivado por una anulación, fallar al cargarse o apuntar a un binario inexistente. Corrobóralo con logs y evidencias de procesos.

Campos clave

Según launchd.plist(5):

ClaveSignificado
LabelNombre único del trabajo (obligatorio)
Program / ProgramArgumentsEjecutable y vector de argumentos
BundleProgramEjecutable relativo a la app, solo en plists de SMAppService
RunAtLoadIniciar al cargarse (arranque o inicio de sesión)
KeepAliveReiniciar al salir, o diccionario condicional
StartInterval / StartCalendarIntervalInicio periódico o programado
WatchPaths / QueueDirectories / StartOnMountDisparadores del sistema de archivos
UserName / GroupNameIdentidad de ejecución
EnvironmentVariablesEntorno (atención a DYLD_INSERT_LIBRARIES)
StandardOutPath / StandardErrorPathArchivos de salida, a menudo evidencia útil
LimitLoadToSessionTypeTipos de sesión para agentes (p. ej. Aqua)
AssociatedBundleIdentifiersApp que se muestra para este trabajo en los ajustes de Ítems de inicio
DisabledEstado por defecto; el estado de launchctl enable/disable se guarda aparte

Marcas de tiempo

Los plists no llevan fecha de creación propia. Usa el sistema de archivos: APFS almacena las horas de creación, modificación, cambio y acceso con precisión de nanosegundos en UTC.

stat -f '%SB | %Sm | %N' -t '%Y-%m-%d %H:%M:%S' ~/Library/LaunchAgents/*.plist

La hora de creación suele ser la mejor referencia de "instalado el", pero puede alterarse con touch o con herramientas de copia. Revisa las horas del binario referenciado, FSEvents para la carpeta y el registro de Background Task Management.

Retención

Un trabajo persiste hasta que se elimina su plist. La eliminación no lo borra todo: las anulaciones disabled*.plist, los registros de Background Task Management, las entradas del unified log, FSEvents, los snapshots locales de APFS y las copias de Time Machine pueden conservar rastros.

Adquisición

# Dead-box or live (root, Full Disk Access)
sudo ditto /Library/LaunchAgents ./case/la_system
sudo ditto /Library/LaunchDaemons ./case/ld_system
sudo ditto /private/var/db/com.apple.xpc.launchd ./case/launchd_db
for u in /Users/*; do [ -d "$u/Library/LaunchAgents" ] && \
  sudo ditto "$u/Library/LaunchAgents" "./case/la_$(basename "$u")"; done

# Live state (read-only)
sudo launchctl print system > ./case/launchctl_system.txt
launchctl print gui/501 > ./case/launchctl_gui501.txt
sudo launchctl print-disabled system > ./case/disabled_system.txt
  • UAC files/system/startup_items.yaml recopila las carpetas estándar de agentes y daemons (por usuario, /Library, /System/Library y /Library/Apple/System/Library, pero no /var/root ni los trabajos integrados en las apps), /Library/StartupItems y loginitems.*.plist; live_response/system/launchctl.yaml ejecuta launchctl list.
  • Aftermath recopila los launch agents y daemons entre sus elementos de persistencia.
  • No ejecutes nunca launchctl bootout, unload ni disable antes de documentar.

Análisis

  • plutil: plutil -p <file> muestra plists XML o binarios; plutil -lint señala los que están mal formados.
  • Plugin AUTOSTART de mac_apt: python mac_apt.py -o out E01 mac.E01 AUTOSTART.
  • Plugin launchd_plist de Plaso: log2timeline.py --parsers 'plist/launchd_plist' --storage-file ld.plaso ./case.
  • KnockKnock (objective-see/KnockKnock) enumera los elementos persistentes de un Mac en vivo con su estado de firma.

Triaje rápido del programa de cada trabajo:

for f in /Library/Launch*/*.plist ~/Library/LaunchAgents/*.plist; do
  printf '%s\t' "$f"; plutil -extract ProgramArguments json -o - "$f" 2>/dev/null \
    || plutil -extract Program raw -o - "$f"; echo
done

Consejos para la investigación

  • Una etiqueta com.apple.* fuera de /System y /Library/Apple es una señal de alarma: los trabajos de Apple no residen en /Library/LaunchDaemons.
  • Cuando ProgramArguments empieza por /bin/sh, /bin/bash, /usr/bin/python3 o /usr/bin/osascript, el intérprete está firmado por Apple; la carga útil es el script pasado como argumento.
  • Calcula el hash y revisa cada destino: codesign -dv --verbose=4, spctl --assess -vv, xattr -l para la cuarentena.
  • Los destinos en carpetas ocultas, /tmp, /Users/Shared o con nombres que imitan a fabricantes bajo ~/Library/Application Support merecen prioridad.
  • Un trabajo que launchctl print muestra como cargado pero sin plist en disco significa que el archivo se eliminó después de cargarse.
  • Compara con la configuración estándar de la organización: actualizadores, VPN, MDM y agentes de copia de seguridad son ruido normal.
  • Revisa también cron y periodic y consulta la guía de persistencia en launchd.

Ver también