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
Tools
Compare all tools- RAM ParserIn browser
- kmutilCLI · built into macOS
- systemextensionsctlCLI · built into macOS
- sqlite3CLI · built into macOS
- plutilCLI · built into macOS
- KnockKnockGUI · open source
- APOLLOCLI · open source
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
| Source | Path | Notes |
|---|---|---|
| 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>.systemextension | Activated copies |
| System extension state | /Library/SystemExtensions/db.plist | Every extension with its state |
| MDM approvals | Configuration profiles with com.apple.syspolicy.kernel-extension-policy or com.apple.system-extension-policy | Pre-approved team IDs |
| Events | Unified Logs, processes sysextd, kernelmanagerd, syspolicyd | Activation, 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 fromdb.plistwhile its app remains, points to tampering or uninstall.
Key fields
| Source | Fields |
|---|---|
KextPolicy table kext_policy | team_id, bundle_id, allowed, developer_name, flags |
KextPolicy table kext_load_history_v3 | path, team_id, bundle_id, boot_uuid, created_at, last_seen, flags, cdhash |
db.plist → extensions | identifier, teamID, state (for example activated_enabled, activated_disabled), categories, originPath, stagedBundleURL, uniqueID |
db.plist → extensionPolicies | MDM-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_v3keeps rows after a kext is deleted, which makes it valuable for a kext that no longer exists on disk.db.plistkeeps 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.pliststates with the security product's own logs: an EDR that stopped reporting while its extension moved toactivated_disabledpoints to a user or admin action, often visible in sudo logs or the Unified Logs.