Completing the cross-platform picture: macOS persistence, live collection, and the emerging challenge of container forensics — where the entire OS model changes.
macOS Persistence — Full Inventory
LaunchAgents and LaunchDaemons (Primary)
# All persistence via launchd (the macOS init system)
# Priority check order:
# 1. User LaunchAgents (no root required — common malware target)
ls -lat ~/Library/LaunchAgents/
# 2. System LaunchAgents (root required)
ls -lat /Library/LaunchAgents/
# 3. System LaunchDaemons (root required, run before login)
ls -lat /Library/LaunchDaemons/
# Quick dump of all non-Apple LaunchAgents
find /Library/LaunchAgents/ ~/Library/LaunchAgents/ /Library/LaunchDaemons/ \
-name "*.plist" | while read plist; do
# Check if the ProgramArguments point to a signed binary
prog=$(defaults read "$plist" ProgramArguments 2>/dev/null | \
grep -v "^(" | head -1 | tr -d '" ')
echo "$plist → $prog"
done
Login Items and Application Extensions
# Login items via AppleScript (items added through System Preferences)
osascript -e 'tell application "System Events" to get name of every login item'
# Service Management Framework login items (macOS 13+)
# Stored in /Library/Application Support/com.apple.backgroundtaskmanagementd/
ls "/Library/Application Support/com.apple.backgroundtaskmanagementd/"
# Application extensions (Safari extensions, Finder extensions)
ls ~/Library/Application\ Support/com.apple.Safari/Extensions/
Kernel Extensions (kexts) — High Privilege Persistence
# List loaded kernel extensions
kextstat | grep -v com.apple
# Kexts are stored in /Library/Extensions/ and /System/Library/Extensions/
# Third-party kexts require user approval since macOS 10.13
# Check for recently added kexts
find /Library/Extensions/ -newer /etc/hosts -name "*.kext" 2>/dev/null
# Kext installation requires System Preferences → Security → Allow
# — TCC database records this approval
Cron, Periodic Scripts, and at
# User cron
crontab -l
sudo crontab -l # root's cron
# System periodic (maintenance scripts)
ls /etc/periodic/daily/ /etc/periodic/weekly/ /etc/periodic/monthly/
cat /etc/periodic/daily/666.logrotate # example
# at jobs
atq
ls /var/at/jobs/ 2>/dev/null
macOS Live Triage Collection
#!/bin/bash
# macOS IR triage script — run as root from USB or network share
CASE="IR-2024-089"
HOST=$(hostname)
OUT="/Volumes/ForensicUSB/${CASE}/${HOST}_$(date +%Y%m%d)"
mkdir -p "$OUT"
# 1. Network state
netstat -anv > "$OUT/netstat.txt"
lsof -i -nP > "$OUT/lsof_net.txt"
# 2. Process list
ps auxf > "$OUT/processes.txt"
ps -axo pid,ppid,user,comm,args > "$OUT/processes_args.txt"
# 3. User sessions
w > "$OUT/users.txt"
last -n 50 >> "$OUT/users.txt"
# 4. Unified Log (last 48 hours)
log collect --last 48h --output "$OUT/system.logarchive"
# 5. Persistence locations
for dir in /Library/LaunchDaemons /Library/LaunchAgents \
~/Library/LaunchAgents /Library/StartupItems; do
[ -d "$dir" ] && cp -r "$dir" "$OUT/$(basename $dir)_launchd"
done
# 6. bash/zsh history
for home in /Users/*/; do
user=$(basename "$home")
cp "${home}.bash_history" "$OUT/${user}_bash_history" 2>/dev/null
cp "${home}.zsh_history" "$OUT/${user}_zsh_history" 2>/dev/null
done
# 7. TCC database (requires full disk access)
cp "/Library/Application Support/com.apple.TCC/TCC.db" "$OUT/system_TCC.db" 2>/dev/null
cp ~/Library/Application\ Support/com.apple.TCC/TCC.db "$OUT/user_TCC.db" 2>/dev/null
# 8. Quarantine database
cp ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 \
"$OUT/QuarantineEvents.db" 2>/dev/null
# 9. /etc/ and key system files
cp /etc/hosts "$OUT/hosts.txt"
cp /etc/passwd "$OUT/passwd.txt"
dscl . list /Users > "$OUT/all_users.txt"
# 10. SIP status
csrutil status > "$OUT/sip_status.txt"
# 11. Installed software
pkgutil --pkgs > "$OUT/installed_packages.txt"
ls /Applications/ > "$OUT/applications.txt"
brew list 2>/dev/null > "$OUT/homebrew_packages.txt"
# Hash all collected files
find "$OUT" -type f -exec shasum -a 256 {} \; > "$OUT/collection_hashes.sha256"
echo "Triage complete: $OUT"
Container Forensics — Docker and Kubernetes
Container environments require a fundamentally different forensic model — the traditional concept of a persistent host disappears.
Docker Container Forensics
# List running and stopped containers
docker ps -a
# Inspect a suspicious container
docker inspect [container_id]
# Shows: image, creation time, mounts, environment variables, network config, entrypoint
# Get container filesystem as a tar archive (image of the container layer)
docker export [container_id] > /forensic-share/container_export.tar
# Read container logs (stdout/stderr of the main process)
docker logs [container_id] --timestamps
# Execute inside a running container for live investigation
docker exec -it [container_id] /bin/sh
# Run: ps aux, netstat, cat /proc/*/cmdline, find / -newer /etc/hosts
# Extract a specific file from a container
docker cp [container_id]:/tmp/suspicious.bin /forensic-share/suspicious.bin
# Get image history (layers = changes to the filesystem)
docker history [image_name]
# Inspect the overlay filesystem (where container files live on the host)
docker inspect [container_id] | grep -A5 "GraphDriver"
# UpperDir: the container's writable layer (changes from base image)
ls /var/lib/docker/overlay2/[hash]/diff/
Key Container Forensic Challenges
Challenge 1: Ephemerality
The container may be destroyed (killed + removed) by an orchestrator
before an analyst reaches it. Container logs persist only if a logging
driver was configured (ELK, CloudWatch, Splunk).
Challenge 2: Shared kernel
Containers share the host kernel — the host's /proc/ shows ALL container
processes. docker inspect + /proc/ can identify container PIDs on the host.
Challenge 3: No persistent storage (by default)
Malware that runs in a container and writes to the container filesystem
leaves no traces on the host after container deletion. Volume mounts
persist — check docker inspect for Mounts.
Challenge 4: Image provenance
The image itself may be malicious. Check: docker inspect shows image
digest; compare against registry. docker history shows all layers —
look for layers that add suspicious files.