5. Endpoint Security Overview
The Endpoint Security (ES) API enables three primary classes of features: user space clients, path muting / inversion, and notification / authorization of system events. To date ES eventing covers a wide array of system activity: Process, File metadata, Memory mapping, Login, Background Task Management (BTM), XProtect, etc the list goes on. For a process to connect to ES it must be properly code signed with the entitlement to do so -- see ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED
. Common examples of Endpoint Security solutions include: modern Endpoint Detection and Response (EDR) sensors, endpoint monitoring and troubleshooting tools, binary authorization, etc.
-
Message: A notification sent by
endpointsecurity.kext
to your security agent. Messages are structs of typees_message_t
(ESMessage.h
) and have a few key top level properties:-
action_type
: Specifying an AUTH or NOTIFY event (see below) -
event
: The security event to deliver to subscribed clients (e.g. create file) -
process
: The process which has initiated the activity resulting in the event -
thread
: The thread that performed the action resulting in an event
-
- Events: A notification your client receives of system activity (e.g. a user being added to an Open Directory node). There are two classes of events: notification and authorization (allowing your security agent to allow / deny an action). Additionally, there's a split between events emitted by XNU and by user space libraries.
-
Client: A C struct of type
es_client_t
(ESClient.h
) which enables your security agent to perform actions. NOTE: Full Disk access is a requirement of the hosting process! For more info seeES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED
. One of the first things that security agents will do is create the ES client:es_new_client()
. The client exposes functionality enabling it to act as a user space security agent. -
Event subscriptions: Clients ask to be notified for a set of ES events (e.g.
ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN
,ES_EVENT_TYPE_AUTH_EXEC
,ES_EVENT_TYPE_AUTH_MMAP
). When a client subscribes to events they will be notified by ES when any action resulting in that event occurs. - Target path: The logical path specified in the event's structure (not the message's).
-
Muting / unmuting: Instruct ES that you do not want to be notified of events matching a set of critieria. Globally or per event.
- By process initiating path / target path (literal or prefix)
- By process audit token
-
Inversion: Essentially selection instead of muting. Here developers have the option to invert muting based on: path, process audit token, or target path (the path specified in the event not the initiating
process
in the message). -
Authorizing system activity: Clients can respond with
ES_AUTH_RESULT_DENY
to prevent the action orES_AUTH_RESULT_ALLOW
to allow it. - Subscriptions: Defines a set of events for your security agent to monitor.
Developers of security agents have two primary ways to connect to ES: System Extension (act as a user space KEXT / driver) or Launch Deamon (act as a regular System scope daemon). Launch Agents are not available here because to connect to ES the process must also be running as root -- see ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED
. By taking the System Extension option developers will benefit from System Integrity Protection's (SIP) "rootless" defense against tampering. Running your agent as a Launch Daemon opens your implementation up to potential tampering. However, by running in this state developers do not need to deal with the somewhat tedious System Extension installation process. Additionally, for research use cases it's not necessary for either -- as long as the above requirements for entitlement and privilege are satisfied: see our AtomicESClient example and Apple's own /usr/bin/eslogger
utility.
Uninstalling System extensions
Even if the root user wanted to remove a running Endpoint Security Extension they cannot in most cases. However, there are a few different options:
- Trigger the developers API call to remove the system extension by following uninstall guidance.
- By default the API call to remove the system extension will be triggered if the user deletes the hosting app using the Finder.
- Non-EDR products can likely be removed in this way
- Boot into recoveryOS (detailed above) and use
systemextensionsctl
to uninstall it.- Only necessary if SIP is enabled.
ES is designed to facilitate "user space security agents" which for all the reasons listed above is a good idea: OS integrity, security, and user experience being some standouts. Beyond simply alerting us of new events ES allows us to take things one step further. There are two classes of event types in ES: notification and authorization. Notification event types (the majority) as their name entails simply "notify" the user of system activity. Authorization events on the other hand enable your security agent to authorize the activity -- very similar to KAuth (discussed above). Additionally, for your user space client to successfully register with ES you must be properly entitled with the com.apple.developer.endpoint-security.client
entitlement (available on application from Apple). This restriction is very similar to Microsoft's Protected Process Light (PPL) level for ELAM (Early Launch Antimalware) drivers. Additionally, ELAM developers need to be a member of the Microsoft Virus Initiative (MVI) and must have their driver be signed by the Windows Hardware Quality Lab (WHQL).
ES is architected as: user land libraries: libEndpointSecuritySystem.dylib
(for entitled system eventing) / libEndpointSecurity.dylib
(for developers), a user land daemon /usr/libexec/endpointsecurityd
and the KEXT / “driver”: EndpointSecurity.kext
. Apple, by architecting ES in this way enables defense-in-depth by proxying requests from the user space clients through Apple services to the kernel drivers. Now, you might ask yourself: "so where then do events come from?". The answer is two fold:
-
Emitted by the kernel: events such as
ES_EVENT_TYPE_NOTIFY_SETGID
andES_EVENT_TYPE_NOTIFY_EXEC
. These events are emitted by hooking the fundamental system calls responsible. Such as:setgid()
for setting a file's GID andexecve(2)
/posix_spawn(2)
for process execution. - Or... by the responsible user space library/binary. These are decently obvious to identify. For example
ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD
has no corresponding syscall (as it's a much higher level abstraction). Therefore, this event is emitted directly bybackgroundtaskmanagementd
and sent toendpointsecurity.kext
to notify subscribed clients.
Un-entitled processes are strictly disallowed from emitting ES events into endpointsecurity.kext
. Binaries which are allowed will be signed with entitlements of the form: com.apple.private.endpoint-security.submit.*
.
We have you covered! Please take a look at AtomicESClient, this project's goal is to get users up and going with an es_client_t
of their own with minimal effort!
As a quick overview:
// @discussion: This ES event will give you basic *high level* process execution information.
public var esEventSubs: [es_event_type_t] = [
ES_EVENT_TYPE_NOTIFY_EXEC
]
var client: OpaquePointer?
// MARK: - New ES client
// Reference: https://developer.apple.com/documentation/endpointsecurity/client
let result: es_new_client_result_t = es_new_client(&client){ _, event in
// Here is where the ES client will "send" events to be handled by our app -- this is the "callback".
completion(EndpointSecurityClientManager.eventToJSON(value: ExampleESEvent(fromRawEvent: event)))
}
// MARK: - Event subscriptions
// Reference: https://developer.apple.com/documentation/endpointsecurity/3228854-es_subscribe
if es_subscribe(client!, esEventSubs, UInt32(esEventSubs.count)) != ES_RETURN_SUCCESS {
print("[ES CLIENT ERROR] Failed to subscribe to core events! \(result.rawValue)")
es_delete_client(client)
exit(EXIT_FAILURE)
}