Skip to content

4. Security Data Sources

Brandon Dalton edited this page Dec 1, 2023 · 1 revision

(Legacy) DTrace

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.

(Legacy) Kernel Authorization KPI

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:

KAuth overview

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:

  1. 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);
  2. 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);
  3. 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;
    }

Discussion

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.

Endpoint Security System Extensions

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).