Trust Boundaries an Operator Must Understand Before Touching a Target
Every macOS technique you will learn in this course either respects a trust boundary, abuses a gap in one, or creates evidence that a boundary was crossed. If you do not know what each boundary enforces, you cannot explain why a technique worked, why it failed, or what a defender will see.
This module is not a macOS security whitepaper. It is a map of the control surface an operator needs to reason about before running a single technique. You will inspect each boundary on your own Mac, understand what it blocks and what it does not, and learn the operator questions that matter during an engagement.
The boundaries covered here appear in every later phase of this course. When a persistence module says "this survives reboot because launchd restarts it," you will already know which launchd domain controls that behavior. When an execution module says "this bypasses library validation," you will know that AMFI is the control being tested. This module is the reference those later modules assume.
The Layered Model
macOS does not have one security boundary. It has several, stacked in layers, each owned by a different subsystem with its own policy store, its own enforcement point, and its own telemetry.
Layer Enforces Operator question
────────────────────────────────────────────────────────────────────────────
Gatekeeper First-launch app assessment Does the artifact need user approval to open?
Notarization Apple-issued launch ticket Has Apple seen and signed off on this binary?
Quarantine Download provenance xattr Is the file treated as "from the internet"?
Code Signing Binary identity and integrity Can the binary be modified without breaking the seal?
AMFI Runtime code-signing enforcement Will the process be killed for invalid code?
Hardened Runtime Per-process restriction flags Which dangerous APIs are blocked even for signed code?
Sandbox Process containment profile What files, network, and IPC can this process reach?
TCC User privacy consent Which protected data requires an approval dialog?
launchd Process lifecycle and privilege Who can start, stop, or control this service?
SIP System file immutability Which directories are read-only even for root?
MDM / Profiles Enterprise policy overlay Is there organization-level policy above the local admin?
Unified Logging System-wide telemetry substrate What did macOS record about everything above?
None of these boundaries exists in isolation. Gatekeeper checks code signing state before allowing first launch. AMFI enforces code signing at runtime. TCC decisions are scoped by sandbox and code identity. SIP prevents even root from modifying the files that implement these controls. And Unified Logging records evidence from every single one.
Take five minutes and read that table again. The rest of this module inspects each row.
Inspect Your Machine Now
Before diving into each boundary, capture a snapshot of what your Mac has enabled right now. This is the "context" step from module 0.1 applied to trust boundaries.
EVIDENCE=~/macseclabs/evidence/0.2-trust-boundaries
mkdir -p "$EVIDENCE"
# SIP status
csrutil status > "$EVIDENCE/sip-status.txt" 2>&1
# Gatekeeper and assessment policy
spctl --status >> "$EVIDENCE/gatekeeper-status.txt" 2>&1
# AMFI and library validation indicators
nvram boot-args 2>/dev/null > "$EVIDENCE/boot-args.txt"
sysctl kern.bootargs >> "$EVIDENCE/boot-args.txt" 2>&1
# TCC database existence and permissions
ls -la "/Library/Application Support/com.apple.TCC/" >> "$EVIDENCE/tcc-paths.txt" 2>&1
ls -la ~/Library/Application\ Support/com.apple.TCC/ >> "$EVIDENCE/tcc-paths.txt" 2>&1
# Active configuration profiles
profiles status -type enrollment >> "$EVIDENCE/profiles-status.txt" 2>&1
# launchd domain layout
ls -la /Library/LaunchDaemons/ > "$EVIDENCE/launchd-system.txt" 2>&1
ls -la ~/Library/LaunchAgents/ > "$EVIDENCE/launchd-user.txt" 2>&1
You will add to this evidence directory as you inspect each boundary.
Gatekeeper
What It Is
Gatekeeper is the subsystem that decides whether a downloaded application is allowed to run. It is not a single binary. It is a policy evaluated by multiple components: LaunchServices, syspolicyd, and the kernel, each checking different properties of the executable before execution proceeds.
The assessment path on first launch:
1. User or process attempts to open an application.
2. LaunchServices checks whether the app is quarantined.
3. If quarantined, syspolicyd evaluates the app against current policy.
4. Policy checks: code signature validity, notarization status, and the
quarantine xattr value.
5. Gatekeeper either allows the app, blocks it, or shows a user prompt.
6. The result is cached so subsequent launches skip assessment.
What Gatekeeper does not do: it does not scan for malware, it does not inspect app behavior at runtime, and it does not apply to apps launched from the command line with a direct path (no quarantine xattr propagation). It is a first-launch gate, not a runtime guard.
Inspect It
# Current Gatekeeper policy
spctl --status
# Detailed assessment policy
spctl --assess --verbose=4 /System/Applications/TextEdit.app 2>&1
# List of developer tools that have bypassed Gatekeeper
spctl --list --type execute 2>&1 | head -20
Gatekeeper policy options you may encounter:
spctl --status output Operator meaning
───────────────────────────────────────────────────────────
assessments enabled Gatekeeper is active. Downloaded apps are assessed.
assessments disabled User or management has turned Gatekeeper off.
Even when Gatekeeper is disabled, the other boundaries (code signing, AMFI, TCC, SIP) are still in effect. Gatekeeper off does not mean "no security." It means one specific assessment step does not run.
Operator Relevance
Gatekeeper is the first trust decision your payload faces if it arrives as a downloaded app. The questions that matter during an engagement:
- Does the delivery path preserve or strip quarantine?
- Is the app signed? Ad hoc? Developer ID? Notarized?
- Does enterprise policy allow apps from identified developers only?
- Does the user see a prompt, and is that prompt acceptable under the rules
of engagement?
- If Gatekeeper blocks the app, does the block create a log event that the
security team will investigate?
The practical effect: if you deliver an unsigned, non-notarized app bundle through a browser download that preserves quarantine, Gatekeeper will block first launch. If you deliver the same app through a USB drive or extract it from a ZIP that did not propagate quarantine, Gatekeeper may not assess it at all. Neither path is "stealthier" in isolation. Each path creates different evidence.
Notarization
What It Is
Notarization is an Apple service that scans software before distribution. A developer uploads their app to Apple. Apple runs automated malware scanning and issues a notarization ticket if the app passes. The ticket is stapled to the app and checked by Gatekeeper during first launch.
Notarization is not code review. Apple is not auditing your source code. The scan looks for known malware signatures, entitlement anomalies, and code-signing issues. It is an automated triage step, not a security guarantee.
Inspect It
# Do not use Apple system apps as your notarization example.
# Many system apps do not carry a stapled third-party notarization ticket.
stapler validate /System/Applications/TextEdit.app 2>&1
# Check a downloaded third-party Developer ID app if you have one installed.
# This is the more useful operator test because Gatekeeper evaluates software
# entering the machine from outside Apple's sealed system volume.
# stapler validate /Applications/SomeApp.app 2>&1
If TextEdit reports that it does not have a stapled ticket, that is expected on many current macOS builds. The important lesson is not "TextEdit is notarized." The important lesson is learning how a stapled ticket appears on a third-party app that was distributed through the normal Developer ID workflow.
Operator Relevance
For an authorized red team, notarization is a delivery constraint, not a bypass target. An operator should know:
- Notarized apps get through Gatekeeper with less friction.
- Notarization requires a Developer ID certificate, which ties the app to a
known identity. That identity is part of the operation's attribution story.
- Apple can revoke notarization tickets remotely. A revoked ticket means the
app is blocked everywhere.
- The absence of notarization does not mean the app is blocked. It means the
user may see a stronger warning.
During an engagement, the notarization question is: does the access path support an app that Gatekeeper will assess, and if so, what signing and notarization state is appropriate under the rules of engagement?
Quarantine
What It Is
Quarantine is an extended attribute (com.apple.quarantine) that macOS attaches
to files downloaded from the internet. It is the trigger that causes Gatekeeper
to assess an app on first launch. Without quarantine, Gatekeeper may not evaluate
the app at all.
The quarantine xattr contains:
- A flag field indicating what assessment has been performed.
- A timestamp of when the file was downloaded.
- The bundle ID or agent name of the application that downloaded it (Safari,
Chrome, curl, etc.).
- The URL or origin domain of the download (optional).
Inspect It
Download a file through your browser, then inspect its quarantine xattr:
# Find recently quarantined files in Downloads
ls -la ~/Downloads/*.dmg ~/Downloads/*.zip ~/Downloads/*.app 2>/dev/null | head -10
# Inspect quarantine on a downloaded file (adjust path to a real file)
xattr -l ~/Downloads/some-downloaded-file.dmg 2>/dev/null
If you do not have a downloaded file handy, create a quarantine test:
# Create a test file and apply quarantine manually
echo "test" > /tmp/quarantine-test.txt
xattr -w com.apple.quarantine \
"0081;$(printf '%x' "$(date +%s)");Safari;https://example.com/" \
/tmp/quarantine-test.txt
xattr -l /tmp/quarantine-test.txt
rm /tmp/quarantine-test.txt
Operator Relevance
Quarantine is one of the most misunderstood macOS security mechanisms. Key operator facts:
- Quarantine is a filesystem xattr. It propagates through some operations
(Archive Utility extraction) but not others (ditto, cp, mv, tar extraction).
- Removing quarantine with xattr -d is itself a loggable event.
- An app launched from the command line with a full path does not trigger
Gatekeeper, regardless of quarantine state, because LaunchServices is not
the launch path.
- ZIP archives downloaded from the internet: Archive Utility propagates
quarantine to extracted items. ditto and unzip from Terminal do not.
The operator lesson: if your delivery chain includes a browser download, you must account for quarantine. If it does not (USB, network share, pre-installed tool, command-line fetch with curl), quarantine propagation depends on the exact tools used.
Code Signing
What It Is
Code signing is the identity and integrity system for macOS executables. Every Mach-O binary, dylib, app bundle, and framework on the system has a code signature that answers two questions: who signed this, and has it been modified since signing?
Code signing states that matter to an operator:
Unsigned:
No code signature at all. AMFI may refuse to run it depending on system
policy. Gatekeeper will block it if quarantined.
Ad hoc signed:
Has a code directory (hash of every page) but no signing identity.
Satisfies AMFI's "must have some signature" requirement but provides
no identity. Created with codesign --sign -.
Developer ID signed:
Signed with an Apple-issued Developer ID certificate. The certificate
ties the binary to a specific developer account. Gatekeeper accepts
this as "from an identified developer."
Notarized:
Developer ID signed plus an Apple-issued notarization ticket stapled
to the app. Gatekeeper gives this the smoothest launch path.
Invalid:
The signature exists but the code has been modified after signing.
AMFI will refuse to run it. This is what happens when you hex-edit
a signed binary.
Inspect It
# Check signature on a system binary
codesign -dv /bin/zsh 2>&1
# Check signature on an app bundle
codesign -dv --verbose=4 /System/Applications/TextEdit.app 2>&1
# Check for hardened runtime
codesign -dv --verbose=4 /System/Applications/TextEdit.app 2>&1 | grep -i hardened
# List all signatures in an app bundle (app + embedded frameworks/dylibs)
codesign --display --deep /System/Applications/TextEdit.app 2>&1 | head -20
Compare an unsigned binary with a signed one:
# Create an unsigned binary (safe, just a shell script with execute bit)
echo '#!/bin/sh\necho hello' > /tmp/unsigned-test
chmod +x /tmp/unsigned-test
codesign -dv /tmp/unsigned-test 2>&1
# Now ad hoc sign it
codesign --force --sign - /tmp/unsigned-test 2>&1
codesign -dv /tmp/unsigned-test 2>&1
rm /tmp/unsigned-test
Notice the difference in codesign -dv output between unsigned and ad hoc
signed. The ad hoc signed binary has a cdhash and a code directory. The unsigned
one does not. For an operator, this output is what a defender's tooling sees when
it inspects your binary.
Operator Relevance
Code signing is the single most important macOS trust primitive. Nearly every other boundary (Gatekeeper, AMFI, TCC, Sandbox) uses code identity as an input to its policy decision. If you do not understand the signing state of your own binary, you cannot predict how any other boundary will treat it.
Operator checklist for any binary you build or deploy:
- What is its signing state? (unsigned, ad hoc, Developer ID, notarized)
- What does codesign -dv say about it?
- Does it need a signing state that satisfies AMFI on the target OS version?
- Does the code identity need to survive a TCC or Keychain access check?
- If the binary is modified post-signing (patching, string replacement,
resource changes), does the signature still validate?
A common beginner mistake: treating "signed" as a binary state. It is not. There are at least four distinct states, and each produces different behavior at every boundary gate.
AMFI (Apple Mobile File Integrity)
What It Is
AMFI is the kernel-level enforcer of code-signing policy at runtime. If code signing is the identity card, AMFI is the guard at the door who checks it. AMFI enforces:
- Library validation: a process cannot load unsigned or ad-hoc-signed dylibs
if the main executable has the Hardened Runtime flag set.
- Code identity stability: if a signed executable is modified in memory, AMFI
may kill the process.
- Debugging restrictions: AMFI controls whether a process can be debugged,
which is why SIP must be partially disabled to debug system binaries.
- The "amfid" daemon: user-space component that communicates with the kernel
to validate code signatures at load time.
Inspect It
# Check whether AMFI is enforcing (requires SIP to be on)
sysctl kern.bootargs 2>/dev/null | grep -i amfi
# Check if amfid is running (it should be on a normal Mac)
ps aux | grep amfid | grep -v grep
# Check a process for its code-signing flags at runtime
# (use any running process PID)
ps -eo pid,comm | head -5
codesign -dv --verbose=4 /bin/zsh 2>&1
AMFI is not something you "turn off" during an engagement. On a production Mac with SIP enabled, AMFI enforcement is active and non-negotiable. The operator skill is understanding what it blocks and working within those constraints.
Operator Relevance
AMFI is the reason dylib hijacking has constraints. If the target process has Hardened Runtime with library validation, you cannot inject an unsigned dylib. If you strip the code signature from a binary to modify it, AMFI may prevent it from launching. These are not bugs. They are the rules. Your techniques must respect them or prove, in a controlled lab, exactly which rule they are testing.
Hardened Runtime
What It Is
The Hardened Runtime is a set of per-process restrictions that apply even to properly signed applications. It is configured at build time through entitlements and code-signing flags. Unlike the sandbox, which restricts what a process can reach, Hardened Runtime restricts what dangerous APIs a process can call.
Key restrictions:
Library validation:
The process can only load dylibs signed by the same team ID or by Apple.
This is the restriction that blocks dylib hijacking against hardened targets.
Debugging:
The process cannot be attached to by a debugger unless the
com.apple.security.get-task-allow entitlement is present.
JIT:
The process cannot create MAP_JIT memory regions unless the
com.apple.security.cs.allow-jit entitlement is present.
DYLD environment variables:
DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, and similar variables are
ignored for hardened processes.
Hardened Runtime is opt-in by the developer, but Apple requires it for notarization. Any notarized app has Hardened Runtime enabled by definition.
Inspect It
# Check whether a binary uses Hardened Runtime
codesign -dv --verbose=4 /System/Applications/TextEdit.app 2>&1 | grep -E "flags|hardened"
# The flags line will show "flags=0x10000" or similar if Hardened Runtime is on
# Flag 0x10000 = runtime (Hardened Runtime enabled)
Operator Relevance
Hardened Runtime is the reason many "classic" macOS injection techniques no longer work against modern, notarized applications. Before you attempt:
- DYLD_INSERT_LIBRARIES against a hardened process: it will be ignored.
- dylib hijacking against a hardened process: library validation blocks it.
- task_for_pid on a hardened process: may require special entitlements.
- Debugging a hardened process: requires get-task-allow entitlement.
The course teaches which techniques work against which target types. The key is knowing, before you try, whether the target's runtime flags make the technique viable or impossible.
Sandbox
What It Is
The macOS sandbox is a per-process containment policy written in the Sandbox Profile Language (SBPL) and enforced by the kernel. A sandboxed process operates inside a allowlist: it can only access files, network endpoints, Mach services, and system resources explicitly listed in its profile.
Sandbox profiles come from several sources:
- Built-in system profiles for platform services (e.g., cfprefsd, mds).
- App sandbox for App Store applications.
- Container-specific profiles for apps that opt into sandboxing.
- System service profiles applied by launchd at spawn time.
Inspect It
# Check whether an app declares the App Sandbox entitlement.
# Calculator is a useful Apple-supplied example on current macOS releases.
codesign -d --entitlements :- /System/Applications/Calculator.app 2>&1 \
| grep -A1 'com.apple.security.app-sandbox' \
|| echo "app-sandbox entitlement not present"
# Compare that with a normal shell binary.
codesign -d --entitlements :- /bin/zsh 2>&1 \
| grep -A1 'com.apple.security.app-sandbox' \
|| echo "app-sandbox entitlement not present"
For a running process, current macOS does not expose a clean non-root
sandbox-check equivalent. Older references to sandbox-check are stale on
modern systems. Runtime process inspection is available through launchctl procinfo, but that subcommand requires root privileges:
# Pick a PID from your own lab process list, then inspect it as root.
ps -eo pid,comm | head -10
sudo launchctl procinfo 1234 2>&1 | sed -n '1,80p'
Use entitlement inspection first because it is safe, non-invasive, and works
without special privileges. Use launchctl procinfo only when you need runtime
confirmation for a specific process and the lab explicitly permits sudo.
Operator Relevance
The sandbox matters to an operator in two directions. First, if your own process ends up sandboxed (e.g., running inside an App Store app's container), your capabilities are severely constrained. Second, if a target process is sandboxed, it may not be able to reach the resources you expect it to access, which limits both attack surface and defensive value.
Key operator facts:
- App Store apps are sandboxed by default.
- Command-line tools, daemons, and agents are generally not sandboxed unless
explicitly configured.
- The sandbox is a per-process policy. Escaping it usually requires a kernel
vulnerability or a misconfigured XPC service with broader entitlements.
- Sandbox violations are logged. A burst of sandbox denials is high-signal
telemetry.
TCC (Transparency, Consent, and Control)
What It Is
TCC is the privacy consent system. When an application wants to access protected data (contacts, calendar, camera, microphone, location, full disk access, accessibility, screen recording, etc.), TCC checks whether the user has granted permission. If not, the system prompts the user for consent.
TCC stores permissions in two SQLite databases:
/Library/Application Support/com.apple.TCC/TCC.db
System-wide. Protected by SIP. Only writable by tccd.
~/Library/Application Support/com.apple.TCC/TCC.db
Per-user. Protected by SIP. Only writable by tccd.
TCC identifies apps by their code-signing identity, not by path or name. If you re-sign an app, TCC treats it as a different app. If an app is unsigned, TCC may still grant access, but the identity is fragile.
Inspect It
# Check TCC database accessibility (you cannot read it directly without FDA)
ls -la "/Library/Application Support/com.apple.TCC/" 2>&1
ls -la ~/Library/Application\ Support/com.apple.TCC/ 2>&1
# List apps with Full Disk Access (requires FDA yourself to read the DB)
# Instead, check which apps have FDA from the GUI perspective
# System Settings > Privacy & Security > Full Disk Access
# Check your Terminal's accessibility status through a controlled test
# This AppleScript will ask for permission if not already granted:
osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>&1
Operator Relevance
TCC is one of the most important boundaries for an operator to understand because it controls access to the data you are most likely to target during an engagement (keychain, browser data, screen content, microphone, camera, and files in protected directories).
Key operator facts:
- TCC prompts require user interaction. If your operation cannot afford a
visible prompt, TCC-protected data is off limits through the normal API.
- FDA (Full Disk Access) is the highest TCC privilege. With FDA, a process
can read protected data directly from the filesystem, bypassing the
higher-level TCC APIs.
- TCC identity is based on code signature. If you modify or re-sign a binary,
it gets a new identity, and previously granted permissions are lost.
- Some TCC protections are per-user, not system-wide. The same app may have
different permissions depending on which user launched it.
- Historical TCC bypasses exist but are patched aggressively. For this course,
TCC is a constraint to understand, not a target to bypass.
launchd
What It Is
launchd is PID 1 on macOS. It is the first process the kernel starts, and it is responsible for starting and managing every other process on the system. launchd is not only a service manager. It is a privilege boundary, a namespace boundary, and a persistence surface.
launchd operates in multiple domains:
System domain (/Library/LaunchDaemons, /System/Library/LaunchDaemons):
Runs as root. Services start at boot. Survives all user sessions.
User domain (~/Library/LaunchAgents):
Runs as the user. Starts at login. Survives only that user's session.
GUI domain (/Library/LaunchAgents):
Runs per-user at login. Managed by the GUI session infrastructure.
Global domain:
System-level services not tied to a specific user session.
Inspect It
# List all launchd domains and their services
launchctl list 2>&1 | head -20
# Show the user domain
launchctl print gui/$(id -u)/ 2>&1 | head -40
# Show the system domain
launchctl print system/ 2>&1 | head -40
# List all LaunchDaemons on the system
ls -la /Library/LaunchDaemons/ 2>&1
ls -la /System/Library/LaunchDaemons/ 2>&1 | head -20
# List your own LaunchAgents
ls -la ~/Library/LaunchAgents/ 2>&1
Operator Relevance
launchd is the most important process on macOS for an operator. It is the mechanism for persistence (Phase 07), privilege escalation (Phase 06), and process execution under controlled identity. Understanding launchd domains is required to understand:
- Where to install a persistence mechanism (user vs system domain).
- Which user context the persisted process will execute under.
- Whether the mechanism survives logout, reboot, or both.
- What launchctl commands can enumerate or manipulate services.
- How Background Task Management (BTM) monitors login items and launch agents
for user approval.
Every persistence technique in this course involves launchd in some form. This module gives you the domain model. Phase 07 gives you the technique catalog.
SIP (System Integrity Protection)
What It Is
SIP is a kernel-level restriction that makes critical system locations read-only, even for root. It is not a process boundary. It is a filesystem immutability boundary enforced by the kernel before any process, regardless of UID, can write to protected paths.
Protected locations include:
/System
/bin
/sbin
/usr (except /usr/local)
/Applications (system apps pre-installed by Apple)
Components of the TCC database path
Kernel extensions and driver paths
SIP also restricts:
- Loading unsigned kernel extensions.
- Attaching to system processes for debugging (unless explicitly configured).
- Writing to the system TCC database.
- Modifying NVRAM boot arguments.
Inspect It
# Check SIP status (the command you will run most often)
csrutil status
# Detailed SIP configuration
csrutil status 2>&1
# Which filesystems are SIP-protected
mount | grep "read-only"
SIP can only be disabled from Recovery Mode. On a production or engagement Mac, assume SIP is fully enabled. The operator skill is knowing what is protected and working around those protections, not attempting to disable SIP during an operation.
Operator Relevance
- SIP means you cannot write to /System, /bin, /sbin, or /usr (except
/usr/local). Plan your file placement accordingly.
- SIP protects the TCC database. You cannot directly modify privacy
permissions by editing the database file.
- SIP restricts which processes can be debugged. System processes are
off-limits for LLDB or Frida without partial SIP disablement, which
requires a reboot into Recovery Mode.
- SIP status is trivially queried. If you disable it during an engagement,
that fact is immediately visible to any defender who checks.
For this course, SIP is always assumed to be fully enabled on the researcher's Mac. Techniques that require SIP to be disabled are called out explicitly and are taught as concept modules, not lab modules.
MDM and Configuration Profiles
What It Is
Mobile Device Management (MDM) and configuration profiles are the enterprise policy overlay on macOS. An organization can use MDM to:
- Enforce Gatekeeper settings (app allowlist/blocklist).
- Deploy certificates and network configurations.
- Restrict system preferences and security settings.
- Deploy managed login items and LaunchAgents.
- Enforce FileVault, firewall, and password policies.
- Install and remove applications.
- Query device state (OS version, installed apps, certificates, profiles).
MDM policy sits above local administrator policy. Even if the user has admin rights, MDM-enforced settings may be immutable.
Inspect It
# Check if the Mac is enrolled in MDM
profiles status -type enrollment 2>&1
# List installed configuration profiles
profiles list 2>&1 | head -30
# Check for specific profile types
profiles show -type configuration 2>&1 | head -30
Most personal Macs show "No" for MDM enrollment. A corporate Mac will show the MDM server URL, enrollment status, and profile count. For an operator, this is one of the highest-value discovery commands in the course.
Operator Relevance
On an MDM-managed Mac, your operator assumptions must change:
- Gatekeeper may be locked to "App Store only" or a specific allowlist.
Your delivery format planning (Phase 01) must account for this.
- Configuration profiles can deploy LaunchAgents that survive reboot and
cannot be removed by the local user. This is a persistence surface (Phase 07)
and a detection surface (your unmanaged agent stands out).
- MDM can query installed applications, profiles, certificates, and
configuration. Any artifact you leave is potentially visible to the
management server.
- MDM can enforce network filtering and proxy settings. Your C2 channel
planning (Phase 11) must account for enterprise network controls.
- The presence of MDM is itself a discovery finding. The operator should
query it early and adjust the rest of the operation accordingly.
Unified Logging
What It Is
Unified Logging is the system-wide logging infrastructure. Every subsystem discussed in this module writes to it. Gatekeeper assessments, code-signing checks, TCC decisions, sandbox violations, AMFI denials, launchd service events, profile installations, and SIP protections all produce log entries.
Unified Logging is not a boundary. It is the evidence layer that records every boundary interaction. For an operator, it is both a discovery tool (what can I learn about this system?) and an OPSEC concern (what does this system record about my actions?).
Inspect It
# Recent log entries related to trust boundaries (last 15 minutes)
log show --last 15m --style compact \
--predicate 'subsystem BEGINSWITH "com.apple" AND (eventMessage CONTAINS[c] "Gatekeeper" OR eventMessage CONTAINS[c] "signing" OR eventMessage CONTAINS[c] "TCC" OR eventMessage CONTAINS[c] "sandbox" OR eventMessage CONTAINS[c] "profile")' \
2>&1 | head -30
On a machine that has been idle, this may return very little. That is normal. On a machine where someone just downloaded and opened an app, you will see syspolicyd, amfid, and possibly TCC entries.
Operator Relevance
There are two operator modes for Unified Logging, and you need both:
Discovery mode:
Use log predicates to understand what is running, what security tooling
is active, and what events are being recorded. Phase 04 covers this in
depth.
OPSEC mode:
Assume everything your technique does is recorded somewhere. The question
is not "did it log?" It is "which log, at what severity, with what
correlation potential, and can the security team find it?"
For the modules ahead: when a technique says "check the Unified Log for evidence of this behavior," this is the subsystem you are querying. When a technique says "this action is visible in the following log channels," Unified Logging is the infrastructure those channels live in.
How the Boundaries Interact
A single operator action can touch five or more boundaries simultaneously. Example: you download a signed, notarized app from a website, extract it, and launch it.
1. Browser downloads the ZIP. -> quarantine xattr attached
2. Archive Utility extracts the app. -> quarantine propagated
3. User double-clicks the app. -> LaunchServices invoked
4. Gatekeeper checks quarantine. -> syspolicyd assessment
5. Gatekeeper validates code signature. -> amfid consulted
6. Gatekeeper checks notarization ticket. -> stapled ticket validated
7. AMFI enforces code identity at exec. -> kernel enforcement
8. Hardened Runtime checks apply at launch. -> per-process flags enforced
9. App requests camera access. -> TCC prompt triggered
10. App writes to its container. -> sandbox containment checked
11. User approves camera access. -> TCC database updated
12. Every step generates log entries. -> Unified Logging records all
If any step fails, the chain breaks. The app may be blocked (Gatekeeper), killed (AMFI), sandboxed into uselessness (Sandbox), or denied the data it needs (TCC). Understanding each link means you can predict where a technique will hit resistance and what evidence it will create at each step.
Trust Boundary Reference Card
Save this as ~/macseclabs/evidence/0.2-trust-boundaries/boundary-reference.txt.
You will refer back to it often.
BOUNDARY INSPECTION COMMAND KEY FILE / SUBSYSTEM
────────────── ────────────────────────────────── ─────────────────────────────
Gatekeeper spctl --status /var/db/SystemPolicy
Notarization stapler validate <app> notarization ticket in bundle
Quarantine xattr -l <file> com.apple.quarantine xattr
Code Signing codesign -dv <binary> embedded LC_CODE_SIGNATURE
AMFI sysctl kern.bootargs amfid (daemon)
Hardened RT codesign -dv <binary> | grep flags runtime flag 0x10000
Sandbox codesign --entitlements <app> com.apple.security.app-sandbox
TCC ls /Library/.../com.apple.TCC TCC.db (SQLite, SIP-protected)
launchd launchctl print gui/$(id -u) /Library/LaunchDaemons, ~/Library/LaunchAgents
SIP csrutil status /System (read-only)
MDM profiles status -type enrollment /var/db/ConfigurationProfiles
Unified Log log show --last 15m /var/db/diagnostics, /var/db/uuidtext
MITRE ATT&CK Mapping
This module does not teach a single technique. It teaches the control surface that every later technique interacts with. The boundaries mapped here are foundational to the following ATT&CK techniques taught in later phases:
| Boundary | Related ATT&CK Techniques (taught later) |
|---|---|
| Gatekeeper / Quarantine / Notarization | T1553.001, T1553.002, T1204.002 |
| Code Signing / AMFI | T1553.002, T1553.006, T1574 |
| TCC | T1548.006, T1555.001 |
| Sandbox | T1068, T1612 |
| launchd | T1543.001, T1543.004, T1547.015 |
| SIP | T1553.005, T1685 |
| MDM / Profiles | T1098, T1547.015, T1072 |
| Unified Logging | T1070, T1685, T1654 |
When you reach the modules that teach these techniques, the boundary knowledge from this module will tell you why the technique works, what stops it, and what evidence it creates.
What You Should Learn
After completing this module you should be able to:
- List the major macOS trust boundaries and describe what each enforces.
- Inspect Gatekeeper, code signing, SIP, TCC, launchd, and MDM state on your own Mac using native commands.
- Explain the difference between ad hoc, Developer ID, and notarized code signing states.
- Describe how quarantine propagates (or does not propagate) through common file operations.
- Explain why Hardened Runtime blocks DYLD_INSERT_LIBRARIES and unsigned dylib loading.
- Understand which launchd domain controls persistence at the user level vs the system level.
- Recognize when MDM is present on a target and what that presence implies for operator planning.
- Use Unified Logging to query security-relevant events.
- Predict which boundaries a given operator action will interact with.
Researcher Checkpoint
Before moving to module 0.3, verify:
- You ran
csrutil statusand know whether SIP is enabled on your lab Mac. - You ran
spctl --statusand know your Gatekeeper assessment policy. - You know the code-signing state of
/bin/zsh(unsigned? ad hoc? Developer ID?). If you had to guess before runningcodesign -dv, did you guess right? - You inspected your launchd domains and can distinguish between LaunchDaemons and LaunchAgents.
- You know whether your Mac is MDM-enrolled (
profiles status -type enrollment). - You can explain, to yourself, why an unsigned dylib cannot be injected into a hardened, notarized app. Which two boundaries team up to prevent that?
- You collected evidence in
~/macseclabs/evidence/0.2-trust-boundaries/.
If any answer is unclear, re-run the inspection commands on your own machine. The best way to learn these boundaries is to query them directly.
This is one free module of many.
Get every module across all ten phases, with working code and the detection engineering to catch each technique.
Get full access