Skip to content

Instantly share code, notes, and snippets.

@tommy-dong
Forked from Omar-Ikram/EndpointSecurityDemo.m
Created September 6, 2024 02:03
Show Gist options
  • Save tommy-dong/9bce6907c4bf6b12bc4cb07bcfb95478 to your computer and use it in GitHub Desktop.
Save tommy-dong/9bce6907c4bf6b12bc4cb07bcfb95478 to your computer and use it in GitHub Desktop.
A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.2.1 (21D62)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
</dict>
</plist>
//
// main.m
// EndpointSecurityDemo
//
// Created by Omar Ikram on 17/06/2019.
//
#import <Foundation/Foundation.h>
#import <EndpointSecurity/EndpointSecurity.h>
#import <bsm/libbsm.h>
#import <signal.h>
/*
A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 Beta 1
(19A471t)
At the time of writing, Apple's documentation is a bit lacking:
https://developer.apple.com/documentation/endpointsecurity/
Fortunately the headers provide enough information to build a working implementation.
Disclaimer:
This code is provided as is and is only intended to be used for illustration purposes. This code is
not production-ready and is not meant to be used in a production environment. Use it at your own risk!
Setup:
1. Build on Xcode 11, with macOS deployment target set to 10.15.
2. Codesign with entitlement 'com.apple.developer.endpoint-security.client'.
Runtime:
1. Test environment should be a macOS 10.15 machine which has SIP disabled (best to use a VM).
2. Run the demo binary in a terminal as root (e.g. with sudo).
3. Terminal will display messages related to subscribed events.
4. The demo will block the top binary and Calculator app bundle from running.
5. CTL-C to exit.
Known Issues:
Apple has documented known issues with EndpointSecurity in the macOS Catalina 10.15 Beta Release
Notes:
https://developer.apple.com/documentation/macos_release_notes/macos_catalina_10_15_beta_release_notes
Other Issues:
1. Calling es_delete_client will cause a kernel panic (from user mode!).
2. AUTH decsions appear to be cached between program invocations. This can be resolved by calling
es_clear_cache to explicitly clear the cache.
3. Type discrepancy in extracting command line arguments. es_exec_arg_count returns uint64_t, but
es_exec_arg only takes uint32_t.
*/
#pragma mark - Logging
#define BOOL_VALUE(x) x ? "Yes" : "No"
#define LOG_INFO(fmt, ...) NSLog(@#fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) NSLog(@"ERROR: " @#fmt, ##__VA_ARGS__)
NSString* event_type_str(const es_event_type_t event_type) {
// This can be expanded to include the other ES_EVENT_TYPE_* constants
switch(event_type) {
case ES_EVENT_TYPE_AUTH_EXEC: return @"ES_EVENT_TYPE_AUTH_EXEC";
case ES_EVENT_TYPE_NOTIFY_FORK: return @"ES_EVENT_TYPE_NOTIFY_FORK";
default: return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d", event_type];
}
}
void log_proc(const NSString* header, const es_process_t *proc) {
LOG_INFO("%@:", header);
LOG_INFO(" proc.pid: %d", audit_token_to_pid(proc->audit_token));
LOG_INFO(" proc.ppid: %d", proc->ppid);
LOG_INFO(" proc.original_ppid: %d", proc->original_ppid);
LOG_INFO(" proc.is_platform_binary: %s", BOOL_VALUE(proc->is_platform_binary));
LOG_INFO(" proc.is_es_client: %s", BOOL_VALUE(proc->is_es_client));
LOG_INFO(" proc.signing_id_truncated: %s", BOOL_VALUE(proc->signing_id_truncated));
LOG_INFO(" proc.team_id_truncated: %s", BOOL_VALUE(proc->team_id_truncated));
LOG_INFO(" proc.signing_id: %s", proc->signing_id);
LOG_INFO(" proc.team_id: %s", proc->team_id);
// proc.cdhash
NSMutableString *hash = [NSMutableString string];
for(uint32_t i = 0; i < CS_CDHASH_LEN; i++) {
[hash appendFormat:@"%x", proc->cdhash[i]];
}
LOG_INFO(" proc.cdhash: %@", hash);
LOG_INFO(" proc.file.path: %s", proc->file.path);
}
void log_event_message(const es_message_t *msg) {
LOG_INFO("--- EVENT MESSAGE ----");
LOG_INFO("event_type: %@ (%d)", event_type_str(msg->event_type), msg->event_type);
LOG_INFO("time: %lld.%.9ld", (long long) msg->time.tv_sec, msg->time.tv_nsec);
LOG_INFO("mach_time: %lld", (long long) msg->mach_time);
log_proc(@"proc", &msg->proc);
// Type specific logging
switch(msg->event_type) {
case ES_EVENT_TYPE_AUTH_EXEC: {
log_proc(@"event.exec.proc", &msg->event.exec.proc);
// Log program arguments
uint64_t argCount = es_exec_arg_count(&msg->event.exec);
LOG_INFO("event.exec.arg_count: %llu", argCount);
// Extract each argument and log it out
for(uint32_t i = 0; i < argCount; i++) {
// es_exec_arg_count returns uint64_t, but es_exec_arg only takes uint32_t?
es_token_t arg = es_exec_arg(&msg->event.exec, i);
NSData *argData = [NSData dataWithBytes:arg.data length:arg.size];
LOG_INFO("arg %d: %@", i, [NSString stringWithUTF8String:[argData bytes]]);
}
}
break;
case ES_EVENT_TYPE_NOTIFY_FORK: {
log_proc(@"event.fork.child", &msg->event.fork.child);
}
break;
case ES_EVENT_TYPE_LAST:
default: {
// Not interested
}
}
LOG_INFO("");
}
#pragma mark - Endpoint Secuirty Demo
es_client_t *g_client = nil;
NSSet *g_blockedPaths = nil;
// Clean-up before exiting
void sig_handler(int sig) {
LOG_INFO("Tidying Up");
if(g_client) {
es_unsubscribe_all(g_client);
//es_delete_client(g_client); // CRASHES THE BOX! (´・_・`)
}
LOG_INFO("Exiting");
exit(EXIT_SUCCESS);
}
// Simple handler to make AUTH (allow or block) decisions.
// Returns either an ES_AUTH_RESULT_ALLOW or ES_AUTH_RESULT_DENY.
es_auth_result_t auth_event_handler(const es_message_t *msg) {
if(ES_EVENT_TYPE_AUTH_EXEC == msg->event_type) {
NSString *path = [NSString stringWithUTF8String:msg->event.exec.proc.file.path];
// Block if path is in our blocked paths list
if([g_blockedPaths containsObject:path]) {
LOG_INFO("BLOCKING: %@", path);
return ES_AUTH_RESULT_DENY;
}
}
return ES_AUTH_RESULT_ALLOW;
}
int main(int argc, const char * argv[]) {
signal(SIGINT, &sig_handler);
@autoreleasepool {
// List of paths to be blocked.
// For this demo we will block the top binary and Calculator app bundle.
g_blockedPaths = [NSSet setWithObjects:
@"/usr/bin/top",
@"/System/Applications/Calculator.app/Contents/MacOS/Calculator",
nil];
// Create a new client with an associated event message handler.
// Requires 'com.apple.developer.endpoint-security.client' entitlement.
es_new_client_result_t res =
es_new_client(&g_client, ^(es_client_t *clt, const es_message_t *msg) {
log_event_message(msg);
// Handle subscribed AUTH events:
// For 'ES_EVENT_TYPE_AUTH_EXEC' events, the associated app will block until
// an es_auth_result_t response is sent or this app exits.
if(ES_ACTION_TYPE_AUTH == msg->action_type) {
es_respond_result_t res =
es_respond_auth_result(clt,
msg,
auth_event_handler(msg),
false // Cache flag doesn't work in 10.15 Beta 1
);
if(ES_RESPOND_RESULT_SUCCESS != res) {
LOG_ERROR("es_respond_auth_result: %d", res);
}
}
});
if(ES_NEW_CLIENT_RESULT_SUCCESS != res) {
if(ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED == res) {
LOG_ERROR("Application requires 'com.apple.developer.endpoint-security.client' entitlement");
} else if(ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED == res) {
LOG_ERROR("Application needs to run as root (and SIP disabled).");
} else {
LOG_ERROR("es_new_client: %d", res);
}
return 1;
}
// Subscribe to the events we're interested in
bool subscribed = es_subscribe(g_client,
2, // The number of event types being subscribed to
ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_NOTIFY_FORK
);
if(!subscribed) {
LOG_ERROR("es_subscribe: FALSE");
return 1;
}
// Cache needs to be explicitly cleared between program invocations
es_clear_cache_result_t resCache = es_clear_cache(g_client);
if(ES_CLEAR_CACHE_RESULT_SUCCESS != resCache) {
LOG_ERROR("es_clear_cache: %d", resCache);
return 1;
}
dispatch_main();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment