Launch Agent
Adversaries may create or modify launch agents to repeatedly execute malicious payloads as part of persistence on macOS. When a user logs in, a per-user launchd process loads parameters for each launch-on-demand user agent from property list (.plist) files in /System/Library/LaunchAgents, /Library/LaunchAgents, and ~/Library/LaunchAgents. Adversaries install Launch Agents by placing a .plist file into these directories with RunAtLoad or KeepAlive keys set to true, ensuring malicious payloads execute at every user login. Launch Agents execute with user-level permissions and are commonly disguised using Apple-like naming conventions (e.g., com.apple.softwareupdate.plist, com.apple.GrowlHelper.plist). This technique is used by Calisto, Proton, MacSpy, CrossRAT, Dok, OceanLotus, ThiefQuest, Dacls, macOS.OSAMiner, InvisibleFerret (Contagious Interview), CoinTicker, and Green Lambert malware families.
let SuspiciousParentProcesses = dynamic(["bash", "sh", "zsh", "python3", "python", "ruby", "perl", "curl", "wget", "osascript", "node", "npm", "installer", "pkgutil", "xattr", "mktemp"]);
// Part 1: .plist file creation or modification in LaunchAgent directories
let PlistFileEvents = DeviceFileEvents
| where Timestamp > ago(24h)
| where FolderPath has "/Library/LaunchAgents/"
| where FileName endswith ".plist"
| where ActionType in ("FileCreated", "FileModified")
| extend AgentScope = case(
FolderPath startswith "/System/Library/LaunchAgents/", "System",
FolderPath startswith "/Library/LaunchAgents/", "Global",
"User"
)
| extend SuspiciousWriter = InitiatingProcessFileName in~ (SuspiciousParentProcesses)
| extend AppleNameSpoof = FileName matches regex @"com\.apple\.[a-z]+\.[a-z]+\.plist"
| extend RandomName = FileName matches regex @"com\.[a-z0-9]{4,10}\.[a-z0-9]{4,10}\.plist" and not (FileName startswith "com.apple." or FileName startswith "com.microsoft." or FileName startswith "com.adobe." or FileName startswith "com.google.")
| project Timestamp, DeviceName, AccountName, DetectionType="PlistWritten",
TargetFile=FileName, TargetPath=FolderPath, AgentScope, SuspiciousWriter, AppleNameSpoof, RandomName,
WriterProcess=InitiatingProcessFileName, WriterCommandLine=InitiatingProcessCommandLine,
WriterFolderPath=InitiatingProcessFolderPath;
// Part 2: launchctl load or bootstrap commands targeting LaunchAgent paths
let LaunchctlEvents = DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName =~ "launchctl"
| where ProcessCommandLine has_any ("load", "bootstrap")
| where ProcessCommandLine has "LaunchAgents" or ProcessCommandLine has ".plist"
| extend AgentScope = case(
ProcessCommandLine has "/System/Library/LaunchAgents", "System",
ProcessCommandLine has "/Library/LaunchAgents", "Global",
"User"
)
| extend SuspiciousWriter = InitiatingProcessFileName in~ (SuspiciousParentProcesses)
| extend AppleNameSpoof = ProcessCommandLine matches regex @"com\.apple\.[a-z]+\.[a-z]+\.plist"
| extend RandomName = ProcessCommandLine matches regex @"com\.[a-z0-9]{4,10}\.[a-z0-9]{4,10}\.plist" and not (ProcessCommandLine has "com.apple." or ProcessCommandLine has "com.microsoft." or ProcessCommandLine has "com.adobe." or ProcessCommandLine has "com.google.")
| project Timestamp, DeviceName, AccountName, DetectionType="LaunchctlLoad",
TargetFile=ProcessCommandLine, TargetPath=ProcessCommandLine, AgentScope, SuspiciousWriter, AppleNameSpoof, RandomName,
WriterProcess=InitiatingProcessFileName, WriterCommandLine=InitiatingProcessCommandLine,
WriterFolderPath=InitiatingProcessFolderPath;
PlistFileEvents
| union LaunchctlEvents
| sort by Timestamp desc Data Sources
Required Tables
False Positives
- Legitimate software installers (Adobe Creative Cloud, Zoom, Dropbox, Google Chrome updater) creating Launch Agents for auto-update or helper functionality during user-initiated installation — these will show installer or package manager as the writer process
- Enterprise MDM solutions (Jamf Pro, Mosyle, Kandji) deploying configuration profiles that include Launch Agent definitions as part of managed device policy enforcement
- Homebrew package manager and casks installing helper services (e.g., postgresql, redis, docker-machine) via brew services — these will show bash or brew as the writer process
- VPN clients (Cisco AnyConnect, Palo Alto GlobalProtect) and endpoint security agents (CrowdStrike Falcon, Carbon Black, SentinelOne) creating helper daemons during initial agent installation
- Developer tools including JetBrains Toolbox, Xcode command line tools, and language version managers (rbenv, pyenv, nvm) registering launch services during setup
References (11)
- https://attack.mitre.org/techniques/T1543/001/
- https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
- https://www.sentinelone.com/blog/how-malware-persists-on-macos/
- https://objective-see.org/blog/blog_0x25.html
- https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1543.001/T1543.001.md
- https://www.trendmicro.com/en_us/research/20/d/new-macos-dacls-rat-backdoor-show-lazarus-apt-targets-macos.html
- https://securelist.com/calisto-trojan-for-macos/86543/
- https://www.checkpoint.com/research/may-i-download-adware-please-new-macos-malware-spreading-via-bundled-software/
- https://www.welivesecurity.com/2016/07/06/new-osxkeydnap-malware-hungry-credentials/
- https://www.zscaler.com/blogs/security-research/contagiousinterview-invisibleferret
- https://github.com/SigmaHQ/sigma/tree/master/rules/macos
Unlock Pro Content
Get the full detection package for T1543.001 including response playbook, investigation guide, and atomic red team tests.