Detect Launch Agent in Splunk
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.
MITRE ATT&CK
- Tactic
- Persistence Privilege Escalation
- Technique
- T1543 Create or Modify System Process
- Sub-technique
- T1543.001 Launch Agent
- Canonical reference
- https://attack.mitre.org/techniques/T1543/001/
SPL Detection Query
index=osquery sourcetype=osquery_differential name=launchd
| where action="added"
| eval plist_path=coalesce('columns.path', spath(_raw, "columns.path"))
| eval agent_label=coalesce('columns.label', spath(_raw, "columns.label"), "unknown")
| eval program_args=coalesce('columns.program_arguments', 'columns.program', spath(_raw, "columns.program_arguments"), "unknown")
| eval run_at_load=coalesce('columns.run_at_load', "0")
| eval keep_alive=coalesce('columns.keep_alive', "0")
| eval on_demand=coalesce('columns.on_demand', "0")
| where match(plist_path, "(?i)/Library/LaunchAgents/")
| eval AgentScope=case(
match(plist_path, "(?i)^/System/Library/LaunchAgents/"), "System",
match(plist_path, "(?i)^/Library/LaunchAgents/"), "Global",
1=1, "User"
)
| eval SuspiciousProgram=if(
match(program_args, "(?i)(\\bbash\\b|\\bsh\\b|\\bzsh\\b|python3?|\\bruby\\b|\\bperl\\b|\\bcurl\\b|\\bwget\\b|osascript|\\bnode\\b|/tmp/|/var/tmp/|/var/folders/)"),
1, 0
)
| eval AppleNameSpoof=if(
match(plist_path, "com\.apple\.[a-z]+\.[a-z]+\.plist") AND NOT match(plist_path, "(?i)^/System/Library/"),
1, 0
)
| eval RandomName=if(
match(plist_path, "com\.[a-z0-9]{4,10}\.[a-z0-9]{4,10}\.plist") AND NOT match(plist_path, "(com\.apple\.|com\.microsoft\.|com\.adobe\.|com\.google\.)"),
1, 0
)
| eval PersistenceEnabled=if(run_at_load="1" OR keep_alive="1", 1, 0)
| eval RiskScore=SuspiciousProgram + AppleNameSpoof + RandomName + PersistenceEnabled
| where RiskScore > 0
| table _time, host, plist_path, agent_label, program_args, AgentScope, run_at_load, keep_alive, SuspiciousProgram, AppleNameSpoof, RandomName, PersistenceEnabled, RiskScore
| sort - RiskScore, - _time Detects macOS Launch Agent persistence using osquery's launchd table via Splunk. Queries osquery_differential for newly added launchd entries in LaunchAgent directories, then evaluates each entry against four risk indicators: (1) SuspiciousProgram — program_arguments pointing to shells, interpreters, or paths in world-writable directories; (2) AppleNameSpoof — plist name matching Apple naming convention from a non-system path; (3) RandomName — algorithmically generated label patterns matching Dok/CoinTicker malware conventions; (4) PersistenceEnabled — RunAtLoad or KeepAlive set to true. Cumulative RiskScore prioritizes analyst review. Requires osquery deployed on macOS endpoints via Fleet, Kolide, or standalone with Splunk Universal Forwarder.
Data Sources
Required Sourcetypes
False Positives & Tuning
- Legitimate software installers creating Launch Agents during user-initiated installation of commercial applications — verify by correlating with recent Finder/installer activity and checking the publisher signature of the target binary
- Enterprise MDM tooling (Jamf, Mosyle, Kandji) deploying launch agents as part of configuration management — these should match approved MDM policy definitions
- Homebrew services (brew services start) creating launch agents for development dependencies like postgresql, redis, nginx — validate by checking if the binary path is under /usr/local/Cellar or /opt/homebrew
- Developer toolchain components (rbenv, pyenv, nvm version managers) registering shim helpers via launch agents during environment setup
- Endpoint security products and VPN clients writing their own helper agents during initial onboarding — these should match known-good hashes from vendor documentation
Other platforms for T1543.001
Testing Methodology
Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Create Persistent Launch Agent via Bash
Expected signal: DeviceFileEvents: FileCreated action for file 'com.df00tech.atomictest.plist' in '~/Library/LaunchAgents/', InitiatingProcessFileName='bash'. osquery_differential: action='added' in launchd table with label='com.df00tech.atomictest', run_at_load='1', program_arguments='/bin/bash -c date...'.
- Test 2Load Launch Agent with launchctl
Expected signal: DeviceProcessEvents: FileName='launchctl', ProcessCommandLine contains 'load' and 'LaunchAgents/com.df00tech.launchctltest.plist', InitiatingProcessFileName='bash'. DeviceFileEvents: FileCreated for the plist in LaunchAgents directory.
- Test 3Apple Name Spoof Launch Agent (MacMa/Green Lambert Pattern)
Expected signal: DeviceFileEvents: FileCreated for 'com.apple.softwareupdate.helper.plist' in ~/Library/LaunchAgents/, InitiatingProcessFileName='bash'. osquery_differential: action='added', label='com.apple.softwareupdate.helper', run_at_load='1', keep_alive='1', path contains '/Users/<user>/Library/LaunchAgents/'.
- Test 4KeepAlive Launch Agent with Randomly Named Plist (Dok/CoinTicker Pattern)
Expected signal: DeviceFileEvents: FileCreated for 'com.<rand1>.<rand2>.plist' in ~/Library/LaunchAgents/, InitiatingProcessFileName='bash'. osquery_differential: action='added', label='com.<rand1>.<rand2>', run_at_load='1', keep_alive='1'.
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.