-
Notifications
You must be signed in to change notification settings - Fork 47
4. Security Data Sources
DTrace was an easy way for developers to get process monitoring capabilities on macOS. However, since the introduction of System Integrity Protection (SIP) in 2015 this method has since been closed off. SIP (rootless) is a powerful security control on macOS whose role encompass:
- Disallowing unauthorized modification of system directories and files even from the root user
- Disallowing code injection and runtime attachment: debugging / ("meaningful" DTrace)
- Loading KEXTs
Previously solutions like BlockBlock utilized programatic DTrace probes on the fork / exec system calls + posix_spawn()
to gain process level resolution.
The deprecated Kauth KPI (introduced with Mac OS X 10.4 Tiger) was historically used for EDR products: monitoring and responding to activity on the endpoint. Here we'll quickly review KAuth fundamentals which are core to modern Endpoint Security (albeit up a level of abstraction). Kauth defines several classes of telemetry known as "activity scopes" (kauth.h
) and each scope holds a group / table of listeners (effectively callbacks) which then enable the developer to authorize activity. For, example the VNODE
scope is one of the most critical as it notifies us of process image executions: KAUTH_VNODE_EXECUTE
.
KAuth Scope | ID | Description |
---|---|---|
KAUTH_SCOPE_GENERIC |
com.apple.kauth.generic |
Generic scope |
KAUTH_SCOPE_PROCESS |
com.apple.kauth.process |
Process/task scope. |
KAUTH_SCOPE_VNODE |
com.apple.kauth.vnode |
Vnode operation scope. Prototype for vnode_authorize is in vnode.h . |
KAUTH_SCOPE_FILEOP |
com.apple.kauth.fileop |
File system operation scope. |
The listener / subscriber (of type kauth_listener
) is defined as the following:
struct kauth_listener {
TAILQ_ENTRY(kauth_listener) kl_link;
const char * kl_identifier;
kauth_scope_callback_t kl_callback;
void * kl_idata;
};
Take for example, when the user executes: /bin/cat
from their shell. fork()
/ execve()
system calls (process image execution) are performed through libSystem.dylib
, context switch to kernel mode occurs, and the security product's VNODE
scope listener(s) will be notified of the event through a kauth_action_t
corresponding to the KAuth event type. The security product then has the ability to authorize, deny, or defer the action. Security products have the following options when performing authorization on events:
Event | Description |
---|---|
KAUTH_RESULT_DEFER |
Allow another listener to make a decision |
KAUTH_RESULT_ALLOW |
Allow the action |
KAUTH_RESULT_DENY |
Deny the action |
Security products using this model follow this general setup process:
-
Register a syscall handler via a sysctl OID (Object ID):
SYSCTL_OID
static int SysctlHandler( struct sysctl_oid * oidp, void * arg1, int arg2, struct sysctl_req * req) SYSCTL_OID( _kern, // parent OID OID_AUTO, // sysctl number, OID_AUTO means we're only accessible by name com_example_apple_samplecode_kext_KauthORama, // our name CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_KERN, // we're a string, more or less gConfiguration, // sysctl_handle_string gets/sets this string sizeof(gConfiguration), // and this is its maximum length SysctlHandler, // our handler "A", // because that's what SYSCTL_STRING does "" // just a comment ); sysctl_register_oid(&sysctl__kern_com_example_apple_samplecode_kext_KauthORama);
-
Installs listeners
kauth_listener_t
for chosen scopes. This effectively means setting the appropriate callbacks. For example:kauth_scope_callback_t callback; gListenerScope = OSMalloc( (uint32_t) (scopeLen + 1), gMallocTag); // Checks the incoming scope and set the appropriate callback. if ( strcmp(gListenerScope, KAUTH_SCOPE_VNODE) == 0 ) { callback = VnodeScopeListener; } // Start monitoring the scope. This internally calls `kauth_add_callback_to_scope(...)` which updates the `ks_listeners` table. gListener = kauth_listen_scope(gListenerScope, callback, NULL);
-
Process incoming events by defining a custom listener function:
static int VnodeScopeListener( kauth_cred_t credential, void * idata, kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3 ) { // ... // Image path err = CreateVnodePath(vp, &vpPath); // Parent path err = CreateVnodePath(dvp, &dvpPath); // Human readable vnode action bitmap err = CreateVnodeActionString(action, isDir, &actionStr, &actionStrBufSize); // ... printf( "scope=" KAUTH_SCOPE_VNODE ", action=%s, uid=%ld, vp=%s, dvp=%s\n", actionStr, (long) kauth_cred_getuid(vfs_context_ucred(context)), (vpPath != NULL) ? vpPath : "<null>", (dvpPath != NULL) ? dvpPath : "<null>" ); // Take no action return KAUTH_RESULT_DEFER; }
Since the introduction of the successor the Endpoint Security APIs, System Extensions (under the Catalina model) and iBoot's Full Security level by default these are largely legacy technologies. This can further be seen directly in the XNU source code where the authors define the #define __kpi_deprecated(_msg)
macro (a preprocessor directive) used to emit a warning at compile time and uses this verbiage: __kpi_deprecated("Use EndpointSecurity instead")
.
For a deeper look of this topic we'll point the reader to Scott Knight's excellent analysis from 2018 on Kauth from the perspective of understanding / reversing McAfee's endpoint security offering: Virus scanning on macOS. Additionally, Patrick Wardle's "Monitoring Process Creation via the Kernel" blog series is a treasure trove. Lastly, for more complete sample code implementing the KAuth KPI Apple's "KauthORama" released in 2014 "enables" you to register a listener for any scope. Reading the sample code should give you a good understanding of experience developing security agents for macOS pre-Catalina.
In the past, Apple allowed third party code to run in kernel space as we discussed above. These took the form of KEXTs (Kernel Extensions). From the perspective of platform security this super power is not ideal and is overly permissive. Additionally, there's no guarantee that the developers of a given KEXT held their code to the same standard as Apple when developing XNU. These principle issues manifest themselves in the form of adverse system states such as those relating to: performance, security, and integrity.
Apple's CoreOS team introduced us to System Extensions back in 2019 with the release of Catalina. System Extensions rely on IOKit drivers (KEXTs) written by Apple to proxy their privileged requests through. It's then the IOKit driver which handles the privileged operations -- not the third party code. This small distinction enables a more performant, stable, and secure Mac. Some key reasons for this are:
- Developers can now write their "privileged" code in a memory safe language like Swift (making classes of vulnerabilities like memory corruption far less likely -- a top zero day offender). Not only this, but also implementing in a higher level language dramatically improves the development experience for many.
- System panics/crashes after OS updates are less likely to occur. Previously, updating macOS when not every KEXT supported the new OS could cause unpredictable behavior like kernel panics.
- Third party code no-longer has kernel privileges, but instead relies on the interfaces exposed by the System Extension API. This imposes intrinsic limitations on the ability for a malicious System Extension to do harm (from a system integrity perspective).
Modern replacement for the deprecated Kauth KPI, OpenBSM audit trail, and the unsupported Mandatory Access Control Framework (MACF). Endpoint Security's principle job is to facilitate user space "agents" which can monitor and authorize a wide range of endpoint actions. Traditionally, security agents needed to reside in kernel space to gain this super power (as they mostly do on Windows currently).