LaunchAgents und LaunchDaemons: Persistenz unter macOS
launchd-Plists in LaunchAgents und LaunchDaemons legen fest, was macOS beim Booten, bei der Anmeldung oder nach Zeitplan startet: Persistenzort Nummer eins.
- Speicherort
- ~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons
- Belegt
- Welcher Code automatisch startet, als welcher Benutzer, bei welchem Auslöser und seit wann
- Zeitstempel
- Keine internen Zeitstempel; APFS-Dateizeiten (Nanosekunden, UTC)
- Zugriff
- Benutzer-Agents: besitzender Benutzer; /Library: root; FDA für das Sicherungstool empfohlen
- Aufbewahrung
- Bis die Plist gelöscht wird; Overrides und BTM-Einträge können sie überdauern
- Sicherung
- Aftermath, mac_apt, UAC, Velociraptor
- mac_aptCLI · Open Source
- PlasoCLI · Open Source
- KnockKnockGUI · Open Source
- plutilCLI · in macOS enthalten
Was es ist
launchd ist unter macOS PID 1. Er startet System-Daemons beim Booten und Benutzer-Agents bei der Anmeldung anhand von Job-Definitionen, die als Property Lists gespeichert sind. Eine Job-Plist nennt ein Label, das auszuführende Programm und die Auslöser (beim Laden, dauerhaft am Leben halten, Intervall, Kalender, Änderungen an Dateipfaden). Legitime Software und die meiste macOS-Malware nutzen genau denselben Mechanismus. Deshalb sind diese Ordner die erste Anlaufstelle bei jeder Prüfung auf Persistenz.
Daemons laufen im Systemkontext (als root, sofern UserName nichts anderes angibt) ohne angemeldeten Benutzer. Agents laufen innerhalb der Sitzung eines Benutzers.
Wo es liegt
| Pfad | Typ | Geschrieben von | macOS / Schutz |
|---|---|---|---|
~/Library/LaunchAgents | Agent pro Benutzer | Der Benutzer, keine Admin-Rechte nötig | Alle Versionen |
/Library/LaunchAgents | Agent für jeden Benutzer | Administrator (root) | Alle Versionen |
/Library/LaunchDaemons | System-Daemon | Administrator (root) | Alle Versionen |
/System/Library/LaunchAgents, /System/Library/LaunchDaemons | Apple-Jobs | Apple | Versiegeltes Systemvolume ab 11 Big Sur |
/Library/Apple/System/Library/LaunchAgents, .../LaunchDaemons | Apple-Jobs auf dem Data-Volume | Apple | Durch SIP geschützt |
<App>.app/Contents/Library/LaunchAgents, .../LaunchDaemons | In Apps gebündelte Jobs, registriert mit SMAppService | Entwickler | Ab 13 Ventura |
/private/var/db/com.apple.xpc.launchd/disabled.plist, disabled.<UID>.plist | Enable/Disable-Overrides von launchd | launchctl enable/disable | root |
/private/var/db/com.apple.xpc.launchd/loginitems.<UID>.plist | Anmeldeobjekte des Service Management | SMLoginItemSetEnabled | root |
/Library/StartupItems | Alte Startobjekte | Administrator | Veralteter Mechanismus |
Prüfen Sie außerdem /var/root/Library/LaunchAgents und jedes Home-Verzeichnis unter /Users.
Was es belegt
- Dass ein Programm so konfiguriert war, dass es beim Booten, bei der Anmeldung, in einem Intervall, nach Kalenderplan, bei Dateiänderungen oder beim Einhängen eines Volumes startet.
- Das Konto, unter dem der Job läuft (ein Daemon ohne
UserNamebedeutet root). - Das Schreiben nach
/Library/LaunchDaemonserforderte root, ein bösartiger Daemon impliziert also eine Rechteausweitung. - Mit dem Laufzeitzustand (
launchctl print): ob der Job geladen ist, seine PID, die Anzahl der Läufe und der letzte Exit-Status.
Eine Plist allein belegt nicht, dass der Job je lief: Er kann durch ein Override deaktiviert sein, nicht laden oder auf ein fehlendes Binärprogramm zeigen. Untermauern Sie das mit Logs und Prozessbelegen.
Wichtige Felder
Aus launchd.plist(5):
| Schlüssel | Bedeutung |
|---|---|
Label | Eindeutiger Job-Name (Pflicht) |
Program / ProgramArguments | Ausführbare Datei und Argumentvektor |
BundleProgram | Ausführbare Datei relativ zur App, nur in SMAppService-Plists |
RunAtLoad | Start beim Laden (Boot oder Anmeldung) |
KeepAlive | Neustart nach Beenden oder bedingtes Dictionary |
StartInterval / StartCalendarInterval | Periodischer oder geplanter Start |
WatchPaths / QueueDirectories / StartOnMount | Auslöser im Dateisystem |
UserName / GroupName | Ausführungsidentität |
EnvironmentVariables | Umgebung (auf DYLD_INSERT_LIBRARIES achten) |
StandardOutPath / StandardErrorPath | Ausgabedateien, oft nützliche Belege |
LimitLoadToSessionType | Sitzungstypen für Agents (z. B. Aqua) |
AssociatedBundleIdentifiers | App, die für diesen Job in den Einstellungen für Anmeldeobjekte angezeigt wird |
Disabled | Standardzustand; der Zustand von launchctl enable/disable wird extern gespeichert |
Zeitstempel
Plists haben kein eigenes Erstellungsdatum. Nutzen Sie das Dateisystem: APFS speichert Erstellungs-, Änderungs-, Statusänderungs- und Zugriffszeiten mit Nanosekundengenauigkeit in UTC.
stat -f '%SB | %Sm | %N' -t '%Y-%m-%d %H:%M:%S' ~/Library/LaunchAgents/*.plist
Die Erstellungszeit ist meist der beste Anker für „installiert am“, kann aber mit touch oder durch Kopierwerkzeuge verändert werden. Prüfen Sie die Zeiten des referenzierten Binärprogramms, FSEvents für den Ordner und den Eintrag im Background Task Management.
Aufbewahrung
Ein Job bleibt bestehen, bis seine Plist entfernt wird. Das Entfernen löscht nicht alles: Overrides in disabled*.plist, Einträge im Background Task Management, Einträge im Unified Log, FSEvents, lokale APFS-Snapshots und Time-Machine-Backups können Spuren bewahren.
Sicherung
# 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.yamlsichert die Standardordner für Agents und Daemons (pro Benutzer,/Library,/System/Libraryund/Library/Apple/System/Library, jedoch nicht/var/rootoder in Apps gebündelte Jobs),/Library/StartupItemsundloginitems.*.plist;live_response/system/launchctl.yamlführtlaunchctl listaus. - Aftermath sichert Launch Agents und Daemons als Teil seiner Persistenzobjekte.
- Führen Sie niemals
launchctl bootout,unloadoderdisableaus, bevor Sie dokumentiert haben.
Auswertung
- plutil:
plutil -p <file>gibt XML- oder Binär-Plists aus;plutil -lintmarkiert fehlerhafte. - Plugin
AUTOSTARTvon mac_apt:python mac_apt.py -o out E01 mac.E01 AUTOSTART. - Plugin
launchd_plistvon Plaso:log2timeline.py --parsers 'plist/launchd_plist' --storage-file ld.plaso ./case. - KnockKnock (objective-see/KnockKnock) listet persistente Objekte auf einem Live-Mac mit Signaturstatus auf.
Schnelle Sichtung des Programms jedes Jobs:
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
Tipps für die Untersuchung
- Ein Label
com.apple.*außerhalb von/Systemund/Library/Appleist ein Warnsignal: Apple-Jobs liegen nicht in/Library/LaunchDaemons. - Beginnt
ProgramArgumentsmit/bin/sh,/bin/bash,/usr/bin/python3oder/usr/bin/osascript, ist der Interpreter von Apple signiert; das Skript-Argument ist die eigentliche Nutzlast. - Hashen und prüfen Sie jedes Ziel:
codesign -dv --verbose=4,spctl --assess -vv,xattr -lfür die Quarantäne. - Ziele in versteckten Ordnern,
/tmp,/Users/Sharedoder Namen, die nach Herstellern klingen, unter~/Library/Application Supportverdienen Vorrang. - Ein Job, den
launchctl printals geladen zeigt, der aber keine Plist auf der Platte hat, bedeutet, dass die Datei nach dem Laden entfernt wurde. - Vergleichen Sie mit dem Standard-Build der Organisation: Updater, VPN, MDM und Backup-Agents sind normales Grundrauschen.
- Prüfen Sie auch cron und periodic und lesen Sie den Leitfaden zur launchd-Persistenz.