MacSecLabs
0.3Free preview

Module 0.3: Endpoint Security as a Detection Source

The Endpoint Security framework, which we will call ES throughout this course, is the single most important telemetry source on macOS. Modern macOS EDR products, including CrowdStrike Falcon, SentinelOne, Microsoft Defender for Endpoint, Jamf Protect, and others, commonly rely heavily on ES as a core data pipeline. If you understand ES, you understand the foundation behind most detections your EDR can produce.

This module explains ES from the detection engineer perspective. You will learn what ES is, how it delivers events, the critical difference between AUTH and NOTIFY events, the event types that matter most for detection, and most importantly, what ES cannot see. That last point, the blind spots, is what separates an average detection engineer from an excellent one.

What ES Actually Is

ES is a kernel subsystem that delivers structured event messages to user space clients. Let us unpack what that means, because each word matters.

Kernel subsystem. ES runs inside the kernel, not in user space. This means it sees operations at the lowest level, before they complete. When a process calls execve() to launch a new program, ES sees the call in the kernel before the program runs. When a process opens a file, ES sees the open before the file is read. This is powerful because it means ES can observe and even block operations in real time.

Structured event messages. ES does not deliver raw log lines or unstructured text. It delivers C structs with typed fields. An ES event for process execution contains the process path, the process identifier, the audit token (which encodes the user, group, and signing identity), the arguments, the environment, and the parent process information. This structure makes it straightforward to write detection logic because you have well defined fields to match against.

User space clients. ES delivers events to applications running in user space. These applications are called ES clients. An ES client subscribes to the event types it wants to see, receives messages from the kernel through a Mach port queue, processes them, and optionally responds. For AUTH events, the client responds with an allow or deny decision. For NOTIFY events, the client simply consumes the event.

How ES Delivers Events

When something happens on the system that an ES client has subscribed to, the kernel constructs an event message and delivers it to the client. The delivery path works like this:

  1. A process performs an action, like calling execve() to launch a new program.
  2. The kernel intercepts the action in the syscall path.
  3. The kernel checks whether any ES client is subscribed to the corresponding event type.
  4. If a client is subscribed, the kernel constructs an es_message_t struct containing the event details.
  5. The kernel writes the message to the client's Mach port queue.
  6. For AUTH events, the kernel waits for the client to respond before allowing or blocking the action.
  7. For NOTIFY events, the kernel delivers the message and continues without waiting.

The Mach port queue is the critical piece. Each ES client has a dedicated queue. The kernel writes messages to the queue, and the client reads from it. The queue has a finite size. If the client processes messages slower than the kernel produces them, the queue fills up. What happens when the queue is full depends on the event type, and this is where the AUTH versus NOTIFY distinction becomes critical.

AUTH Versus NOTIFY: The Most Important Distinction

This is the single most important concept in ES, and many detection engineers do not understand it. Let us make it clear.

AUTH events (short for authorization) are events where the kernel asks the ES client for permission before completing the operation. The kernel pauses the calling process and waits for the client to respond with either "allow" or "deny." If the client says deny, the operation fails. If the client says allow, the operation proceeds.

For example, ES_EVENT_TYPE_AUTH_EXEC is an authorization event for process execution. When a process calls execve(), the kernel pauses the exec, sends an AUTH_EXEC message to every subscribed client, and waits. The client examines the message and responds. If the client denies, the process does not execute.

The implication for queue overflow is important. If the client's queue is full when the kernel tries to deliver an AUTH event, the kernel cannot deliver the message. But it also cannot proceed without a response, because the whole point of AUTH is that the client makes the decision. So the kernel waits. The calling process is stalled until the queue drains. This means AUTH events create backpressure. The system slows down, but no events are lost.

NOTIFY events are events where the kernel informs the client after the fact. The kernel does not wait for a response. It delivers the message and continues. If the queue is full when the kernel tries to deliver a NOTIFY event, the kernel drops the message. Silently. No error. No notification to anyone. The event happened, but the client never sees it.

For example, ES_EVENT_TYPE_NOTIFY_EXEC is a notification event for process execution. The kernel allows the exec to proceed, then attempts to notify subscribed clients. If the queue is full, the notification is lost. The process executed, but the EDR never recorded it.

This distinction has massive detection implications. An EDR that subscribes primarily to NOTIFY events can be saturated. An attacker who generates events faster than the EDR can process them can fill the queue and cause events to be dropped. We demonstrate this exact technique in Phase 8, where we show how an attacker can saturate the ES queue to hide subsequent activity.

Every detection engineer needs to know which of their EDR's events are AUTH and which are NOTIFY. Unfortunately, most EDR products do not expose this information directly. But understanding the mechanism helps you reason about what your EDR might miss under load.

The Event Types That Matter for Detection

ES defines over 80 event types as of macOS 26.5. You do not need to memorize all of them. You need to know the ones that appear most often in detections. Here are the most important event types grouped by category:

