macOS AMFI and code-signing deep
TL;DR: AppleMobileFileIntegrity (AMFI) is the kernel-side enforcer that turns Apple’s code-signing promises into runtime reality on macOS. It is the missing piece that links userland tools like
codesign, the App Store quarantine model, and entitlement checks into a single MAC framework policy. This note is the deep companion to entitlements-and-codesigning and gatekeeper-and-notarisation, and explains why most modern macOS malware focuses on gatekeeper-bypasses and entitlement abuse rather than directly defeating AMFI.
Why it matters
macOS code-signing is layered. From an attacker’s perspective, each layer can be attacked independently:
- Gatekeeper decides if a fresh download is allowed to run at all.
- Notarisation is Apple’s cloud malware scan plus signing of a ticket.
codesign/ CMS signatures prove who signed a binary and what entitlements it requests.- Hardened Runtime restricts what a signed process can do at runtime (DYLD injection, JIT, etc.).
- AMFI is the kernel-mode policy that actually enforces “this page is signed, this entitlement is granted, this binary is allowed to execute”.
If you only understand the userland half (codesign -dvvv, spctl) you will miss why some bypasses are catastrophic (AMFI policy hole = arbitrary entitlements) versus why others are merely annoying (quarantine bit not set = Gatekeeper skipped). See macos-architecture and sip for the surrounding kernel-protection story.
Classes, patterns and the enforcement process
The signing stack from disk to execve
- A developer builds a Mach-O binary and signs it with
codesign --sign "Developer ID Application: ...". The signature is embedded as aLC_CODE_SIGNATUREload command pointing to a SuperBlob containing CodeDirectory, requirements, entitlements, and a CMS blob. - Optionally the app is notarised: uploaded to Apple, scanned, then a “ticket” is stapled to the bundle.
- On download via a quarantine-aware app, the file gets the
com.apple.quarantinexattr. See gatekeeper-and-notarisation. - On first launch, Gatekeeper /
syspolicydconsultsspctl’s policy database, checks the ticket, and prompts the user. - The kernel
execvepath calls into AMFI via the MAC Framework (MACF) hooks (mpo_vnode_check_signature,mpo_proc_check_run_cs_invalid,mpo_cred_check_label_update_execve, etc.). - AMFI consults the CodeDirectory hashes, the entitlement blob, the team identifier, and provisioning profile (rare on macOS), then sets the process’s CS flags (
CS_VALID,CS_HARD,CS_KILL,CS_RESTRICT,CS_REQUIRE_LV). - At runtime, every page fault that brings in a code page is re-validated against the CodeDirectory hash tree. A modified page leads to
CS_KILLand SIGKILL.
AMFI as a MACF policy
AMFI ships as AppleMobileFileIntegrity.kext and registers as a MAC Framework policy. Important hooks:
mpo_vnode_check_signaturedecides whether a Mach-O may even be mapped executable.mpo_proc_check_get_taskcontrols whethertask_for_pidandprocessor_set_taskscan hand out task ports, which is the gate for debugging and code injection. See macos-kernel-debugging.mpo_cred_label_update_execveis where entitlements are parsed and bound to the process credential.mpo_proc_check_run_cs_invalidis what kills a process whose pages no longer match.
AMFI also exposes amfid in userland, a daemon the kernel asks to verify signatures it cannot finish in-kernel (for example, when CMS verification needs network or keychain access).
Entitlements, hardened runtime, library validation
- Entitlements are signed XML/plist blobs in the CodeDirectory. They are requests. Apple-only entitlements (
com.apple.private.*,com.apple.rootless.*) require Apple’s signing keys; a third-party Developer ID cannot validly request them on a non-jailbroken system. Cross-reference entitlements-and-codesigning. - Hardened Runtime is opt-in (required for notarisation) and disables several legacy injection vectors:
DYLD_INSERT_LIBRARIES, unsigned dylib loading, executable memory creation withoutcom.apple.security.cs.allow-jit, debugger attachment, etc. - Library Validation (LV) is implied by Hardened Runtime and forces every loaded dylib to be signed by the same Team ID or by Apple. LV is the single biggest defence against userland dylib hijack; turning it off requires the
com.apple.security.cs.disable-library-validationentitlement, which notarisation rarely allows.
SIP, AMFI and rootless
System Integrity Protection (see sip) tags certain files and directories as restricted. AMFI cooperates by treating Apple-platform binaries (those with the platform-binary flag in the CodeDirectory) as eligible for private entitlements. Many bypasses are really chains: an sip-bypasses primitive lets you tamper with a platform binary, which then runs with private entitlements, which AMFI honours.
How malware bypasses or sidesteps the model
- Skip the model entirely: drop a non-quarantined file (LOLBin chain, AppleScript, curl writing to disk, an
xattr -d com.apple.quarantineonce code is running). Gatekeeper never runs; AMFI still validates the signature but a self-signed ad-hoc binary can still execute as long as it requests no privileged entitlements. This is the bread and butter of commodity Mac malware. - Use a real Developer ID: buy or steal one, sign the loader, notarise it (Apple’s scan is shallow and stage-2 payloads are fetched later). Lazarus and various adware vendors have done this for years. Cross-reference apt-tradecraft-dprk-lazarus and case-study-3cx-supply-chain.
- Notarisation abuse: get a clean first-stage notarised, then download a second-stage that is unsigned but launched via
dlopenfrom an already-validated host withdisable-library-validation, or executed viaNSTaskof an interpreter. - Entitlement extraction: find an Apple binary holding
com.apple.private.*and abuse it (XPC service confusion, env var injection on an entitled helper, dyld cache abuse). The classic CVE-2019-8513 family and many macos-sandbox-escape bugs work this way. - AMFI itself: pure AMFI bypasses (forging signatures the kernel accepts) are rare and usually chained with a kernel info-leak or a logic bug in
amfid. Linus Henze’s “CVE-2021-30724 amfid bypass” lineage and earlieramfidMitM tricks viatask_for_pidofamfidare good study material.
Defensive baseline
- Treat notarisation as a weak signal: it means “not obviously malware on a single day”, not “safe”.
- Require Hardened Runtime + Library Validation for in-house Mac apps; reject build configurations that enable
disable-library-validationorallow-dyld-environment-variableswithout review. - Inventory Developer ID certificates and rotate / revoke aggressively. Stolen Developer IDs are the primary signing-key supply-chain risk on macOS.
- Endpoint detection: alert on
xattr -d com.apple.quarantine, onspctl --master-disable, oncsrutil disable, on processes withCS_VALID=0running for more than a few seconds, and on children ofInstaller.appwriting toLaunchAgents. Tie to macos-unified-logs-forensics. - Hunt for entitlement anomalies: scan all third-party signed code on managed endpoints for any
com.apple.private.*,com.apple.rootless.*, orget-task-allowentitlement that is not Apple-signed. These are immediate red flags. - Validate the provenance chain in CI: only ship binaries whose
codesign -dvvv --entitlements :- --xmloutput matches a pinned baseline.
Workflow to study
- Read Apple’s TN3127 “Inside Code Signing: Provisioning Profiles” and TN3125 “Inside Code Signing: Hashes” end to end.
- Take any signed app and dump every layer:
codesign -dvvv --entitlements :- /path/appcodesign --display --requirements - /path/appspctl -a -vv /path/appstapler validate /path/appotool -l /path/binary | grep -A4 LC_CODE_SIGNATURE
- Read the open-source pieces:
Security/OSX/libsecurity_codesigning/, thecctoolscodesign_allocate, and Patrick Wardle’s The Art of Mac Malware Vol. 1 chapters on signing. - Walk the AMFI hooks in xnu (
security/mac_*) and in the published kext symbol names. Map each hook to a userland symptom you can reproduce. - Reproduce a benign “quarantine skip” lab: download a signed app two ways, with and without quarantine, and watch the difference in
log stream --predicate 'subsystem == "com.apple.syspolicy"'. - Read recent advisories tagged AMFI or
_RETin Apple’s security release notes and diff with the xnu open-source drop for that release; this is the same one-day pattern in one-day-from-patch-diff. - Build a tiny harness that calls
csops/csops_audittokenon every running PID and flags processes whose CS flags mismatch their on-disk signature; this is a great detection primitive.
Related
- entitlements-and-codesigning
- gatekeeper-and-notarisation
- gatekeeper-bypasses
- sip
- sip-bypasses
- macos-architecture
- macos-sandbox-escape
- macos-userland-mitigations
- macos-kernel-debugging
- macos-unified-logs-forensics
- apple-mitigations
- ios-vs-macos-divergence
- mach-and-xpc
- apt-tradecraft-dprk-lazarus
- case-study-3cx-supply-chain
- one-day-from-patch-diff
References
- Apple, “TN3127: Inside Code Signing: Requirements, Hashes, and Signatures”: https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements
- Apple, “Hardened Runtime”: https://developer.apple.com/documentation/security/hardened_runtime
- Patrick Wardle, “The Art of Mac Malware, Volume 1” (free online): https://taomm.org/
- Csaba Fitzl, “AMFI and code signing on macOS” talk notes (Objective by the Sea): https://objectivebythesea.org/
- Linus Henze, write-up on amfid bypass lineage (Pwn2Own / public PoCs): https://github.com/LinusHenze
- jonathanlevin.net, “*OS Internals Vol. III” companion materials on AMFI: https://newosxbook.com/