Lesson 4 of 5 · 25 min read

macOS Forensic Artifacts — Unified Log, FSEvents, TCC, and Quarantine

macOS forensics requires a different mental model — Apple’s closed ecosystem means different artifact locations, binary log formats, and unique privacy controls that both protect users and create forensic blind spots. macOS is the dominant platform for developer endpoints and executive devices, making it a high-value target.


Unified Log — macOS Central Log System

# Command-line log query (log show)
# Filter by process name
log show --predicate 'processImagePath contains "sshd"' --info --last 24h

# Filter by subsystem (Apple's structured logging)
log show --predicate 'subsystem == "com.apple.securityd"' --last 48h

# Authentication events (sudo, login)
log show --predicate 'category == "authorization"' --info --last 24h

# Gatekeeper quarantine/block events
log show --predicate 'subsystem == "com.apple.launchservices"' \
  --predicate 'category == "quarantine"' --last 7d

# Export to JSON for analysis
log show --last 7d --output json > /forensic-share/unified_log.json

# Key subsystems for IR:
# com.apple.securityd       — authentication, keychain
# com.apple.launchservices  — app launches, Gatekeeper
# com.apple.loginwindow     — user login events
# com.apple.tccd            — TCC permission grants/denials
# com.apple.sandbox         — sandbox violations

# Archive current log for offline analysis
log collect --last 7d --output /forensic-share/system.logarchive
# Open the .logarchive file with Console.app on a forensic Mac

FSEvents — File System Change Record

# FSEvents are stored as binary files in /.fseventsd/
ls -la /.fseventsd/

# Parse with fseventsd-tools (Python)
# Install: pip install fseventsd
python3 -m fseventsd parse /.fseventsd/ --output /forensic-share/fsevents.csv

# CSV output fields:
# mask, node_id, path
# mask values: Created (0x00000100), Removed (0x00000200), Modified (0x00001000)

# Example investigation: find all files created/modified in /tmp/ in last 24h
# from the FSEvents database
# Use macOS Forensic Toolset or macMRIforensics for automated parsing

# Key investigative questions FSEvents can answer:
# - Was a specific file present on disk (even after deletion)?
# - When was a suspicious binary dropped?
# - Did the attacker create and then delete staging directories?

TCC Database — Permission Grants

# Query system TCC database (requires root or SIP-disabled environment)
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db \
  "SELECT client, service, auth_value, last_modified FROM access WHERE auth_value = 2;"

# Query user TCC database
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db \
  "SELECT client, service, auth_value, last_modified FROM access;"

# Important service names:
# kTCCServiceSystemPolicyAllFiles = Full Disk Access
# kTCCServiceCamera               = Camera
# kTCCServiceMicrophone           = Microphone
# kTCCServiceAddressBook          = Contacts
# kTCCServiceCalendar             = Calendar
# kTCCServiceReminders            = Reminders
# kTCCServiceAccessibility        = Accessibility (screen recording, control)
# kTCCServiceScreenCapture        = Screen Recording

# auth_reason values:
# 1 = User approved
# 4 = System approved
# 7 = MDM/enterprise policy
# Any unusual app with Full Disk Access is a high-severity finding

Quarantine Database — Download History

# The QuarantineEventsV2 database records every file quarantined by the OS
# including download URL, date, and source app

sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 \
  "SELECT LSQuarantineTimeStamp, LSQuarantineAgentName, 
          LSQuarantineOriginURLString, LSQuarantineDataURLString, 
          LSQuarantineEventIdentifier
   FROM LSQuarantineEvent
   ORDER BY LSQuarantineTimeStamp DESC
   LIMIT 50;"

# LSQuarantineTimeStamp: Mac absolute time (seconds since 2001-01-01)
# Convert: SELECT datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch')
# LSQuarantineAgentName: Safari, Chrome, curl, etc.
# LSQuarantineDataURLString: direct download URL
# LSQuarantineOriginURLString: referrer page

# This database persists even after xattr -d com.apple.quarantine <file>
# — the download event is recorded regardless of whether the file still has the attribute

LaunchAgents and LaunchDaemons — Persistence Locations

# Check all persistence locations
echo "=== System LaunchDaemons (root, boot time) ==="
ls -lat /Library/LaunchDaemons/
ls -lat /System/Library/LaunchDaemons/ | head -20

echo "=== System LaunchAgents (user login, system-wide) ==="
ls -lat /Library/LaunchAgents/

echo "=== User LaunchAgents (current user login) ==="
ls -lat ~/Library/LaunchAgents/

# Examine a suspicious plist
cat ~/Library/LaunchAgents/com.apple.softwareupdate.plist

# Key fields to examine in each plist:
plutil -p ~/Library/LaunchAgents/com.apple.softwareupdate.plist
# Look for:
# - ProgramArguments: the binary being executed (check path)
# - RunAtLoad: true (executes on every login)
# - KeepAlive: true (restarts if killed)
# - StartInterval: N (runs every N seconds — cron-like C2 beacon)
# - EnvironmentVariables: DYLD_INSERT_LIBRARIES (LD_PRELOAD equivalent)

# Find recently modified plists (7 days)
find /Library/LaunchDaemons/ /Library/LaunchAgents/ ~/Library/LaunchAgents/ \
  -name "*.plist" -newer /etc/hosts -ls 2>/dev/null

# Verify each plist's binary exists and is signed
for plist in ~/Library/LaunchAgents/*.plist; do
  bin=$(defaults read "$plist" ProgramArguments 2>/dev/null | head -2 | tail -1 | tr -d '()," ')
  [ -n "$bin" ] && codesign -dvv "$bin" 2>&1 | grep -E "Authority|identifier"
done

Additional macOS Forensic Artifacts

# DYLD_INSERT_LIBRARIES — macOS equivalent of LD_PRELOAD
# Check LaunchAgent plists for EnvironmentVariables containing DYLD_INSERT_LIBRARIES

# Login items (user-facing, also creates LaunchAgent)
osascript -e 'tell application "System Events" to get the name of every login item'

# Cron (still present on macOS, rarely used legitimately)
crontab -l
ls /etc/cron.d/ /var/at/jobs/ 2>/dev/null

# /etc/periodic/ — scheduled maintenance scripts (run by launchd)
ls /etc/periodic/daily/ /etc/periodic/weekly/ /etc/periodic/monthly/

# User account information
cat /etc/passwd
dscl . list /Users
dscl . read /Users/username

# Recently used applications and files
# ~/Library/Application Support/com.apple.sharedfilelist/
# Parse with: sfl2csv tool

# Check gatekeeper assessment database
spctl --assess --verbose /Applications/SuspiciousApp.app