Process events:

  • AUTH_EXEC and NOTIFY_EXEC: Process execution. The most commonly used event type in detection. Every process that launches generates an exec event.
  • NOTIFY_FORK: Process fork. A parent process creates a child process.
  • NOTIFY_EXIT: Process exit. Useful for correlating short lived processes.

File events:

  • AUTH_OPEN and NOTIFY_OPEN: File open. Tells you which process opened which file and with what permissions.
  • NOTIFY_CLOSE: File close.
  • AUTH_RENAME and NOTIFY_RENAME: File rename. Important for tracking malware that renames itself.
  • AUTH_UNLINK and NOTIFY_UNLINK: File deletion. Important for detecting cleanup of evidence.

Network events (important limitation): ES does not provide TCP or network socket connection events. The only socket-related events are ES_EVENT_TYPE_NOTIFY_UIPC_BIND and ES_EVENT_TYPE_NOTIFY_UIPC_CONNECT, which cover Unix domain IPC sockets (local inter-process communication), not TCP/IP. This is a significant visibility gap: ES cannot tell you when a process opens a TCP connection to an external server. For network telemetry, you must rely on other sources: the Network Extension framework, packet filter (pf) logs, the netstat command output, and DNS monitoring via mDNSResponder unified log events. We cover these alternative network sources in Module 0.5.

Memory events:

  • AUTH_MMAP and NOTIFY_MMAP: Memory mapping. Used to detect code injection.
  • AUTH_MPROTECT and NOTIFY_MPROTECT: Memory protection change. Setting memory to executable is a strong indicator of code injection.

Identity and authentication events:

  • NOTIFY_AUTHENTICATION: Authentication events.
  • NOTIFY_LOGIN_LOGIN: User login.
  • NOTIFY_LOGIN_LOGOUT: User logout.

A production EDR typically subscribes to 20 to 30 of these event types. The full list is available in the ES header files in the macOS SDK.

What ES Cannot See: The Blind Spots

This is the section that matters most. ES is powerful, but it is not omniscient. It has blind spots that sophisticated attackers exploit. As a detection engineer, you must know where these blind spots are so you can build compensating detections or, at minimum, document the visibility gap for your stakeholders.

Mach traps. ES monitors syscalls through the BSD layer. It hooks into the syscall dispatch path. But macOS has a second kernel dispatch mechanism: Mach traps. Mach traps use a separate dispatch table (mach_trap_table) that ES does not hook. Operations like task_set_exception_ports (registering exception handlers), mach_msg (Mach IPC), and thread_set_state (modifying thread registers) all go through Mach traps. ES does not generate events for these operations. This is the foundation of the Mach exception covert channel we built in the Red Team track and detect in Phase 8 of this course.

The seq_num gap. Even when ES does deliver events, it can drop NOTIFY events when the queue is full. Apple provides a seq_num field on every event message that lets a client detect drops by tracking sequence number gaps. But detecting the drop is not the same as having the data. If an event was dropped, the data is gone. A well designed EDR monitors seq_num and alerts on gaps. A poorly designed EDR does not, and silently misses events.

Events inside the sandbox. ES sees events from sandboxed processes, but the sandbox can restrict what operations a process attempts, which limits the events ES can observe. An attacker who operates within a sandboxed context generates fewer events because the sandbox blocks many of the operations that would trigger ES.

Kernel resident code. If an attacker has a kernel extension or kernel level implant (which requires bypassing SIP on modern macOS), they operate below the ES layer. ES is a kernel subsystem, but a kernel attacker can potentially disable or subvert it. This is an extreme scenario but one you should be aware of for high threat environments.

How This Shapes Your Detection Strategy

Knowing the blind spots shapes how you build detections. For every technique you want to detect, ask:

  1. Does ES generate an event for this behavior? If yes, which event type?
  2. Is the event AUTH or NOTIFY? If NOTIFY, can the attacker saturate the queue and drop it?
  3. Does the behavior go through a Mach trap instead of a syscall? If so, ES will not see it directly.
  4. What compensating telemetry exists? Unified logs, file system artifacts, network logs, or forensic artifacts may fill the gap.

When ES does not cover a behavior, you do not give up. You layer other telemetry sources. This is why Phase 0.4 covers Unified Logging, and Phase 9 covers forensic artifacts. The combination of ES, unified logs, and file system artifacts gives you coverage that no single source provides alone.

What Comes Next

Module 0.4 covers Unified Logging, the always available telemetry source that fills many of the gaps ES leaves. You will learn how to query the unified log, which subsystems produce security relevant events, and how to combine unified log data with ES events to build more complete detections.

By the end of Phase 0, you will have the complete telemetry picture: what ES sees, what Unified Logging sees, what neither sees, and how to combine them. That foundation supports every detection rule you write in the rest of the course.

Continue macOS Detection Engineering

These foundation modules are open as a preview. Full modules, lab downloads, progress tracking, and certificate workflow require active course access.

Get full access