Skip to content

PersistenceExecution

Kernel and System Extensions: macOS kext Evidence

Kernel extensions and system extensions on macOS: KextPolicy load history, the SystemExtensions database and what they show about drivers, EDR and rootkits.

Location
/Library/SystemExtensions/db.plist and /private/var/db/SystemPolicyConfiguration/KextPolicy
Proves
Which third-party kernel extensions and system extensions were installed, approved, activated or loaded, by which team ID, and when
Timestamps
KextPolicy created_at / last_seen as date-time values (check the storage type per image); Unified Log entries in UTC
Access
root; KextPolicy is SIP-protected, read it from an image or with Full Disk Access
Retention
Records persist until the extension is removed and often after; Unified Log events follow rotation
Collection
Aftermath, UAC, sysdiagnose, Disk image

What it is

Third-party code can extend macOS below the user level in two ways:

  • Kernel extensions (kexts) run inside the kernel. Apple deprecated them in macOS 10.15 Catalina. They still load on Intel Macs after user approval, and on Apple Silicon only after the owner lowers the startup security policy to Reduced Security and allows user-managed kexts.
  • System extensions (Catalina and later) run in user space with special entitlements: Endpoint Security clients (EDR, antivirus), network extensions (VPN, content filters, DNS proxies) and DriverKit drivers. They are installed from an app bundle and must be approved by the user or by an MDM profile.

Both need explicit approval, which leaves a policy trail. Attackers rarely ship kexts today, but network extensions and Endpoint Security clients are attractive because they see traffic or process events, and disabling a legitimate security extension is a classic defence-evasion step.

Where it lives

SourcePathNotes
Third-party kexts/Library/Extensions/Apple kexts sit on the sealed system volume in /System/Library/Extensions/
Staged kexts/Library/StagedExtensions/Validated copies used for loading, 10.15+
Auxiliary kernel collection/Library/KernelCollections/Built by kmutil when third-party kexts are approved, 11+
Kext policy/private/var/db/SystemPolicyConfiguration/KextPolicy (SQLite)Approvals and load history
System extensions/Library/SystemExtensions/<UUID>/<name>.systemextensionActivated copies
System extension state/Library/SystemExtensions/db.plistEvery extension with its state
MDM approvalsConfiguration profiles with com.apple.syspolicy.kernel-extension-policy or com.apple.system-extension-policyPre-approved team IDs
EventsUnified Logs, processes sysextd, kernelmanagerd, syspolicydActivation, approval, load

What it proves

  • A kext was approved (kext_policy), and that it actually loaded, with the time of each load (kext_load_history_v3).
  • A system extension was activated, replaced, disabled or removed (state in db.plist), with its bundle ID, team ID and the containing app.
  • Whether an approval came from the user or from an MDM profile.
  • By absence: an EDR system extension in state terminated_waiting_to_uninstall_on_reboot, or missing from db.plist while its app remains, points to tampering or uninstall.

Key fields

SourceFields
KextPolicy table kext_policyteam_id, bundle_id, allowed, developer_name, flags
KextPolicy table kext_load_history_v3path, team_id, bundle_id, boot_uuid, created_at, last_seen, flags, cdhash
db.plist → extensionsidentifier, teamID, state (for example activated_enabled, activated_disabled), categories, originPath, stagedBundleURL, uniqueID
db.plist → extensionPoliciesMDM-allowed team IDs and extension identifiers

Column and key names evolve with releases (the _v3 suffix is itself a version marker); check the schema of your image. APOLLO ships a systempolicyconfig_kextpolicy_kext_load_history_v3 module.

Timestamps

In kext_load_history_v3, created_at (first load) and last_seen (latest load) are used as-is by APOLLO, without epoch conversion, which indicates they are stored as ready-to-read date-time values; confirm the storage type with typeof() on your image before building a timeline. boot_uuid ties a load to a boot session that you can match in the Unified Logs. db.plist has no per-extension date on many releases, so use the birth time of each /Library/SystemExtensions/<UUID>/ folder and Unified Log activation events.

Retention

  • kext_load_history_v3 keeps rows after a kext is deleted, which makes it valuable for a kext that no longer exists on disk.
  • db.plist keeps entries for uninstalled extensions until the next clean-up, in terminated states.
  • Unified Log events follow rotation.

Collection

sudo ditto /private/var/db/SystemPolicyConfiguration ./case/SystemPolicyConfiguration
sudo ditto /Library/SystemExtensions ./case/SystemExtensions
sudo ditto /Library/Extensions ./case/LibraryExtensions
# Live state
kmutil showloaded --list-only > ./case/kmutil_loaded.txt
systemextensionsctl list > ./case/sysext_list.txt

Aftermath lists /Library/SystemExtensions/; UAC runs kextstat where it exists. On Apple Silicon, record the startup security policy as well (bputil -d in recoveryOS, or System Information), since third-party kexts require Reduced Security.

Parsing

sqlite3 -readonly KextPolicy \
  "SELECT path, team_id, bundle_id, created_at, last_seen, typeof(created_at), boot_uuid
   FROM kext_load_history_v3 ORDER BY last_seen DESC;"

plutil -p ./case/SystemExtensions/db.plist | grep -E 'identifier|teamID|state'

log show --archive host.logarchive --timezone UTC \
  --predicate 'process == "sysextd" OR process == "kernelmanagerd"'

KnockKnock lists installed kexts and extensions on a live Mac with signing information. For memory images, the RAM Parser offers a kext inventory, which can reveal a loaded module that hides from disk-based listings.

Investigator tips

  • Build a baseline of expected team IDs (EDR vendor, VPN client, MDM agent). Anything else is a lead.
  • A network extension with a content-filter or DNS-proxy category can silently redirect or observe traffic; look at its app and signing identity.
  • Changes to the startup security policy on Apple Silicon are rare and deliberate; check for them when you find any third-party kext.
  • Compare db.plist states with the security product's own logs: an EDR that stopped reporting while its extension moved to activated_disabled points to a user or admin action, often visible in sudo logs or the Unified Logs.

See also