DFIR artifacts, artifact scope, macOS version applicability, minimum privilege required to establish or modify persistence, false-positive risk, baseline dependence, and review guidance.
| Mechanism | Required privilege | Source of Truth | Signal | Collection / Triage | Trigger | What to review |
|---|---|---|---|---|---|---|
|
Launch Agents
[S1]
| Mixed | Both | High |
Artifact / Path
/System/Library/LaunchAgents/Library/LaunchAgents~/Library/LaunchAgentsResolves to Program / ProgramArguments[0]Referenced MachServices / Sockets if presentEnumerate
launchctl print gui/<uid>find ~/Library/LaunchAgents /Library/LaunchAgents -maxdepth 1 -name '*.plist'Inspect
launchctl print gui/<uid>/<label>launchctl blame gui/<uid>/<label>plutil -p <plist> | User login / GUI session | Parse Label, Program, ProgramArguments, RunAtLoad, KeepAlive, WatchPaths, QueueDirectories, StartCalendarInterval, PathState, Sockets, and MachServices. Use launchctl blame to identify the actual trigger that loaded a service: this distinguishes a job started by RunAtLoad from one started by WatchPaths, Sockets, or MachServices, which matters for hunting on-demand persistence. Prioritize targets outside Apple or vendor-controlled paths and jobs present on disk but absent or odd in live launchctl state. Scope and required privilege vary by path: user-scoped in ~/Library, system-scoped in /Library. |
|
Launch Daemons
[S2]
| Root | Both | High |
Artifact / Path
/System/Library/LaunchDaemons/Library/LaunchDaemonsResolves to Program / ProgramArguments[0]Referenced service endpoints and loaded binary pathEnumerate
launchctl print systemfind /Library/LaunchDaemons -maxdepth 1 -name '*.plist'Inspect
launchctl print system/<label>launchctl blame system/<label>plutil -p <plist> | Boot / pre-login | Very high-value persistence. Review launchd keys such as RunAtLoad, KeepAlive, StartCalendarInterval, and PathState, plus ownership, signer, Team ID, and whether the target binary lives in a user-writable or otherwise weak path. Use launchctl blame on the live service-target to surface the real trigger (RunAtLoad vs Sockets / MachServices / WatchPaths) instead of relying on plist intent alone. |
|
launchd overrides / disabled state
[S3]
| Mixed | State | Medium |
Artifact / Path
Persistent disabled / override stateResolves to Effective enabled / disabled state for a labelFinal live state shown by launchctl, not just plist intentEnumerate
launchctl print-disabled systemlaunchctl print-disabled gui/<uid>Inspect
launchctl print system/<label>launchctl print gui/<uid>/<label> | Persistent state across reboot | All versions; storage details vary. Do not trust plist review alone: disabled or override state can persist independently of the plist, and the exact storage location has changed over time. |
|
BTM / SMAppService
[S4]
| Mixed | Both | Medium |
Artifact / Path
~/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm (legacy / pre-Ventura)/private/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v*.btm (Ventura+, versioned BTM store)Resolves to Registered helper binary or app bundle in BTMOwning app bundle, Team ID, and registration recordEnumerate
sfltool dumpbtmInspect
plutil -p ~/Library/Application\ Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btmcodesign -dv --verbose=4 <helper_or_owner_app> | User login / helper startup | macOS 13+ Background Task Management / SMAppService model. Review BTM state and on-disk records together, then correlate each entry with the owning app bundle, signer, Team ID, and the helper actually present on disk. Treated here as mixed scope because modern SMAppService can register login items as well as launch-item style services. |
|
Login Items (legacy / classic)
[S4a]
| User | Both | Medium |
Artifact / Path
Legacy login item records / shared file list stateResolved app or helper path launched at user loginResolves to App bundle or helper registered for loginOwning app, signer, Team ID, and user-scoped registrationEnumerate
osascript -e 'tell application "System Events" to get the properties of every login item'plutil -p <legacy_login_item_store_when_present>Inspect
codesign -dv --verbose=4 <login_item_or_owner_app>ls -la <resolved_login_item_path> | User login | Legacy / classic login items predate BTM / SMAppService. Backing files and exact storage vary by OS generation, so verify on the target OS and map each registration to the real app or helper on disk. |
|
Embedded Login Helpers
[S5]
| User | Both | Noisy |
Artifact / Path
<App>.app/Contents/Library/LoginItemsResolves to Embedded helper app / binaryOwner app bundle, signer, Team ID, and expected install locationEnumerate
find /Applications ~/Applications -path '*/Contents/Library/LoginItems/*'Inspect
codesign -dv --verbose=4 <helper>plutil -p <owner_app>/Contents/Info.plist | Login / app-managed helper start | Common in legitimate software too. Treat as modern app-bundled persistence and baseline Bundle ID, Team ID, signer, and owner app. Focus on signer mismatches, stale paths, and helpers embedded in unusual app locations. |
| Mechanism | Required privilege | Source of Truth | Signal | Collection / Triage | Trigger | What to review |
|---|---|---|---|---|---|---|
|
Shell init (zsh)
[S7]
| Mixed | Disk | Medium |
Artifact / Path
/etc/zshenv/etc/zprofile/etc/zshrc/etc/zlogin/etc/zlogout~/.zshenv~/.zprofile~/.zshrc~/.zlogin~/.zlogoutResolves to Commands sourced by zsh startup filesZDOTDIR-resolved files and downstream scripts / binariesEnumerate
echo $ZDOTDIRls -la ~/.z* /etc/z*Inspect
grep -nEv '^\s*(#|$)' ~/.zshenv ~/.zprofile ~/.zshrc ~/.zlogin ~/.zlogout /etc/zshenv /etc/zprofile /etc/zshrc /etc/zlogin /etc/zlogout | Login shell / interactive shell / logout | zsh is the default shell on current macOS, but not exclusive to a single release boundary. Resolve ZDOTDIR before concluding nothing changed, or you can miss relocated per-user dotfiles and hidden sourced files. |
|
Shell init (bash / sh)
[S8]
| Mixed | Disk | Medium |
Artifact / Path
/etc/profile/etc/bashrc~/.bash_profile~/.bash_login~/.bashrc~/.bash_logout~/.profileResolves to Commands sourced by bash / sh startup filesReferenced scripts, PATH entries, and ENV / BASH_ENV targetsEnumerate
env | grep -E 'PATH|ENV|BASH_ENV'ls -la ~/.bash* ~/.profile /etc/profile /etc/bashrcInspect
grep -nEv '^\s*(#|$)' ~/.bash_profile ~/.bash_login ~/.bashrc ~/.bash_logout ~/.profile /etc/profile /etc/bashrc | Login shell / interactive shell | bash remains present and is still common on older, migrated, admin, CI, and developer estates. Look for hidden sourcing, PATH abuse, curl or osascript launchers, and suspicious environment exports. |
|
SSH authorized_keys
[S9]
| Mixed | Disk | High |
Artifact / Path
~/.ssh/authorized_keys~/.ssh/config/etc/ssh/sshd_configResolves to Authorized key entries actually accepted by sshdAuthorizedKeysFile path(s), command= restrictions, principals, and signer contextEnumerate
grep -i '^AuthorizedKeysFile' /etc/ssh/sshd_configls -la ~/.sshInspect
ssh-keygen -lf ~/.ssh/authorized_keysgrep -n '' ~/.ssh/authorized_keys /etc/ssh/sshd_config | SSH connection | Access persistence rather than local autostart. High signal on privileged or shared admin accounts. Also verify AuthorizedKeysFile in sshd_config so you do not miss a custom key path. Treated as mixed scope because key material is commonly user-scoped while AuthorizedKeysFile policy may be system-scoped. |
|
Cron
[S10]
| Mixed | Disk | Medium |
Artifact / Path
/etc/crontab/usr/lib/cron/tabs/Resolves to Command field and invoked script pathPer-user or system crontab entry actually executedEnumerate
crontab -lsudo ls -la /usr/lib/cron/tabs/ /etc/crontabfor u in $(dscl . list /Users | grep -v '^_'); do sudo crontab -u "$u" -l 2>/dev/null && echo "[$u]"; doneInspect
sudo grep -nEv '^\s*(#|$)' /etc/crontab /usr/lib/cron/tabs/* | Schedule / periodic | More common on older or mixed estates than on launchd-first modern deployments. macOS-specific quirk: per-user crontabs live in /usr/lib/cron/tabs/, not /var/cron/tabs as on most BSD/Linux references. crontab -l only shows the current user's jobs, so iterate over local accounts (or list the spool directly with root) to avoid missing entries. Verify targets for user-writable paths, temp directories, or masquerading names. |
|
At / atrun
[S11]
| Mixed | Both | Context |
Artifact / Path
at queue / spoolat.allow / at.denyResolves to Queued command / script in the at jobatrun-driven one-shot execution targetEnumerate
atqInspect
at -c <job>grep -n '' /etc/at.allow /etc/at.deny | One-shot deferred execution | Largely theoretical on default macOS: /usr/libexec/atrun is disabled out of the box, so submitted at jobs do not actually fire until an attacker (or admin) enables it with root via sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist. Treat any host where atrun is loaded and at jobs are queued as a strong, version-independent signal. Prefer command-based review (atq, at -c <job>) over a single hardcoded spool path and focus on jobs that launch from temp, shared, hidden, or user-writable locations. |
|
Periodic Jobs
[S12]
| Root | Disk | Context |
Artifact / Path
/etc/periodic/daily/etc/periodic/weekly/etc/periodic/monthly/etc/defaults/periodic.conf/etc/periodic.conf (override; absent by default)Resolves to Script executed from periodic dirsAny custom path introduced through periodic.confEnumerate
ls -la /etc/periodic/daily /etc/periodic/weekly /etc/periodic/monthlyInspect
grep -R '.' /etc/periodic /etc/defaults/periodic.conf /etc/periodic.conf 2>/dev/null | Daily / weekly / monthly maintenance | Low-frequency but worth keeping for exhaustive modern macOS triage. Note that /etc/defaults/periodic.conf is the shipped default; /etc/periodic.conf does not exist by default and is only read if created as a local override, so its mere presence is itself a signal worth investigating. Review inherited script paths, shellouts, and permissions rather than assuming stock maintenance content. |
|
Emond
[S13]
| Root | Disk | High |
Artifact / Path
/etc/emond.d/rules//private/var/db/emondClients/System/Library/LaunchDaemons/com.apple.emond.plistResolves to Action command or script referenced by the ruleCondition-to-action chain that leads to code executionEnumerate
ls -la /etc/emond.d/rules /private/var/db/emondClientsInspect
plutil -p /System/Library/LaunchDaemons/com.apple.emond.plistgrep -R '.' /etc/emond.d/rules | Event-driven (startup, auth, policy) | Apple removed emond entirely in macOS 13 Ventura: the /sbin/emond binary and /System/Library/LaunchDaemons/com.apple.emond.plist no longer ship. On any modern fleet (13+), the presence of /etc/emond.d/rules/*, /private/var/db/emondClients, or any emond-related plist is itself a strong signal because the mechanism does not exist natively. On ≤12 hosts, treat unexplained rules, clients, or odd actions as immediate priority. |
| Mechanism | Required privilege | Source of Truth | Signal | Collection / Triage | Trigger | What to review |
|---|---|---|---|---|---|---|
|
System Extensions
[S14]
| AdminApproval | State | Medium |
Artifact / Path
Contents/Library/SystemExtensionsOwning app bundleCurrent activation stateResolves to Activated system extension and owning appBundle ID, Team ID, and activation state shown by systemextensionsctlEnumerate
systemextensionsctl listInspect
codesign -dv --verbose=4 <owning_app>systemextensionsctl list | grep -E 'enabled|active' | Boot / activation / app-managed load | macOS 10.15+ current model. Use state-first enumeration. Baseline Team ID, Bundle ID, and activation status, then verify the owning app, signer, and approval context. |
|
KEXTs
[S15]
| RootApproval | Both | High |
Artifact / Path
/Library/ExtensionsResolves to Loaded kext bundle ID and pathActual loaded extension plus the on-disk bundleEnumerate
kmutil showloadedkextstatInspect
codesign -dv --verbose=4 <kext>kmutil inspect -b <bundle_id> | Boot / extension load | Legacy mechanism. Kernel extensions are deprecated from macOS 10.15 onward in favor of system extensions, and their use is more restricted on 11+. Presence on a modern fleet is high-signal and often implies exception or reduced-security context. |
|
Chromium Extensions
[S16]
| Mixed | Both | Noisy |
Artifact / Path
~/Library/Application Support/Google/Chrome/*/Extensions~/Library/Application Support/Google/Chrome/*/Preferences/Library/Managed Preferences/com.google.Chrome.plistResolves to Extension ID, manifest, and effective policy sourceForce-installed or managed extension config tied to a real profileEnumerate
find ~/Library/Application\ Support/Google/Chrome -path '*/Extensions/*'plutil -p /Library/Managed\ Preferences/com.google.Chrome.plist 2>/dev/null | grep -E 'ExtensionInstallForcelist|ExtensionSettings'Inspect
grep -n 'extensions' ~/Library/Application\ Support/Google/Chrome/*/Preferencescodesign -dv --verbose=4 <extension_host_app_if_app_mode> | Browser launch / profile load | All versions with Chromium-based browsers; noisy on managed fleets. Review extension IDs, policy-forced installs, managed profiles, update URLs, and any extension granted unusual permissions or loaded from a nonstandard path. Treated as mixed scope because this row covers both per-profile installs and managed / policy-backed installs. |
|
Safari App Extensions
[S17]
| UserApproval | Both | Medium |
Artifact / Path
Owning app bundle / appexSafari extension state / preferencesResolves to appex / owner app bundle actually providing the extensionStored extension state tied to the installed owner appEnumerate
pluginkit -mAv | grep -i safarifind /Applications ~/Applications -name '*.appex'Inspect
codesign -dv --verbose=4 <appex_or_owner_app>find ~/Library/Containers/com.apple.Safari/Data/Library/Preferences -name "*.plist" -exec plutil -p {} \; 2>/dev/null | Browser launch / extension activation | Safari App Extensions are the older app/appex-backed model, introduced with Safari 10 (macOS 10.12 Sierra; also available as a Safari 10 update on 10.11.6 El Capitan). Review the owner app, signer, Team ID, and whether stored state matches the app actually present on disk. Treated as mixed scope because extension state is user-specific while the owning app may live in either user or global application space. |
|
Safari Web Extensions
[S17a]
| UserApproval | Both | Medium |
Artifact / Path
Owning app bundle / web extension payloadSafari extension state / preferencesResolves to Safari web extension actually providing the browser codeStored enablement / permissions tied to the installed owner appEnumerate
pluginkit -mAv | grep -i safarifind /Applications ~/Applications -name '*.appex'Inspect
codesign -dv --verbose=4 <appex_or_owner_app>find ~/Library/Containers/com.apple.Safari/Data/Library/Preferences -name "*.plist" -exec plutil -p {} \; 2>/dev/null | Browser launch / extension activation | Safari Web Extensions are the current model on Safari 14+. Review the owner app, signer, Team ID, extension permissions, and whether the stored state matches the installed app and expected browser profile. Treated as mixed scope because extension state is user-specific while the owning app may live in either user or global application space. |
|
Application / daemon plug-ins
[S17b]
| Mixed | Both | Context |
Artifact / Path
App / daemon-specific plug-in directories/Library/Security/SecurityAgentPlugins/ (authorization plug-ins)/Library/DirectoryServices/PlugIns/ (Open Directory)~/Library/iTunes/iTunes Plug-ins (historical; iTunes removed in 10.15)~/Library/QuickLook / /Library/QuickLook/Library/SpotlightResolves to Actual plug-in bundle loaded by the host app or daemonOwning host, signer, and execution context inherited from that hostEnumerate
pluginkit -mAvqlmanage -m plugins 2>/dev/nullmdimport -L 2>/dev/nullInspect
codesign -dv --verbose=4 <plugin_bundle>find ~/Library /Library -type d \( -name '*Plug-Ins*' -o -name 'QuickLook' -o -name 'Spotlight' \) 2>/dev/null | Host app / daemon launch or plug-in discovery | Covers non-browser plug-in persistence such as app-specific plug-ins and daemon plug-ins. Two especially sensitive subclasses deserve dedicated attention: authorization plug-ins in /Library/Security/SecurityAgentPlugins/ (loaded by authorizationhost / SecurityAgent, can intercept credential prompts and run very early in the auth chain) and Open Directory plug-ins in /Library/DirectoryServices/PlugIns/ (loaded by opendirectoryd, root-context, very high impact). For all rows, focus on writable plug-in directories, unexpected host / signer pairings, and bundles that inherit sensitive host privileges or sandbox exceptions. Treated as mixed scope because plug-ins may live in user-writable or system-wide host directories. |
|
Python Startup Hooks
[S18]
| Mixed | Disk | Context |
Artifact / Path
.pthsitecustomize.pyusercustomize.pyResolves to Imported module or code run on interpreter start.pth executable lines, sitecustomize, or usercustomize targetsEnumerate
python3 -m sitefind ~/Library /usr/local /opt/homebrew \( -name '*.pth' -o -name 'sitecustomize.py' -o -name 'usercustomize.py' \) 2>/dev/nullInspect
grep -n '' <.pth_or_customize_file> | Python interpreter start | Most relevant on dev, automation, and build hosts. Hunt for executable import lines in .pth files, unexpected site-package directories, and hooks that silently reach out to the network or launch other interpreters. Treated as mixed scope because this row covers both user-specific and system-level Python locations. |
|
LaunchServices / URL Handlers
[S19]
| User | Both | Context |
Artifact / Path
LSHandlersCustom scheme / file-handler associations~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plistResolves to Handler Bundle ID and actual registered appCustom scheme or file association that causes the execution pathEnumerate
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dumpInspect
plutil -p ~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist | File open / URL scheme invocation | All versions; preference and registered-state storage varies. Lower-priority and indirect, but useful when persistence appears to hinge on a document opener or custom URL scheme workflow. |
| Mechanism | Required privilege | Source of Truth | Signal | Collection / Triage | Trigger | What to review |
|---|---|---|---|---|---|---|
|
Installer Packages
[S20]
| Admin | Disk | Context |
Artifact / Path
preinstallpostinstallResolves to preinstall / postinstall script targetDropped payload or persistence artifact created by the packageEnumerate
pkgutil --pkgsInspect
pkgutil --expand <pkg> <dir>spctl --assess --type install <pkg> | Package installation | Usually a deployment or dropper surface rather than final persistence. Review because it often lays down LaunchDaemons, helpers, login items, or extensions. Scope is treated as mixed because packages are a deployment surface that can lay down either user- or system-scoped persistence. |
|
DYLD_* injection / LSEnvironment
[S20a]
| Mixed | Disk | Medium |
Artifact / Path
LSEnvironment in app Info.plistEnvironmentVariables in launchd plistsDYLD_INSERT_LIBRARIESDYLD_FRAMEWORK_PATHResolves to Injected dylib path and targeted host processPlist or app bundle whose launch path causes the dylib to loadEnumerate
grep -R -n 'DYLD_\|LSEnvironment\|EnvironmentVariables' /Applications ~/Applications ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons 2>/dev/nullInspect
plutil -p <plist_or_Info.plist>codesign -d --entitlements :- <target_app_or_binary> 2>/dev/null | Target process launch | Separate from Mach-O load-command tampering. On modern macOS, the dynamic loader ignores DYLD_* environment variables for Apple platform binaries and for third-party apps built with the hardened runtime, unless the target carries one of two specific entitlements: com.apple.security.cs.allow-dyld-environment-variables or com.apple.security.cs.disable-library-validation. The fastest discriminating check is therefore codesign -d --entitlements :- <app> on the target host process: a hardened-runtime app exposing either entitlement is the realistic injection target (this is the class of weakness behind the Zoom dylib injection cases). Treated as mixed scope because the persistence artifact may be a user app / plist or a system-wide app / launch item. |
|
Application / binary modification
[S20b]
| Mixed | Disk | Medium |
Artifact / Path
Modified app bundle executable or resourcesTrojanized or infected Mach-O host binaryResolves to Host app or binary that now re-executes malicious codeTampered code signature, loader chain, or embedded persistence logicEnumerate
codesign --verify --deep --strict <app_or_binary>spctl --assess --type execute <app_or_binary>Inspect
codesign -dv --verbose=4 <app_or_binary>otool -l <app_or_binary>shasum -a 256 <app_or_binary> | Host app / binary execution | Broader than added load commands alone. Use this row for trojanized apps, patched bundle contents, and infected binaries that silently restore or re-establish other persistence when the host is launched. Treated as mixed scope because the tampered host may live in user-writable or system-wide locations. |
|
LC_LOAD_DYLIB Addition
[S21]
| Mixed | Disk | Medium |
Artifact / Path
Mach-O load commandsResolves to Injected / added dylib pathImpacted Mach-O binary that loads the dylib at runtimeEnumerate
otool -L <binary>Inspect
otool -l <binary>codesign -dv <binary> | Trusted binary execution | Persistence-adjacent binary tampering. Focus on unexpected dylibs from user-writable paths, hidden directories, or signers that do not fit the host baseline. Treated as mixed scope because the modified host binary may live in user-writable or system-wide locations. |
|
Dylib Hijacking / Proxying
[S21a]
| Mixed | Disk | Medium |
Artifact / Path
App-local Frameworks / PlugIns / LoginItems dylib search pathsWeak / optional dylib dependencies declared by a host Mach-OProxy dylib carrying an LC_REEXPORT_DYLIB to a renamed copy of the originalResolves to Malicious dylib loaded under an existing trusted host processOriginal library functionality preserved through the proxy re-exportEnumerate
otool -l <host_binary> | grep -E 'LC_LOAD_DYLIB|LC_LOAD_WEAK_DYLIB|LC_REEXPORT_DYLIB' -A 3find <App>.app/Contents \( -name '*.dylib' -o -name '*.framework' \)Inspect
otool -l <suspect_dylib> | grep -A 3 LC_REEXPORT_DYLIBcodesign -dv --verbose=4 <suspect_dylib>codesign -d --entitlements :- <host_app> 2>/dev/null | Host app launch / dynamic load | Distinct from a simple LC_LOAD_DYLIB addition. Two related techniques: (1) dylib hijacking exploits a host that searches multiple directories for a dylib (or has a weak / optional dependency that does not exist), letting the attacker drop a malicious dylib of the same name in the first searched / writable location; (2) dylib proxying replaces a real dependency with a malicious dylib that carries an LC_REEXPORT_DYLIB command pointing to a renamed copy of the original, so functionality is preserved while the proxy executes its constructor inside the trusted host. Both inherit the host's TCC permissions (camera, mic, etc.). Detection pivots: presence of LC_REEXPORT_DYLIB in an app's bundled dylib, dylibs in writable per-app directories whose signer differs from the host, and hardened-runtime hosts carrying com.apple.security.cs.disable-library-validation. |
|
Login Hooks
[S22]
| Mixed | Disk | High |
Artifact / Path
/Library/Preferences/com.apple.loginwindow.plist~/Library/Preferences/com.apple.loginwindow.plistLoginHookLogoutHookResolves to LoginHook / LogoutHook script pathCommand or script executed by loginwindow policyEnumerate
sudo defaults read /Library/Preferences/com.apple.loginwindow 2>/dev/nullfind /Users -path '*/Library/Preferences/com.apple.loginwindow.plist' 2>/dev/nulldefaults read ~/Library/Preferences/com.apple.loginwindow 2>/dev/nullInspect
plutil -p /Library/Preferences/com.apple.loginwindow.plistplutil -p ~/Library/Preferences/com.apple.loginwindow.plist | Login / logout | Deprecated since 10.11. Hooks can be installed either user-scoped (~/Library/Preferences/com.apple.loginwindow.plist, current user only) or system-wide (/Library/Preferences/com.apple.loginwindow.plist, requires root, fires for every user at login). The system-wide form is the more dangerous DFIR variant and is often missed when only the user plist is checked. Execution behavior and practical support are historical / target-OS dependent, so verify on the endpoint rather than assuming a hard upper bound from the badge alone. |
|
Startup Items
[S23]
| Root | Disk | High |
Artifact / Path
/Library/StartupItemsStartupParameters.plistResolves to StartupItem script or executableStartupParameters.plist-defined item actually launched during bootEnumerate
find /Library/StartupItems -maxdepth 2 -type fInspect
plutil -p /Library/StartupItems/*/StartupParameters.plist | Boot | Legacy mechanism mainly relevant for OS X 10.3 and earlier compatibility. From 10.4 onward, low-level service startup largely moved to launchd, but the artifact still matters in older images, migrations, and niche DFIR cases. |
|
RC Scripts
[S24]
| Root | Disk | High |
Artifact / Path
rc.localrc.common/private/etc/rc*Resolves to Script body or chained command pathActual command invoked from rc.local / rc.commonEnumerate
find /private/etc -maxdepth 2 -name 'rc*'Inspect
grep -n '' /private/etc/rc* | Boot / init path | Mostly historical on current macOS: like Login Hooks and Startup Items, these mechanisms are deprecated boot paths whose presence on a modern host is itself anomalous and therefore high-signal. Keep for completeness and older-host triage; on modern fleets, treat any non-empty rc.local / rc.common as immediate priority rather than as a first-pass enumeration target. |
.mobileconfig) and MDM-pushed payloads are a deployment surface, not a launchd-style autostart, but they materially shape what runs and what is allowed to run on a Mac. Treat them as a first-class persistence and policy surface: a profile can install login items, allowlist a system extension or kext, grant TCC / PPPC exceptions, install root certificates, push managed preferences for any app, or enroll the device in MDM with remote-management privileges. On a compromised or social-engineered host, a single rogue profile can re-establish other persistence and silently relax defenses.| Mechanism | Required privilege | Source of Truth | Signal | Collection / Triage | Trigger | What to review |
|---|---|---|---|---|---|---|
|
Configuration Profiles (.mobileconfig)
[S25]
| Mixed | Both | Medium |
Artifact / Path
/var/db/ConfigurationProfiles/Store//var/db/ConfigurationProfiles/Settings//Library/Managed Preferences/ (resulting managed prefs)/Library/Managed Preferences/<user>/ (per-user managed prefs)Stray .mobileconfig files in Downloads / Desktop / tempResolves to Installed configuration profile, its identifier, and the issuing organizationEffective managed-preference values that override per-app or per-system defaultsEnumerate
sudo profiles listsudo profiles -Psudo profiles show -type configurationsudo profiles show -type enrollmentfind /Library/Managed\ Preferences -type f 2>/dev/nullfind ~/Downloads ~/Desktop /tmp /var/tmp -name '*.mobileconfig' 2>/dev/nullInspect
sudo profiles -P -o /tmp/profiles_export.plistsudo ls -la /var/db/ConfigurationProfiles/Store/ /var/db/ConfigurationProfiles/Settings/security cms -D -i <profile.mobileconfig>plutil -p /Library/Managed\ Preferences/<domain>.plist | Profile install / MDM push / managed pref load | Two install paths exist: a user double-clicks a .mobileconfig and approves it in System Settings, or an MDM server pushes it silently to an enrolled device. In both cases the on-disk truth lives under /var/db/ConfigurationProfiles/ while the enforced effects surface in /Library/Managed Preferences/. Use sudo profiles list as the first pivot: any profile whose source organization, identifier, or payload set does not match the documented enterprise baseline is high-priority. Profiles installed outside an MDM enrollment (i.e. user-approved local profiles on a managed device) are particularly suspicious. Decode raw .mobileconfig files with security cms -D -i to read their CMS-signed XML payloads. Treated as mixed scope because profiles can target the device, the current user, or both. |
|
High-impact MDM payloads
[S25a]
| Mixed | Both | High |
Artifact / Path
PayloadType com.apple.TCC.configuration-profile-policy (PPPC / privacy)PayloadType com.apple.system-extension-policyPayloadType com.apple.syspolicy.kernel-extension-policyPayloadType com.apple.servicemanagement (managed login items)PayloadType com.apple.security.pkcs1 / .pem / .root (installed certs)PayloadType com.apple.ManagedClient.preferences (custom managed prefs for any app)Resolves to TCC / PPPC exception that lets a chosen binary bypass user prompts for camera, mic, full disk access, AppleEvents, accessibility, etc.Allowlisted Team ID / Bundle ID for system extensions or kextsTrusted root certificate added to the system / user keychainForced login item or background service registered via SMAppServiceEnumerate
sudo profiles show -type configuration | grep -E 'PayloadType|PayloadIdentifier|PayloadOrganization'sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db 'SELECT service, client, auth_value, policy_id FROM access WHERE policy_id IS NOT NULL;'security find-certificate -a -p /Library/Keychains/System.keychainsystemextensionsctl listInspect
security cms -D -i <profile.mobileconfig> | plutil -p -security cms -V -i <profile.mobileconfig>codesign -dv --verbose=4 <binary_granted_TCC_via_profile> | Profile install / payload activation | These payload types are the ones that meaningfully change a host's security posture and that an attacker would actually want to abuse. PPPC payloads can grant a binary persistent TCC / privacy exceptions that the user would otherwise have to click through (the attacker's payload then runs with full disk access, accessibility control, or AppleEvents send rights without prompts). System-extension and kext policy payloads can pre-allowlist a third-party Team ID, removing the approval friction that normally protects extension installs. com.apple.servicemanagement can register login items / SMAppService entries from policy. Certificate payloads can install root CAs that defeat TLS pinning expectations. com.apple.ManagedClient.preferences is a wildcard: it can push managed-preference key/value pairs into any app's preference domain. Operational caveat: querying the system TCC.db requires the terminal (or your DFIR tool) to hold Full Disk Access, even with sudo, because the file is itself protected by TCC; entries with non-null policy_id are the ones installed by configuration profile rather than by user click. For each profile, decode and review the full payload list, not just the profile name; map every PPPC entry to the actual binary it empowers; correlate any new system-extension or kext allowlist with a real activation event in systemextensionsctl list or kmutil showloaded. |
launchctl print and print-disabled.emond.ZDOTDIR before concluding nothing changed.crontab, atq, and /etc/periodic.sudo profiles list / sudo profiles show -type configuration; flag any profile not pushed by your MDM and review high-impact payloads (PPPC, system-extension and kext allowlists, certificate installs, managed login items).~/Library/LaunchAgents or /Library/LaunchDaemons that points into /tmp, /private/tmp, /Users/Shared, a hidden directory, or an Apple-looking filename in a user-writable path.RunAtLoad + KeepAlive on an unsigned or newly written target, especially when paired with WatchPaths or QueueDirectories on ~/Downloads, Desktop, temp, or shared folders.ZDOTDIR set to an unusual path or shell rc files that silently source another script from a hidden or transient location.systemextensionsctl list or a kext that appears on a host where one is not operationally expected..pth file containing executable import lines, or a new sitecustomize.py / usercustomize.py under an unexpected site-packages directory.xattr -l for com.apple.quarantine and review how the artifact first arrived on the host.