Skip to content

Instantly share code, notes, and snippets.

@tommy-dong
Forked from Omar-Ikram/EndpointSecurityDemo.m
Created September 6, 2024 02:03

Revisions

  1. @Omar-Ikram Omar-Ikram revised this gist Feb 20, 2022. 1 changed file with 34 additions and 17 deletions.
    51 changes: 34 additions & 17 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -9,13 +9,14 @@
    // Updated by Omar Ikram on 07/05/2021 - macOS Big Sur 11.3.1 (20E241)
    // Updated by Omar Ikram on 04/07/2021 - macOS Monterey 12 Beta 2 (21A5268h)
    // Updated by Omar Ikram on 08/01/2022 - macOS Monterey 12.1 (21C52)
    // Updated by Omar Ikram on 15/02/2022 - macOS Monterey 12.2.1 (21D62)
    //

    /*
    A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.1 (21C52).
    A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.2.1 (21D62).
    Minumum supported version: macOS Catalina 10.15
    Minimum supported version: macOS Catalina 10.15
    This demo is an update of previous demos, which has been updated to support the latest API changes
    Apple has made for macOS Monterey 12.
    @@ -30,11 +31,18 @@
    not production-ready and is not meant to be used in a production environment. Use it at your own risk!
    Setup:
    1. Build with Xcode 13 (tested with Version 13.2.1 (13C100)), having the macOS deployment target set to
    10.15 (or later) and the Hardened Runtime capability enabled.
    2. Codesign with entitlement 'com.apple.developer.endpoint-security.client'.
    1. Build with Xcode 13 (tested with Version 13.2.1 (13C100)), having the macOS deployment target set
    to 10.15 (or later) and the Hardened Runtime capability enabled.
    2. Link with libraries:
    - libEndpointSecurity.tbd (Endpoint Security functions)
    - libbsm.tbd (Audit Token functions)
    - UniformTypeIdentifiers.framework (UTI functions, which is not available on macOS Catalina 10.15
    , so it needs to be optinally linked - e.g. with the '-weak_framework' linker option)
    3. Codesign with entitlement 'com.apple.developer.endpoint-security.client'.
    If your Apple Developer account has been granted the entitlment from Apple, then the program needs
    If your Apple Developer account has been granted the entitlement from Apple, then the program needs
    to be compiled as an App (i.e. Application Bundle). This will allow you to assign a Provisioning
    Profile to the program, which you need to have associated the entitlement to it.
    @@ -44,7 +52,7 @@ to be compiled as an App (i.e. Application Bundle). This will allow you to assig
    Runtime:
    1. Test environment should be a macOS 10.15+ machine.
    2. Run the demo binary in a terminal as root (e.g. with sudo).
    i) Running with no aguments will display a simple usage message.
    i) Running with no arguments will display a simple usage message.
    ii) Running with the 'serial' argument will run the demo using
    the example serial event message handler.
    iii) Running with the 'asynchronous' argument will run the demo using
    @@ -55,7 +63,7 @@ to be compiled as an App (i.e. Application Bundle). This will allow you to assig
    serially or asynchronously (depending on the selected command line argument given).
    The demo will also demonstrate using Endpoint Security Auth events to make the
    following Auth based decsions:
    following Auth based decisions:
    i) Block the 'top' binary and 'Calculator' app bundle from running.
    ii) Block 'vim' binary from reading plain text files.
    5. CTL-C to exit.
    @@ -896,14 +904,23 @@ bool setup_endpoint_security(void) {
    es_new_client_result_t res = es_new_client(&g_client, g_handler);

    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 be run as root "
    "(and SIP disabled if not signed with a Provisioning Profile that has the "
    "'com.apple.developer.endpoint-security.client' entitlement).");
    } else {
    LOG_ERROR("es_new_client: %d", res);
    switch(res) {
    case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED:
    LOG_ERROR("Application requires 'com.apple.developer.endpoint-security.client' entitlement");
    break;

    case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
    LOG_ERROR("Application lacks Transparency, Consent, and Control (TCC) approval "
    "from the user. This can be resolved by granting 'Full Disk Access' from "
    "the 'Security & Privacy' tab of System Preferences.");
    break;

    case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED:
    LOG_ERROR("Application needs to be run as root");
    break;

    default:
    LOG_ERROR("es_new_client: %d", res);
    }

    return false;
    @@ -974,7 +991,7 @@ int main(int argc, const char * argv[]) {
    // 'es_release_muted_paths'), but Apple advises against this.
    log_muted_paths_events();
    } else {
    // ES on macOS Monterey 12 implicitly mutes events from cfprefsd. We need to explictly do
    // ES on macOS Monterey 12 implicitly mutes events from cfprefsd. We need to explicitly do
    // this on older versions of macOS to prevent deadlocks in this program. This is because
    // UTType and NSDate objects, used in parts of this program, may implicitly
    // make NSUserDefaults calls which will generate ES events for cfprefsd.
  2. @Omar-Ikram Omar-Ikram revised this gist Feb 14, 2022. 1 changed file with 94 additions and 38 deletions.
    132 changes: 94 additions & 38 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -8,11 +8,12 @@
    // Updated by Omar Ikram on 31/01/2021 - macOS Big Sur 11.1 (20C69)
    // Updated by Omar Ikram on 07/05/2021 - macOS Big Sur 11.3.1 (20E241)
    // Updated by Omar Ikram on 04/07/2021 - macOS Monterey 12 Beta 2 (21A5268h)
    // Updated by Omar Ikram on 08/01/2022 - macOS Monterey 12.1 (21C52)
    //

    /*
    A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.0 Beta 2 (21A5268h).
    A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.1 (21C52).
    Minumum supported version: macOS Catalina 10.15
    @@ -29,11 +30,19 @@
    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 13 (tested with 13.0 beta (13A5155e)), with the macOS deployment target set to 10.15 (or later).
    1. Build with Xcode 13 (tested with Version 13.2.1 (13C100)), having the macOS deployment target set to
    10.15 (or later) and the Hardened Runtime capability enabled.
    2. Codesign with entitlement 'com.apple.developer.endpoint-security.client'.
    If your Apple Developer account has been granted the entitlment from Apple, then the program needs
    to be compiled as an App (i.e. Application Bundle). This will allow you to assign a Provisioning
    Profile to the program, which you need to have associated the entitlement to it.
    If you have not been granted the entitlement. You can still build the program (as an App or Command
    Line Tool), but it will only be able to run on a machine which has SIP disabled (best to use a VM).
    Runtime:
    1. Test environment should be a macOS 10.15+ machine which has SIP disabled (best to use a VM).
    1. Test environment should be a macOS 10.15+ machine.
    2. Run the demo binary in a terminal as root (e.g. with sudo).
    i) Running with no aguments will display a simple usage message.
    ii) Running with the 'serial' argument will run the demo using
    @@ -67,6 +76,7 @@ serially or asynchronously (depending on the selected command line argument give

    es_client_t *g_client = nil;
    NSSet *g_blocked_paths = nil;
    NSDateFormatter *g_date_formater = nil;

    // Endpoint Security event handler selected at startup from the command line
    es_handler_block_t g_handler = nil;
    @@ -104,8 +114,8 @@ uint64_t MachTimeToNanoseconds(uint64_t machTime) {
    return nanoseconds;
    }

    int MachTimeToSeconds(uint64_t machTime) {
    return MachTimeToNanoseconds(machTime) / 1.0e9;
    uint64_t MachTimeToSeconds(uint64_t machTime) {
    return MachTimeToNanoseconds(machTime) / NSEC_PER_SEC;
    }

    #pragma mark Helpers - Code Signing
    @@ -189,7 +199,8 @@ int MachTimeToSeconds(uint64_t machTime) {
    @"AUTH_GET_TASK_READ", @"NOTIFY_GET_TASK_READ", @"NOTIFY_GET_TASK_INSPECT",

    // The following events are available beginning in macOS 12.0
    @"NOTIFY_SETUID", @"NOTIFY_SETGID", @"NOTIFY_SETEUID", @"NOTIFY_SETEGID", @"NOTIFY_SETREUID", @"NOTIFY_SETREGID",
    @"NOTIFY_SETUID", @"NOTIFY_SETGID", @"NOTIFY_SETEUID", @"NOTIFY_SETEGID", @"NOTIFY_SETREUID",
    @"NOTIFY_SETREGID", @"AUTH_COPYFILE", @"NOTIFY_COPYFILE"
    };

    if(event_type >= ES_EVENT_TYPE_LAST) {
    @@ -233,25 +244,42 @@ void free_message(es_message_t * _Nonnull msg) {

    NSString* fdtype_str(const uint32_t fdtype) {
    switch(fdtype) {
    case PROX_FDTYPE_ATALK: return @"PROX_FDTYPE_ATALK";
    case PROX_FDTYPE_VNODE: return @"PROX_FDTYPE_VNODE";
    case PROX_FDTYPE_SOCKET: return @"PROX_FDTYPE_SOCKET";
    case PROX_FDTYPE_PSHM: return @"PROX_FDTYPE_PSHM";
    case PROX_FDTYPE_PSEM: return @"PROX_FDTYPE_PSEM";
    case PROX_FDTYPE_KQUEUE: return @"PROX_FDTYPE_KQUEUE";
    case PROX_FDTYPE_PIPE: return @"PROX_FDTYPE_PIPE";
    case PROX_FDTYPE_FSEVENTS: return @"PROX_FDTYPE_FSEVENTS";
    case PROX_FDTYPE_NETPOLICY: return @"PROX_FDTYPE_NETPOLICY";
    case PROX_FDTYPE_ATALK: return @"ATALK";
    case PROX_FDTYPE_VNODE: return @"VNODE";
    case PROX_FDTYPE_SOCKET: return @"SOCKET";
    case PROX_FDTYPE_PSHM: return @"PSHM";
    case PROX_FDTYPE_PSEM: return @"PSEM";
    case PROX_FDTYPE_KQUEUE: return @"KQUEUE";
    case PROX_FDTYPE_PIPE: return @"PIPE";
    case PROX_FDTYPE_FSEVENTS: return @"FSEVENTS";
    case PROX_FDTYPE_NETPOLICY: return @"NETPOLICY";
    default: return [NSString stringWithFormat:@"Unknown/Unsupported fdtype: %d",
    fdtype];
    }
    }

    void init_date_formater(void) {
    // Display dates in RFC 3339 date and time format: https://www.ietf.org/rfc/rfc3339.txt
    g_date_formater = [NSDateFormatter new];
    g_date_formater.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
    g_date_formater.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZZZ";
    g_date_formater.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    }

    NSString* formatted_date_str(__darwin_time_t secs_since_1970) {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:secs_since_1970];
    return [NSDateFormatter localizedStringFromDate:date
    dateStyle:NSDateFormatterShortStyle
    timeStyle:NSDateFormatterShortStyle];
    return [g_date_formater stringFromDate:date];
    }

    bool is_system_file(const NSString* path) {
    // For the purpose of this demo. A system file is a file that is under these directories:
    for(NSString* prefix in @[@"/System/", @"/usr/share/"]) {
    if([path hasPrefix:prefix]) {
    return true;
    }
    }

    return false;
    }

    bool is_plain_text_file(const NSString* path) {
    @@ -265,6 +293,19 @@ bool is_plain_text_file(const NSString* path) {
    }
    }

    char* filetype_str(const mode_t st_mode) {
    switch(((st_mode) & S_IFMT)) {
    case S_IFBLK: return "BLK";
    case S_IFCHR: return "CHR";
    case S_IFDIR: return "DIR";
    case S_IFIFO: return "FIFO";
    case S_IFREG: return "REG";
    case S_IFLNK: return "LINK";
    case S_IFSOCK: return "SOCK";
    default: return "";
    }
    }

    #pragma mark - Logging

    #define BOOL_VALUE(x) x ? "Yes" : "No"
    @@ -363,16 +404,16 @@ void log_file(const NSString* header, const es_file_t* file) {

    LOG_INFO("stat.st_dev: %d", file->stat.st_dev);
    LOG_INFO("stat.st_ino: %llu", file->stat.st_ino);
    LOG_INFO("stat.st_mode: %u", file->stat.st_mode);
    LOG_INFO("stat.st_mode: %u (%s)", file->stat.st_mode, filetype_str(file->stat.st_mode));
    LOG_INFO("stat.st_nlink: %u", file->stat.st_nlink);

    LOG_INFO("stat.st_uid: %u", file->stat.st_uid);
    LOG_INFO("stat.st_gid: %u", file->stat.st_gid);

    LOG_INFO("stat.st_atimespec: %@", formatted_date_str(file->stat.st_atimespec.tv_sec));
    LOG_INFO("stat.st_mtimespec: %@", formatted_date_str(file->stat.st_mtimespec.tv_sec));
    LOG_INFO("stat.st_ctimespec: %@", formatted_date_str(file->stat.st_ctimespec.tv_sec));
    LOG_INFO("stat.st_birthtimespec: %@", formatted_date_str(file->stat.st_birthtimespec.tv_sec));
    LOG_INFO("stat.st_atime: %@", formatted_date_str(file->stat.st_atime));
    LOG_INFO("stat.st_mtime: %@", formatted_date_str(file->stat.st_mtime));
    LOG_INFO("stat.st_ctime: %@", formatted_date_str(file->stat.st_ctime));
    LOG_INFO("stat.st_birthtime: %@", formatted_date_str(file->stat.st_birthtime));

    LOG_INFO("stat.st_size: %lld", file->stat.st_size);
    LOG_INFO("stat.st_blocks: %lld", file->stat.st_blocks);
    @@ -528,7 +569,7 @@ void log_event_message(const es_message_t *msg) {
    LOG_INFO("version: %u", version);

    LOG_INFO("time: %@", formatted_date_str(msg->time.tv_sec));
    LOG_INFO("mach_time: %lld", (long long) msg->mach_time);
    LOG_INFO("mach_time: %lld", msg->mach_time);

    // Note: It's very important that an auth event is processed within the deadline:
    // https://developer.apple.com/documentation/endpointsecurity/es_message_t/3334985-deadline
    @@ -537,17 +578,16 @@ void log_event_message(const es_message_t *msg) {
    // It is not configurable.
    // It won't get longer, but it will get shorter."
    // https://developer.apple.com/forums/thread/649552?answerId=615802022#615802022
    LOG_INFO("deadline: %lld", (long long) msg->deadline);
    LOG_INFO("deadline: %llu", msg->deadline);

    uint64_t deadlineInterval = msg->deadline;

    if(deadlineInterval > 0) {
    deadlineInterval -= msg->mach_time;
    }

    LOG_INFO("deadline interval: %lld (%d seconds)",
    (long long) deadlineInterval,
    MachTimeToSeconds(deadlineInterval));
    LOG_INFO("deadline interval: %llu (%llu seconds)",
    deadlineInterval, MachTimeToSeconds(deadlineInterval));

    // Note: You can use the seq_num field to detect if the kernel had to drop any event messages,
    // for an event type, to the client.
    @@ -607,7 +647,7 @@ void detect_and_log_dropped_events(const es_message_t *msg) {
    const NSString *type = event_type_str(msg->event_type);
    NSNumber *last_seq_num = [g_seq_nums objectForKey:type];

    if(last_seq_num) {
    if(last_seq_num != nil) {
    uint64_t expected_seq_num = [last_seq_num unsignedLongLongValue] + 1;

    if(seq_num > expected_seq_num) {
    @@ -655,7 +695,7 @@ void print_usage(const char *name) {
    printf("\tverbose\t\tTurns on verbose logging\n");
    }

    // Handler to make auth (allow or block) decisions.
    // An example 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) {
    // NOTE: You should ignore events from other ES Clients;
    @@ -682,7 +722,7 @@ es_auth_result_t auth_event_handler(const es_message_t *msg) {
    return ES_AUTH_RESULT_DENY;
    }

    // Block vim accessing plain text files
    // Block vim from accessing plain text files
    if(ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) {
    NSString *processPath = esstring_to_nsstring(msg->process->executable->path);

    @@ -693,6 +733,11 @@ es_auth_result_t auth_event_handler(const es_message_t *msg) {

    NSString *filePath = esstring_to_nsstring(msg->event.open.file->path);

    if(is_system_file(filePath)) {
    // Ignore System files
    return ES_AUTH_RESULT_ALLOW;
    }

    if(!is_plain_text_file(filePath)) {
    // Not a text file
    return ES_AUTH_RESULT_ALLOW;
    @@ -843,6 +888,8 @@ bool mute_path(const char* path)
    return true;
    }

    // Note: This function shows the boilerplate code required to setup a connection to Endpoint Security
    // and subscribe to events.
    bool setup_endpoint_security(void) {
    // Create a new client with an associated event message handler.
    // Requires 'com.apple.developer.endpoint-security.client' entitlement.
    @@ -852,7 +899,9 @@ bool setup_endpoint_security(void) {
    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).");
    LOG_ERROR("Application needs to be run as root "
    "(and SIP disabled if not signed with a Provisioning Profile that has the "
    "'com.apple.developer.endpoint-security.client' entitlement).");
    } else {
    LOG_ERROR("es_new_client: %d", res);
    }
    @@ -897,6 +946,7 @@ int main(int argc, const char * argv[]) {
    return 1;
    }

    init_date_formater();
    g_seq_nums = [NSMutableDictionary new];

    // List of paths to be blocked.
    @@ -914,15 +964,21 @@ int main(int argc, const char * argv[]) {
    // Uncomment the 'mute_path' line below to stop receiving events from the 'vim' binary.
    // This program will then stop receiving 'ES_EVENT_TYPE_AUTH_OPEN' events for vim and will no
    // longer be able to block vim from opening plain text files.
    //mute_path("/usr/bin/vim");
    // mute_path("/usr/bin/vim");

    // Note: Endpoint Security will automatically mute a set of paths, on creation of new clients
    // ('es_new_client'), for performance reasons.
    // macOS Monterey 12 now has the 'es_muted_paths_events' function, which can be used to inspect
    // the muted paths. It is possible to unmute these paths (e.g. by using 'es_release_muted_paths'),
    // but Apple advises against this.
    if(@available(macOS 12.0, *)) {
    // Note: Endpoint Security for performance reasons will automatically mute a set of paths
    // on creation of new clients ('es_new_client').
    // macOS Monterey 12 now has the 'es_muted_paths_events' function, which can be used to
    // inspect the muted paths. It is possible to unmute these paths (e.g. by using
    // 'es_release_muted_paths'), but Apple advises against this.
    log_muted_paths_events();
    } else {
    // ES on macOS Monterey 12 implicitly mutes events from cfprefsd. We need to explictly do
    // this on older versions of macOS to prevent deadlocks in this program. This is because
    // UTType and NSDate objects, used in parts of this program, may implicitly
    // make NSUserDefaults calls which will generate ES events for cfprefsd.
    mute_path("/usr/sbin/cfprefsd");
    }

    // Start handling events from Endpoint Security
  3. @Omar-Ikram Omar-Ikram revised this gist Jul 4, 2021. 1 changed file with 162 additions and 81 deletions.
    243 changes: 162 additions & 81 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -7,38 +7,29 @@
    // Updated by Omar Ikram on 01/12/2019 - macOS Catalina 10.15 (19A583)
    // Updated by Omar Ikram on 31/01/2021 - macOS Big Sur 11.1 (20C69)
    // Updated by Omar Ikram on 07/05/2021 - macOS Big Sur 11.3.1 (20E241)
    // Updated by Omar Ikram on 04/07/2021 - macOS Monterey 12 Beta 2 (21A5268h)
    //

    #import <Foundation/Foundation.h>
    #import <EndpointSecurity/EndpointSecurity.h>
    #import <bsm/libbsm.h>
    #import <signal.h>
    #import <mach/mach_time.h>
    #import <Kernel/kern/cs_blobs.h>
    #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
    #import <Appkit/AppKit.h>
    #import <libproc.h>

    /*
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Big Sur 11.3 (20E241).
    A demo of using Apple's EndpointSecurity framework - tested on macOS Monterey 12.0 Beta 2 (21A5268h).
    Minumum supported version: macOS Catalina 10.15
    This demo is an update of previous demos, which has been updated to support the latest API changes
    Apple has made for macos 11 Big Sur.
    Apple has made for macOS Monterey 12.
    The demo has also been expanded significantly to include more detail and cover more of the API.
    The code, hopefully, should be self explanitory. Important details are marked by a comment
    The code, hopefully, should be self explanatory. Important details are marked by a comment
    starting with "Note:".
    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 12 (tested with 12.5), with the macOS deployment target set to 10.15 (or later).
    1. Build on Xcode 13 (tested with 13.0 beta (13A5155e)), with the macOS deployment target set to 10.15 (or later).
    2. Codesign with entitlement 'com.apple.developer.endpoint-security.client'.
    Runtime:
    @@ -62,6 +53,16 @@ serially or asynchronously (depending on the selected command line argument give
    */

    #import <Foundation/Foundation.h>
    #import <EndpointSecurity/EndpointSecurity.h>
    #import <bsm/libbsm.h>
    #import <signal.h>
    #import <mach/mach_time.h>
    #import <Kernel/kern/cs_blobs.h>
    #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
    #import <Appkit/AppKit.h>
    #import <libproc.h>

    #pragma mark Globals

    es_client_t *g_client = nil;
    @@ -118,41 +119,23 @@ int MachTimeToSeconds(uint64_t machTime) {

    // Code signing flags defined in cs_blobs.h
    const CSFlag g_csFlags[] = {
    CSFLAG(CS_VALID),
    CSFLAG(CS_ADHOC),
    CSFLAG(CS_GET_TASK_ALLOW),
    CSFLAG(CS_INSTALLER),
    CSFLAG(CS_FORCED_LV),
    CSFLAG(CS_INVALID_ALLOWED),
    CSFLAG(CS_HARD),
    CSFLAG(CS_KILL),
    CSFLAG(CS_CHECK_EXPIRATION),
    CSFLAG(CS_RESTRICT),
    CSFLAG(CS_ENFORCEMENT),
    CSFLAG(CS_REQUIRE_LV),
    CSFLAG(CS_ENTITLEMENTS_VALIDATED),
    CSFLAG(CS_NVRAM_UNRESTRICTED),
    CSFLAG(CS_RUNTIME),
    CSFLAG(CS_LINKER_SIGNED),
    CSFLAG(CS_ALLOWED_MACHO),
    CSFLAG(CS_EXEC_SET_HARD),
    CSFLAG(CS_EXEC_SET_KILL),
    CSFLAG(CS_EXEC_SET_ENFORCEMENT),
    CSFLAG(CS_EXEC_INHERIT_SIP),
    CSFLAG(CS_KILLED),
    CSFLAG(CS_DYLD_PLATFORM),
    CSFLAG(CS_PLATFORM_BINARY),
    CSFLAG(CS_PLATFORM_PATH),
    CSFLAG(CS_DEBUGGED),
    CSFLAG(CS_SIGNED),
    CSFLAG(CS_DEV_CODE)
    CSFLAG(CS_VALID), CSFLAG(CS_ADHOC), CSFLAG(CS_GET_TASK_ALLOW),
    CSFLAG(CS_INSTALLER), CSFLAG(CS_FORCED_LV), CSFLAG(CS_INVALID_ALLOWED),
    CSFLAG(CS_HARD), CSFLAG(CS_KILL), CSFLAG(CS_CHECK_EXPIRATION),
    CSFLAG(CS_RESTRICT), CSFLAG(CS_ENFORCEMENT), CSFLAG(CS_REQUIRE_LV),
    CSFLAG(CS_ENTITLEMENTS_VALIDATED), CSFLAG(CS_NVRAM_UNRESTRICTED),
    CSFLAG(CS_RUNTIME), CSFLAG(CS_LINKER_SIGNED), CSFLAG(CS_ALLOWED_MACHO),
    CSFLAG(CS_EXEC_SET_HARD), CSFLAG(CS_EXEC_SET_KILL), CSFLAG(CS_EXEC_SET_ENFORCEMENT),
    CSFLAG(CS_EXEC_INHERIT_SIP), CSFLAG(CS_KILLED), CSFLAG(CS_DYLD_PLATFORM),
    CSFLAG(CS_PLATFORM_BINARY), CSFLAG(CS_PLATFORM_PATH), CSFLAG(CS_DEBUGGED),
    CSFLAG(CS_SIGNED), CSFLAG(CS_DEV_CODE)
    };

    NSString* codesigning_flags_str(const uint32_t codesigning_flags) {
    NSMutableArray *match_flags = [NSMutableArray new];

    // Test which code signing flags have been set and add the matched ones to an array
    for(int i = 0; i < (sizeof g_csFlags / sizeof *g_csFlags); i++) {
    for(uint32_t i = 0; i < (sizeof g_csFlags / sizeof *g_csFlags); i++) {
    if((codesigning_flags & g_csFlags[i].value) == g_csFlags[i].value) {
    [match_flags addObject:g_csFlags[i].name];
    }
    @@ -172,18 +155,61 @@ int MachTimeToSeconds(uint64_t machTime) {
    }
    }

    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_AUTH_OPEN: return @"ES_EVENT_TYPE_AUTH_OPEN";
    case ES_EVENT_TYPE_NOTIFY_FORK: return @"ES_EVENT_TYPE_NOTIFY_FORK";
    default: return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d",
    event_type];
    const NSString* event_type_str(const es_event_type_t event_type) {
    static const NSString *names[] = {
    // The following events are available beginning in macOS 10.15
    @"AUTH_EXEC", @"AUTH_OPEN", @"AUTH_KEXTLOAD", @"AUTH_MMAP", @"AUTH_MPROTECT",
    @"AUTH_MOUNT", @"AUTH_RENAME", @"AUTH_SIGNAL", @"AUTH_UNLINK", @"NOTIFY_EXEC",
    @"NOTIFY_OPEN", @"NOTIFY_FORK", @"NOTIFY_CLOSE", @"NOTIFY_CREATE", @"NOTIFY_EXCHANGEDATA",
    @"NOTIFY_EXIT", @"NOTIFY_GET_TASK", @"NOTIFY_KEXTLOAD", @"NOTIFY_KEXTUNLOAD", @"NOTIFY_LINK",
    @"NOTIFY_MMAP", @"NOTIFY_MPROTECT", @"NOTIFY_MOUNT", @"NOTIFY_UNMOUNT", @"NOTIFY_IOKIT_OPEN",
    @"NOTIFY_RENAME", @"NOTIFY_SETATTRLIST", @"NOTIFY_SETEXTATTR", @"NOTIFY_SETFLAGS", @"NOTIFY_SETMODE",
    @"NOTIFY_SETOWNER", @"NOTIFY_SIGNAL", @"NOTIFY_UNLINK", @"NOTIFY_WRITE", @"AUTH_FILE_PROVIDER_MATERIALIZE",
    @"NOTIFY_FILE_PROVIDER_MATERIALIZE", @"AUTH_FILE_PROVIDER_UPDATE", @"NOTIFY_FILE_PROVIDER_UPDATE",
    @"AUTH_READLINK", @"NOTIFY_READLINK", @"AUTH_TRUNCATE", @"NOTIFY_TRUNCATE", @"AUTH_LINK", @"NOTIFY_LOOKUP",
    @"AUTH_CREATE", @"AUTH_SETATTRLIST", @"AUTH_SETEXTATTR", @"AUTH_SETFLAGS", @"AUTH_SETMODE", @"AUTH_SETOWNER",

    // The following events are available beginning in macOS 10.15.1
    @"AUTH_CHDIR", @"NOTIFY_CHDIR", @"AUTH_GETATTRLIST", @"NOTIFY_GETATTRLIST", @"NOTIFY_STAT", @"NOTIFY_ACCESS",
    @"AUTH_CHROOT", @"NOTIFY_CHROOT", @"AUTH_UTIMES", @"NOTIFY_UTIMES", @"AUTH_CLONE", @"NOTIFY_CLONE",
    @"NOTIFY_FCNTL", @"AUTH_GETEXTATTR", @"NOTIFY_GETEXTATTR", @"AUTH_LISTEXTATTR", @"NOTIFY_LISTEXTATTR",
    @"AUTH_READDIR", @"NOTIFY_READDIR", @"AUTH_DELETEEXTATTR", @"NOTIFY_DELETEEXTATTR", @"AUTH_FSGETPATH",
    @"NOTIFY_FSGETPATH", @"NOTIFY_DUP", @"AUTH_SETTIME", @"NOTIFY_SETTIME", @"NOTIFY_UIPC_BIND", @"AUTH_UIPC_BIND",
    @"NOTIFY_UIPC_CONNECT", @"AUTH_UIPC_CONNECT", @"AUTH_EXCHANGEDATA", @"AUTH_SETACL", @"NOTIFY_SETACL",

    // The following events are available beginning in macOS 10.15.4
    @"NOTIFY_PTY_GRANT", @"NOTIFY_PTY_CLOSE", @"AUTH_PROC_CHECK", @"NOTIFY_PROC_CHECK", @"AUTH_GET_TASK",

    // The following events are available beginning in macOS 11.0
    @"AUTH_SEARCHFS", @"NOTIFY_SEARCHFS", @"AUTH_FCNTL", @"AUTH_IOKIT_OPEN", @"AUTH_PROC_SUSPEND_RESUME",
    @"NOTIFY_PROC_SUSPEND_RESUME", @"NOTIFY_CS_INVALIDATED", @"NOTIFY_GET_TASK_NAME",
    @"NOTIFY_TRACE", @"NOTIFY_REMOTE_THREAD_CREATE", @"AUTH_REMOUNT", @"NOTIFY_REMOUNT",

    // The following events are available beginning in macOS 11.3
    @"AUTH_GET_TASK_READ", @"NOTIFY_GET_TASK_READ", @"NOTIFY_GET_TASK_INSPECT",

    // The following events are available beginning in macOS 12.0
    @"NOTIFY_SETUID", @"NOTIFY_SETGID", @"NOTIFY_SETEUID", @"NOTIFY_SETEGID", @"NOTIFY_SETREUID", @"NOTIFY_SETREGID",
    };

    if(event_type >= ES_EVENT_TYPE_LAST) {
    return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d", event_type];
    }

    return names[event_type];
    }

    NSString* events_str(size_t count, const es_event_type_t* events) {
    NSMutableArray *arr = [NSMutableArray new];

    for(size_t i = 0; i < count; i++) {
    [arr addObject:event_type_str(events[i])];
    }

    return [arr componentsJoinedByString:@", "];
    }

    // On macOS 11 Big Sur, Apple have deprecated es_copy_message in favour of es_retain_message
    // On macOS Big Sur 11, Apple have deprecated es_copy_message in favour of es_retain_message
    es_message_t * copy_message(const es_message_t * msg) {
    if(@available(macOS 11.0, *)) {
    es_retain_message(msg);
    @@ -194,7 +220,7 @@ int MachTimeToSeconds(uint64_t machTime) {
    }
    }

    // On macOS 11 Big Sur, Apple have deprecated es_free_message in favour of es_release_message
    // On macOS Big Sur 11, Apple have deprecated es_free_message in favour of es_release_message
    void free_message(es_message_t * _Nonnull msg) {
    if(@available(macOS 11.0, *)) {
    es_release_message(msg);
    @@ -263,38 +289,65 @@ bool is_plain_text_file(const NSString* path) {
    } \
    }

    bool log_subscribed_events(void) {
    // Log the subscribed events
    size_t subscription_count = 0;
    es_event_type_t *subscription_ids = NULL;
    es_return_t result = es_subscriptions(g_client, &subscription_count, &subscription_ids);
    NSMutableArray *subs = [NSMutableArray new];
    void log_audit_token(const NSString* header, const audit_token_t audit_token) {
    LOG_INFO("%@:", header);
    LOG_INDENT_INC();
    LOG_INFO("pid: %d", audit_token_to_pid(audit_token));
    LOG_INFO("ruid: %d", audit_token_to_ruid(audit_token));
    LOG_INFO("euid: %d", audit_token_to_euid(audit_token));
    LOG_INFO("rgid: %d", audit_token_to_rgid(audit_token));
    LOG_INFO("egid: %d", audit_token_to_egid(audit_token));
    LOG_INDENT_DEC();
    }

    API_AVAILABLE(macos(12.0))
    bool log_muted_paths_events(void) {
    es_muted_paths_t *muted_paths = NULL;
    es_return_t result = es_muted_paths_events(g_client, &muted_paths);

    if(ES_RETURN_SUCCESS != result)
    {
    LOG_ERROR("es_subscriptions: ES_RETURN_ERROR");
    if(ES_RETURN_SUCCESS != result) {
    LOG_ERROR("es_muted_paths_events: ES_RETURN_ERROR");
    return false;
    }

    for(int i = 0; i < subscription_count; i++) {
    [subs addObject:event_type_str(subscription_ids[i])];
    if(NULL == muted_paths) {
    // There are no muted paths
    return true;
    }

    LOG_IMPORTANT_INFO("Subscribed Events: %@", [subs componentsJoinedByString:@", "]);
    LOG_IMPORTANT_INFO("Muted Paths");
    for(size_t i = 0; i < muted_paths->count; i++) {
    es_muted_path_t muted_path = muted_paths->paths[i];
    LOG_INFO("muted_path[%ld]: %@", i, esstring_to_nsstring(muted_path.path));

    if(g_verbose_logging) {
    LOG_INDENT_INC();
    LOG_INFO("type: %s", (muted_path.type == ES_MUTE_PATH_TYPE_PREFIX) ? "Prefix" : "Literal");
    LOG_INFO("event_count: %ld", muted_path.event_count);
    LOG_INFO("events: %@", events_str(muted_path.event_count, muted_path.events));
    LOG_INDENT_DEC();
    }
    }

    free(subscription_ids);
    es_release_muted_paths(muted_paths);
    return true;
    }

    void log_audit_token(const NSString* header, const audit_token_t audit_token) {
    LOG_INFO("%@:", header);
    LOG_INDENT_INC();
    LOG_INFO("pid: %d", audit_token_to_pid(audit_token));
    LOG_INFO("ruid: %d", audit_token_to_ruid(audit_token));
    LOG_INFO("euid: %d", audit_token_to_euid(audit_token));
    LOG_INFO("rgid: %d", audit_token_to_rgid(audit_token));
    LOG_INFO("egid: %d", audit_token_to_egid(audit_token));
    LOG_INDENT_DEC();
    bool log_subscribed_events(void) {
    // Log the subscribed events
    size_t count = 0;
    es_event_type_t *events = NULL;
    es_return_t result = es_subscriptions(g_client, &count, &events);

    if(ES_RETURN_SUCCESS != result) {
    LOG_ERROR("es_subscriptions: ES_RETURN_ERROR");
    return false;
    }

    LOG_IMPORTANT_INFO("Subscribed Events: %@", events_str(count, events));

    free(events);
    return true;
    }

    void log_file(const NSString* header, const es_file_t* file) {
    @@ -551,7 +604,7 @@ void detect_and_log_dropped_events(const es_message_t *msg) {
    if(version >= 2) {
    uint64_t seq_num = msg->seq_num;

    NSString *type = event_type_str(msg->event_type);
    const NSString *type = event_type_str(msg->event_type);
    NSNumber *last_seq_num = [g_seq_nums objectForKey:type];

    if(last_seq_num) {
    @@ -749,13 +802,13 @@ void respond_to_auth_event(es_client_t *clt, const es_message_t *msg, es_auth_re
    };

    es_handler_block_t get_message_handler_from_commandline_args(int argc, const char * argv[]) {
    if (argc < 2) {
    if(argc < 2) {
    // No command line argument was given
    return nil;
    }

    // check if verbose logging argument was given
    if (argc > 2) {
    if(argc > 2) {
    NSString *verbose = [[NSString stringWithUTF8String:argv[2]] lowercaseString];
    g_verbose_logging = [verbose isEqualToString:@"verbose"];
    }
    @@ -771,6 +824,25 @@ es_handler_block_t get_message_handler_from_commandline_args(int argc, const cha
    return [handlers objectForKey:arg];
    }

    // On macOS Monterey 12, Apple have deprecated es_mute_path_literal in favour of es_mute_path
    bool mute_path(const char* path)
    {
    es_return_t result = ES_RETURN_ERROR;

    if(@available(macOS 12.0, *)) {
    result = es_mute_path(g_client, path, ES_MUTE_PATH_TYPE_LITERAL);
    } else {
    result = es_mute_path_literal(g_client, path);
    }

    if(ES_RETURN_SUCCESS != result) {
    LOG_ERROR("mute_path: ES_RETURN_ERROR");
    return false;
    }

    return true;
    }

    bool setup_endpoint_security(void) {
    // Create a new client with an associated event message handler.
    // Requires 'com.apple.developer.endpoint-security.client' entitlement.
    @@ -839,14 +911,23 @@ int main(int argc, const char * argv[]) {
    }

    // Note: Endpoint Security have a set of es_mute* functions to suppress events for a process.
    // Uncomment the 'es_mute_path_literal' line below to stop receiving events from the 'vim' binary.
    // Uncomment the 'mute_path' line below to stop receiving events from the 'vim' binary.
    // This program will then stop receiving 'ES_EVENT_TYPE_AUTH_OPEN' events for vim and will no
    // longer be able to block vim from opening plain text files.
    // es_mute_path_literal(g_client, "/usr/bin/vim");
    //mute_path("/usr/bin/vim");

    // Note: Endpoint Security will automatically mute a set of paths, on creation of new clients
    // ('es_new_client'), for performance reasons.
    // macOS Monterey 12 now has the 'es_muted_paths_events' function, which can be used to inspect
    // the muted paths. It is possible to unmute these paths (e.g. by using 'es_release_muted_paths'),
    // but Apple advises against this.
    if(@available(macOS 12.0, *)) {
    log_muted_paths_events();
    }

    // Start handling events from Endpoint Security
    dispatch_main();
    }

    return 0;
    }
    }
  4. @Omar-Ikram Omar-Ikram revised this gist May 13, 2021. No changes.
  5. @Omar-Ikram Omar-Ikram revised this gist May 13, 2021. 1 changed file with 716 additions and 167 deletions.
    883 changes: 716 additions & 167 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -2,110 +2,488 @@
    // main.m
    // EndpointSecurityDemo
    //
    // Created by Omar Ikram on 17/06/2019 - Catalina 10.15 Beta 1 (19A471t)
    // Updated by Omar Ikram on 15/08/2019 - Catalina 10.15 Beta 5 (19A526h)
    // Updated by Omar Ikram on 01/12/2019 - Catalina 10.15 (19A583)
    // Created by Omar Ikram on 17/06/2019 - macOS Catalina 10.15 Beta 1 (19A471t)
    // Updated by Omar Ikram on 15/08/2019 - macOS Catalina 10.15 Beta 5 (19A526h)
    // Updated by Omar Ikram on 01/12/2019 - macOS Catalina 10.15 (19A583)
    // Updated by Omar Ikram on 31/01/2021 - macOS Big Sur 11.1 (20C69)
    // Updated by Omar Ikram on 07/05/2021 - macOS Big Sur 11.3.1 (20E241)
    //

    #import <Foundation/Foundation.h>
    #import <EndpointSecurity/EndpointSecurity.h>
    #import <bsm/libbsm.h>
    #import <signal.h>
    #import <mach/mach_time.h>
    #import <Kernel/kern/cs_blobs.h>
    #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
    #import <Appkit/AppKit.h>
    #import <libproc.h>

    /*
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 (19A583).
    This demo is an update of previous demos for Catalina 10.15 Beta releases, which has been updated to
    support the final API changes Apple has made for Catalina 10.15.
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Big Sur 11.3 (20E241).
    Minumum supported version: macOS Catalina 10.15
    This demo is an update of previous demos, which has been updated to support the latest API changes
    Apple has made for macos 11 Big Sur.
    The demo has also been expanded significantly to include more detail and cover more of the API.
    The code, hopefully, should be self explanitory. Important details are marked by a comment
    starting with "Note:".
    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.
    1. Build on Xcode 12 (tested with 12.5), with the macOS deployment target set to 10.15 (or later).
    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).
    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).
    i) Running with no aguments will process messages serially.
    ii) Running with any arguments will delay the blocking of a matched application for 20 seconds,
    but allow other messages to be processed without being delayed.
    i) Running with no aguments will display a simple usage message.
    ii) Running with the 'serial' argument will run the demo using
    the example serial event message handler.
    iii) Running with the 'asynchronous' argument will run the demo using
    the example asynchronous event message handler.
    iv) Adding the 'verbose' argument at the end will turn on verbose logging.
    3. Terminal will display messages related to subscribed events.
    4. The demo will block the top binary and Calculator app bundle from running.
    4. The demo will demonstrate processing Endpoint Security event messages
    serially or asynchronously (depending on the selected command line argument given).
    The demo will also demonstrate using Endpoint Security Auth events to make the
    following Auth based decsions:
    i) Block the 'top' binary and 'Calculator' app bundle from running.
    ii) Block 'vim' binary from reading plain text files.
    5. CTL-C to exit.
    */

    #pragma mark - Logging
    #pragma mark Globals

    #define BOOL_VALUE(x) x ? "Yes" : "No"
    #define LOG_INFO(fmt, ...) NSLog(@#fmt, ##__VA_ARGS__)
    #define LOG_ERROR(fmt, ...) NSLog(@"ERROR: " @#fmt, ##__VA_ARGS__)
    es_client_t *g_client = nil;
    NSSet *g_blocked_paths = nil;

    // Endpoint Security event handler selected at startup from the command line
    es_handler_block_t g_handler = nil;

    // Used to detect if any events have been dropped by the kernel
    uint64_t g_global_seq_num = 0;
    NSMutableDictionary *g_seq_nums = nil;

    // Set to true if want to cache the results of an auth event response
    bool g_cache_auth_results = false;

    // Logs can become quite busy, especially when subscribing to ES_EVENT_TYPE_AUTH_OPEN events.
    // Only log all event messages when the flag is enabled;
    // otherwise only denied Auth event messages will be logged.
    bool g_verbose_logging = false;

    #pragma mark Helpers - Mach Absolute Time

    NSString* esstring_to_nsstring(const es_string_token_t *es_string_token) {
    NSString *res = @"";
    // This could be running on either Apple Silicon or Intel based CPUs.
    // We will need to apply timebase information when converting Mach absolute time to nanoseconds:
    // https://developer.apple.com/documentation/apple_silicon/addressing_architectural_differences_in_your_macos_code#3616875
    //
    // Note: Running x86_64 code running under Rosetta 2 will have timebase information for Intel CPUs.
    // This will cause discrepancies when converting Mach absolute time values from Endpoint Security Messages.
    // The best option would be to compile your client as a universal binary:
    // https://developer.apple.com/documentation/xcode/building_a_universal_macos_binary
    uint64_t MachTimeToNanoseconds(uint64_t machTime) {
    uint64_t nanoseconds = 0;
    static mach_timebase_info_data_t sTimebase;
    if(sTimebase.denom == 0)
    (void)mach_timebase_info(&sTimebase);

    nanoseconds = ((machTime * sTimebase.numer) / sTimebase.denom);

    return nanoseconds;
    }

    int MachTimeToSeconds(uint64_t machTime) {
    return MachTimeToNanoseconds(machTime) / 1.0e9;
    }

    #pragma mark Helpers - Code Signing

    typedef struct {
    const NSString* name;
    int value;
    } CSFlag;

    #define CSFLAG(flag) {@#flag, flag}

    // Code signing flags defined in cs_blobs.h
    const CSFlag g_csFlags[] = {
    CSFLAG(CS_VALID),
    CSFLAG(CS_ADHOC),
    CSFLAG(CS_GET_TASK_ALLOW),
    CSFLAG(CS_INSTALLER),
    CSFLAG(CS_FORCED_LV),
    CSFLAG(CS_INVALID_ALLOWED),
    CSFLAG(CS_HARD),
    CSFLAG(CS_KILL),
    CSFLAG(CS_CHECK_EXPIRATION),
    CSFLAG(CS_RESTRICT),
    CSFLAG(CS_ENFORCEMENT),
    CSFLAG(CS_REQUIRE_LV),
    CSFLAG(CS_ENTITLEMENTS_VALIDATED),
    CSFLAG(CS_NVRAM_UNRESTRICTED),
    CSFLAG(CS_RUNTIME),
    CSFLAG(CS_LINKER_SIGNED),
    CSFLAG(CS_ALLOWED_MACHO),
    CSFLAG(CS_EXEC_SET_HARD),
    CSFLAG(CS_EXEC_SET_KILL),
    CSFLAG(CS_EXEC_SET_ENFORCEMENT),
    CSFLAG(CS_EXEC_INHERIT_SIP),
    CSFLAG(CS_KILLED),
    CSFLAG(CS_DYLD_PLATFORM),
    CSFLAG(CS_PLATFORM_BINARY),
    CSFLAG(CS_PLATFORM_PATH),
    CSFLAG(CS_DEBUGGED),
    CSFLAG(CS_SIGNED),
    CSFLAG(CS_DEV_CODE)
    };

    NSString* codesigning_flags_str(const uint32_t codesigning_flags) {
    NSMutableArray *match_flags = [NSMutableArray new];

    if (es_string_token && es_string_token->data && es_string_token->length > 0) {
    // es_string_token->data is a pointer to a null-terminated string
    res = [NSString stringWithUTF8String:es_string_token->data];
    // Test which code signing flags have been set and add the matched ones to an array
    for(int i = 0; i < (sizeof g_csFlags / sizeof *g_csFlags); i++) {
    if((codesigning_flags & g_csFlags[i].value) == g_csFlags[i].value) {
    [match_flags addObject:g_csFlags[i].name];
    }
    }

    return res;
    return [match_flags componentsJoinedByString:@","];
    }

    #pragma mark Helpers - Endpoint Security

    NSString* esstring_to_nsstring(const es_string_token_t es_string_token) {
    if(es_string_token.data && es_string_token.length > 0) {
    // es_string_token.data is a pointer to a null-terminated string
    return [NSString stringWithUTF8String:es_string_token.data];
    } else {
    return @"";
    }
    }

    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_AUTH_OPEN: return @"ES_EVENT_TYPE_AUTH_OPEN";
    case ES_EVENT_TYPE_NOTIFY_FORK: return @"ES_EVENT_TYPE_NOTIFY_FORK";
    default: return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d", event_type];
    default: return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d",
    event_type];
    }
    }

    // On macOS 11 Big Sur, Apple have deprecated es_copy_message in favour of es_retain_message
    es_message_t * copy_message(const es_message_t * msg) {
    if(@available(macOS 11.0, *)) {
    es_retain_message(msg);
    // simulate a copy
    return (es_message_t*) msg;
    } else {
    return es_copy_message(msg);
    }
    }

    // On macOS 11 Big Sur, Apple have deprecated es_free_message in favour of es_release_message
    void free_message(es_message_t * _Nonnull msg) {
    if(@available(macOS 11.0, *)) {
    es_release_message(msg);
    } else {
    es_free_message(msg);
    }
    }

    #pragma mark Helpers - Misc

    NSString* fdtype_str(const uint32_t fdtype) {
    switch(fdtype) {
    case PROX_FDTYPE_ATALK: return @"PROX_FDTYPE_ATALK";
    case PROX_FDTYPE_VNODE: return @"PROX_FDTYPE_VNODE";
    case PROX_FDTYPE_SOCKET: return @"PROX_FDTYPE_SOCKET";
    case PROX_FDTYPE_PSHM: return @"PROX_FDTYPE_PSHM";
    case PROX_FDTYPE_PSEM: return @"PROX_FDTYPE_PSEM";
    case PROX_FDTYPE_KQUEUE: return @"PROX_FDTYPE_KQUEUE";
    case PROX_FDTYPE_PIPE: return @"PROX_FDTYPE_PIPE";
    case PROX_FDTYPE_FSEVENTS: return @"PROX_FDTYPE_FSEVENTS";
    case PROX_FDTYPE_NETPOLICY: return @"PROX_FDTYPE_NETPOLICY";
    default: return [NSString stringWithFormat:@"Unknown/Unsupported fdtype: %d",
    fdtype];
    }
    }

    NSString* formatted_date_str(__darwin_time_t secs_since_1970) {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:secs_since_1970];
    return [NSDateFormatter localizedStringFromDate:date
    dateStyle:NSDateFormatterShortStyle
    timeStyle:NSDateFormatterShortStyle];
    }

    bool is_plain_text_file(const NSString* path) {
    if(@available(macOS 11.0, *)) {
    UTType* utt = [UTType typeWithFilenameExtension:[path pathExtension]];
    return [utt conformsToType:UTTypePlainText];
    } else {
    return [[NSWorkspace sharedWorkspace]
    filenameExtension:[path pathExtension]
    isValidForType:@"public.plain-text"];
    }
    }

    #pragma mark - Logging

    #define BOOL_VALUE(x) x ? "Yes" : "No"

    int g_log_indent = 0;
    #define LOG_INDENT_INC() {g_log_indent += 2;}
    #define LOG_INDENT_DEC() {g_log_indent -= 2;}

    #define LOG_IMPORTANT_INFO(fmt, ...) NSLog(@"*** " @#fmt @" ***", ##__VA_ARGS__)
    #define LOG_INFO(fmt, ...) NSLog(@"%*s" @#fmt, g_log_indent, "", ##__VA_ARGS__)
    #define LOG_ERROR(fmt, ...) NSLog(@"ERROR: " @#fmt, ##__VA_ARGS__)

    #define LOG_VERBOSE_EVENT_MESSAGE(msg) { \
    if(g_verbose_logging) { \
    log_event_message(msg); \
    } \
    }

    #define LOG_NON_VERBOSE_EVENT_MESSAGE(msg) { \
    if(!g_verbose_logging) { \
    log_event_message(msg); \
    } \
    }

    bool log_subscribed_events(void) {
    // Log the subscribed events
    size_t subscription_count = 0;
    es_event_type_t *subscription_ids = NULL;
    es_return_t result = es_subscriptions(g_client, &subscription_count, &subscription_ids);
    NSMutableArray *subs = [NSMutableArray new];

    if(ES_RETURN_SUCCESS != result)
    {
    LOG_ERROR("es_subscriptions: ES_RETURN_ERROR");
    return false;
    }

    for(int i = 0; i < subscription_count; i++) {
    [subs addObject:event_type_str(subscription_ids[i])];
    }

    LOG_IMPORTANT_INFO("Subscribed Events: %@", [subs componentsJoinedByString:@", "]);

    free(subscription_ids);
    return true;
    }

    void log_audit_token(const NSString* header, const audit_token_t audit_token) {
    LOG_INFO("%@:", header);
    LOG_INDENT_INC();
    LOG_INFO("pid: %d", audit_token_to_pid(audit_token));
    LOG_INFO("ruid: %d", audit_token_to_ruid(audit_token));
    LOG_INFO("euid: %d", audit_token_to_euid(audit_token));
    LOG_INFO("rgid: %d", audit_token_to_rgid(audit_token));
    LOG_INFO("egid: %d", audit_token_to_egid(audit_token));
    LOG_INDENT_DEC();
    }

    void log_file(const NSString* header, const es_file_t* file) {
    if(!file) {
    LOG_INFO("%@: (null)", header);
    return;
    }

    LOG_INFO("%@:", header);
    LOG_INDENT_INC();
    LOG_INFO("path: %@", esstring_to_nsstring(file->path));
    LOG_INFO("path_truncated: %s", BOOL_VALUE(file->path_truncated));

    LOG_INFO("stat.st_dev: %d", file->stat.st_dev);
    LOG_INFO("stat.st_ino: %llu", file->stat.st_ino);
    LOG_INFO("stat.st_mode: %u", file->stat.st_mode);
    LOG_INFO("stat.st_nlink: %u", file->stat.st_nlink);

    LOG_INFO("stat.st_uid: %u", file->stat.st_uid);
    LOG_INFO("stat.st_gid: %u", file->stat.st_gid);

    LOG_INFO("stat.st_atimespec: %@", formatted_date_str(file->stat.st_atimespec.tv_sec));
    LOG_INFO("stat.st_mtimespec: %@", formatted_date_str(file->stat.st_mtimespec.tv_sec));
    LOG_INFO("stat.st_ctimespec: %@", formatted_date_str(file->stat.st_ctimespec.tv_sec));
    LOG_INFO("stat.st_birthtimespec: %@", formatted_date_str(file->stat.st_birthtimespec.tv_sec));

    LOG_INFO("stat.st_size: %lld", file->stat.st_size);
    LOG_INFO("stat.st_blocks: %lld", file->stat.st_blocks);
    LOG_INFO("stat.st_blksize: %d", file->stat.st_blksize);
    LOG_INFO("stat.st_flags: %u", file->stat.st_flags);
    LOG_INFO("stat.st_gen: %u", file->stat.st_gen);
    LOG_INDENT_DEC();
    }

    void log_proc(const NSString* header, const es_process_t *proc) {
    void log_proc(uint32_t msg_version, const NSString* header, const es_process_t* proc) {
    if(!proc) {
    LOG_INFO("%@: (null)", header);
    return;
    }

    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.ruid: %d", audit_token_to_ruid(proc->audit_token));
    LOG_INFO(" proc.euid: %d", audit_token_to_euid(proc->audit_token));
    LOG_INFO(" proc.rgid: %d", audit_token_to_rgid(proc->audit_token));
    LOG_INFO(" proc.egid: %d", audit_token_to_egid(proc->audit_token));
    LOG_INFO(" proc.group_id: %d", proc->group_id);
    LOG_INFO(" proc.session_id: %d", proc->session_id);
    LOG_INFO(" proc.codesigning_flags: %x", proc->codesigning_flags);
    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: %@", esstring_to_nsstring(&proc->signing_id));
    LOG_INFO(" proc.team_id: %@", esstring_to_nsstring(&proc->team_id));
    LOG_INDENT_INC();
    log_audit_token(@"proc.audit_token", proc->audit_token);
    LOG_INFO("proc.ppid: %d", proc->ppid);
    LOG_INFO("proc.original_ppid: %d", proc->original_ppid);

    if(msg_version >= 4) {
    log_audit_token(@"proc.responsible_audit_token", proc->responsible_audit_token);
    log_audit_token(@"proc.parent_audit_token", proc->parent_audit_token);
    }

    LOG_INFO("proc.group_id: %d", proc->group_id);
    LOG_INFO("proc.session_id: %d", proc->session_id);
    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: %@", esstring_to_nsstring(proc->signing_id));
    LOG_INFO("proc.team_id: %@", esstring_to_nsstring(proc->team_id));

    if(msg_version >= 3) {
    LOG_INFO("proc.start_time: %@", formatted_date_str(proc->start_time.tv_sec));
    }

    LOG_INFO("proc.codesigning_flags: %x (%@)",
    proc->codesigning_flags, codesigning_flags_str(proc->codesigning_flags));

    // proc.cdhash
    NSMutableString *hash = [NSMutableString string];
    for(uint32_t i = 0; i < CS_CDHASH_LEN; i++) {
    [hash appendFormat:@"%x", proc->cdhash[i]];
    [hash appendFormat:@"%02x", proc->cdhash[i]];
    }
    LOG_INFO("proc.cdhash: %@", hash);

    log_file(@"proc.executable", proc->executable);

    if(msg_version >= 2 && proc->tty) {
    log_file(@"proc.tty", proc->tty);
    }

    LOG_INDENT_DEC();
    }

    void log_command_line_arguments(const es_event_exec_t* exec) {
    uint32_t arg_count = es_exec_arg_count(exec);
    LOG_INFO("event.exec.arg_count: %u", arg_count);
    LOG_INDENT_INC();

    // Extract each argument and log it out
    for(uint32_t i = 0; i < arg_count; i++) {
    es_string_token_t arg = es_exec_arg(exec, i);
    LOG_INFO("arg[%d]: %@", i, esstring_to_nsstring(arg));
    }

    LOG_INDENT_DEC();
    }

    void log_environment_variable(const es_event_exec_t* exec) {
    uint32_t env_count = es_exec_env_count(exec);
    LOG_INFO("event.exec.env_count: %u", env_count);
    LOG_INDENT_INC();

    // Extract each env and log it out
    for(uint32_t i = 0; i < env_count; i++) {
    es_string_token_t arg = es_exec_env(exec, i);
    LOG_INFO("env[%d]: %@", i, esstring_to_nsstring(arg));
    }

    LOG_INDENT_DEC();
    }

    void log_file_descriptors(const es_event_exec_t* exec) {
    if(@available(macOS 11.0, *)) {
    uint32_t fd_count = es_exec_fd_count(exec);
    LOG_INFO("event.exec.fd_count: %u", fd_count);
    LOG_INDENT_INC();

    // Extract each fd and log it out
    for(uint32_t i = 0; i < fd_count; i++) {
    // Pointer must not outlive event
    const es_fd_t *arg = es_exec_fd(exec, i);

    LOG_INFO("fd[%d].fd: %d", i, arg->fd);
    LOG_INFO("fd[%d].fdtype: %@", i, fdtype_str(arg->fdtype));

    if(PROX_FDTYPE_PIPE == arg->fdtype) {
    LOG_INFO("fd[%d].fd: %llu", i, arg->pipe.pipe_id);
    }
    }

    LOG_INDENT_DEC();
    }
    LOG_INFO(" proc.cdhash: %@", hash);
    LOG_INFO(" proc.executable.path: %@",
    proc->executable ? esstring_to_nsstring(&proc->executable->path) : @"(null)");
    }

    void log_event_exec(uint32_t msg_version, const es_event_exec_t* exec) {
    log_proc(msg_version, @"event.exec.target", exec->target);
    log_command_line_arguments(exec);
    log_environment_variable(exec);
    log_file_descriptors(exec);

    if(msg_version >= 2 && exec->script) {
    log_file(@"event.exec.script", exec->script);
    }

    if(msg_version >= 3) {
    log_file(@"event.exec.cwd", exec->cwd);
    }

    if(msg_version >= 4) {
    LOG_INFO("event.exec.last_fd: %d", exec->last_fd);
    }
    }

    void log_event_open(const es_event_open_t* open) {
    NSMutableArray *match_flags = [NSMutableArray new];

    if((open->fflag & FREAD) == FREAD) {
    [match_flags addObject:@"FREAD"];
    }

    if((open->fflag & FWRITE) == FWRITE) {
    [match_flags addObject:@"FWRITE"];
    }

    LOG_INFO("event.open.fflag: %d (%@)",
    open->fflag, [match_flags componentsJoinedByString:@", "]);
    log_file(@"event.open.file", open->file);
    }

    // Logs the top level datatype sent by Endpoint Security subsystem to its clients
    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);
    // Note: Message structure could change in future versions
    LOG_INFO("version: %u", msg->version);
    LOG_INFO("time: %lld.%.9ld", (long long) msg->time.tv_sec, msg->time.tv_nsec);

    // Note: Apple have designed the Endpoint Security structures to support additional fields
    // in the future. Always check the version of the message before using a field, in the message
    // or sub-structure, which has been added to a later version of Endpoint Security.
    // Only new fields are added. Existing fields should be available in future revisions.
    uint32_t version = msg->version;
    LOG_INFO("version: %u", version);

    LOG_INFO("time: %@", formatted_date_str(msg->time.tv_sec));
    LOG_INFO("mach_time: %lld", (long long) msg->mach_time);

    // It's very important that the message is processed within the deadline:
    // Note: It's very important that an auth event is processed within the deadline:
    // https://developer.apple.com/documentation/endpointsecurity/es_message_t/3334985-deadline
    // From an Apple Security Engineer:
    // "You must respond by the deadline.
    // It is not configurable.
    // It won't get longer, but it will get shorter."
    // https://developer.apple.com/forums/thread/649552?answerId=615802022#615802022
    LOG_INFO("deadline: %lld", (long long) msg->deadline);

    uint64_t deadlineInterval = msg->deadline;
    @@ -114,31 +492,43 @@ void log_event_message(const es_message_t *msg) {
    deadlineInterval -= msg->mach_time;
    }

    LOG_INFO("deadline interval: %lld (%d seconds)", (long long) deadlineInterval,
    (int) (deadlineInterval / 1.0e9));
    LOG_INFO("deadline interval: %lld (%d seconds)",
    (long long) deadlineInterval,
    MachTimeToSeconds(deadlineInterval));

    // Note: You can use the seq_num field to detect if the kernel had to drop any event messages,
    // for an event type, to the client.
    if(version >= 2) {
    LOG_INFO("seq_num: %lld", msg->seq_num);
    }

    // Note: You can use the global_seq_num field to detect if the kernel had to drop any event
    // messages to the client.
    if(version >= 4) {
    LOG_INFO("global_seq_num: %lld", msg->global_seq_num);
    }

    if(version >= 4 && msg->thread) {
    LOG_INFO("thread_id: %lld", msg->thread->thread_id);
    }

    LOG_INFO("action_type: %s", (msg->action_type == ES_ACTION_TYPE_AUTH) ? "Auth" : "Notify");
    log_proc(@"process", msg->process);
    log_proc(version, @"process", msg->process);

    // Type specific logging
    // Event specific logging
    switch(msg->event_type) {
    case ES_EVENT_TYPE_AUTH_EXEC: {
    log_proc(@"event.exec.target", msg->event.exec.target);

    // Log program arguments
    uint32_t argCount = es_exec_arg_count(&msg->event.exec);
    LOG_INFO("event.exec.arg_count: %u", argCount);
    log_event_exec(version, &msg->event.exec);
    }
    break;

    // Extract each argument and log it out
    for(uint32_t i = 0; i < argCount; i++) {
    es_string_token_t arg = es_exec_arg(&msg->event.exec, i);
    LOG_INFO("arg %d: %@", i, esstring_to_nsstring(&arg));
    }
    case ES_EVENT_TYPE_AUTH_OPEN: {
    log_event_open(&msg->event.open);
    }
    break;

    case ES_EVENT_TYPE_NOTIFY_FORK: {
    log_proc(@"event.fork.child", msg->event.fork.child);
    log_proc(version, @"event.fork.child", msg->event.fork.child);
    }
    break;

    @@ -151,151 +541,310 @@ void log_event_message(const es_message_t *msg) {
    LOG_INFO("");
    }

    #pragma mark - Endpoint Secuirty Demo
    // Demonstrates detecting dropped event messages from the kernel, by either
    // using the using the seq_num or global_seq_num fields in an event message
    void detect_and_log_dropped_events(const es_message_t *msg) {
    uint32_t version = msg->version;

    // Note: You can use the seq_num field to detect if the kernel had to
    // drop any event messages, for an event type, to the client.
    if(version >= 2) {
    uint64_t seq_num = msg->seq_num;

    NSString *type = event_type_str(msg->event_type);
    NSNumber *last_seq_num = [g_seq_nums objectForKey:type];

    if(last_seq_num) {
    uint64_t expected_seq_num = [last_seq_num unsignedLongLongValue] + 1;

    if(seq_num > expected_seq_num) {
    LOG_ERROR("EVENTS DROPPED! seq_num is ahead by: %llu",
    (seq_num - expected_seq_num));
    }
    }

    [g_seq_nums setObject:[NSNumber numberWithUnsignedLong:seq_num] forKey:type];
    }

    // Note: You can use the global_seq_num field to detect if the kernel had to
    // drop any event messages to the client.
    if(version >= 4) {
    uint64_t global_seq_num = msg->global_seq_num;

    if(global_seq_num > ++g_global_seq_num) {
    LOG_ERROR("EVENTS DROPPED! global_seq_num is ahead by: %llu",
    (global_seq_num - g_global_seq_num));
    g_global_seq_num = global_seq_num;
    }
    }
    }

    es_client_t *g_client = nil;
    NSSet *g_blockedPaths = nil;
    #pragma mark - Endpoint Secuirty Demo

    // Clean-up before exiting
    void sig_handler(int sig) {
    LOG_INFO("Tidying Up");
    LOG_IMPORTANT_INFO("Tidying Up");

    if(g_client) {
    es_unsubscribe_all(g_client);
    es_delete_client(g_client);
    }

    LOG_INFO("Exiting");
    LOG_IMPORTANT_INFO("Exiting");
    exit(EXIT_SUCCESS);
    }

    // Simple handler to make AUTH (allow or block) decisions.
    void print_usage(const char *name) {
    printf("Usage: %s (serial | asynchronous) (verbose)\n", name);
    printf("Arguments:\n");
    printf("\tserial\t\tUse serial message handler\n");
    printf("\tasynchronous\tUse asynchronous message handler\n");
    printf("\tverbose\t\tTurns on verbose logging\n");
    }

    // 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) {
    // NOTE: You should ignore events from other ES Clients;
    // otherwise you may trigger more events causing a potentially infinite cycle.
    if(msg->process->is_es_client) {
    return ES_AUTH_RESULT_ALLOW;
    }

    // Ignore events from root processes
    if(0 == audit_token_to_ruid(msg->process->audit_token)) {
    return ES_AUTH_RESULT_ALLOW;
    }

    // Block exec if path of process is in our blocked paths list
    if(ES_EVENT_TYPE_AUTH_EXEC == msg->event_type) {
    NSString *path = esstring_to_nsstring(&msg->event.exec.target->executable->path);
    NSString *path = esstring_to_nsstring(msg->event.exec.target->executable->path);

    if(![g_blocked_paths containsObject:path]) {
    return ES_AUTH_RESULT_ALLOW;
    }

    // Process is in our blocked list
    LOG_IMPORTANT_INFO("BLOCKING EXEC: %@", path);
    return ES_AUTH_RESULT_DENY;
    }

    // Block vim accessing plain text files
    if(ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) {
    NSString *processPath = esstring_to_nsstring(msg->process->executable->path);

    if(![processPath isEqualToString:@"/usr/bin/vim"]) {
    // Not vim
    return ES_AUTH_RESULT_ALLOW;
    }

    NSString *filePath = esstring_to_nsstring(msg->event.open.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;
    if(!is_plain_text_file(filePath)) {
    // Not a text file
    return ES_AUTH_RESULT_ALLOW;
    }

    // Process is vim trying to access a text file
    LOG_IMPORTANT_INFO("BLOCKING OPEN: %@", filePath);
    return ES_AUTH_RESULT_DENY;
    }

    // All good
    return ES_AUTH_RESULT_ALLOW;
    }

    int main(int argc, const char * argv[]) {
    signal(SIGINT, &sig_handler);
    // Sends a response back to Endpoint Security for an auth event
    // Note: You must always send a response back before the deadline expires.
    void respond_to_auth_event(es_client_t *clt, const es_message_t *msg, es_auth_result_t result) {
    // Only log ES_AUTH_RESULT_DENY results when verbose logging is disabled
    if(ES_AUTH_RESULT_DENY == result) {
    LOG_NON_VERBOSE_EVENT_MESSAGE(msg);
    }

    @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];
    // Note: You use es_respond_auth_result() to respond to auth events,
    // except for ES_EVENT_TYPE_AUTH_OPEN events, which require a response
    // using es_respond_flags_result() instead.
    if(ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) {
    uint32_t authorized_flags = 0;

    // Example of a simple handler to process event messages serially from Endpoint Security
    es_handler_block_t serialHandler = ^(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
    );

    if(ES_RESPOND_RESULT_SUCCESS != res) {
    LOG_ERROR("es_respond_auth_result: %d", res);
    }
    }
    };
    if(ES_AUTH_RESULT_ALLOW == result) {
    authorized_flags = msg->event.open.fflag;
    }

    // Example of a handler to process event messages out of order from Endpoint Security.
    es_handler_block_t deferedHandler = ^(es_client_t *clt, const es_message_t *msg) {
    es_message_t *copiedMsg = es_copy_message(msg);

    if(!copiedMsg) {
    LOG_ERROR("Failed to copy message");
    return;
    }

    log_event_message(copiedMsg);

    // Process 'ES_ACTION_TYPE_AUTH' events on a separate thread
    // and sleep for 20s if action is ES_AUTH_RESULT_DENY.
    // Other events will not have to wait and can will be processed out of order.
    if(ES_ACTION_TYPE_AUTH == copiedMsg->action_type) {
    dispatch_async(
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){
    es_auth_result_t authResult = auth_event_handler(copiedMsg);

    if(ES_AUTH_RESULT_DENY == authResult) {
    [NSThread sleepForTimeInterval:20.0];
    }

    es_respond_result_t res =
    es_respond_auth_result(clt, copiedMsg, authResult, false);

    if(ES_RESPOND_RESULT_SUCCESS != res) {
    LOG_ERROR("es_respond_auth_result: %d", res);
    }

    es_free_message(copiedMsg);
    }
    );
    return;
    }

    es_free_message(copiedMsg);
    };
    es_respond_result_t res =
    es_respond_flags_result(clt, msg, authorized_flags, g_cache_auth_results);

    // 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,
    (argc > 0) ? deferedHandler : serialHandler);
    if(ES_RESPOND_RESULT_SUCCESS != res) {
    LOG_ERROR("es_respond_flags_result: %d", res);
    }

    } else {
    es_respond_result_t res =
    es_respond_auth_result(clt, msg, result, g_cache_auth_results);

    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);
    if(ES_RESPOND_RESULT_SUCCESS != res) {
    LOG_ERROR("es_respond_auth_result: %d", res);
    }
    }
    }

    // Example of an event message handler to process event messages serially from Endpoint Security.
    es_handler_block_t serial_message_handler = ^(es_client_t *clt, const es_message_t *msg) {
    // Endpoint Security, by default, calls a event message handler serially for each message.

    LOG_VERBOSE_EVENT_MESSAGE(msg);

    // NOTE: It is important to process events in a timely manner.
    // The kernel will start to drop events for the client if they are not responded to in time.
    detect_and_log_dropped_events(msg);

    // Auth events require a response sent back before the deadline expires
    if(ES_ACTION_TYPE_AUTH == msg->action_type) {
    respond_to_auth_event(clt, msg, auth_event_handler(msg));
    }
    };

    // Example of an event message handler to process event messages asynchronously from Endpoint Security
    es_handler_block_t asynchronous_message_handler = ^(es_client_t *clt, const es_message_t *msg) {
    // Endpoint Security, by default, calls a event message handler serially for each message.
    // We copy/retain the message so that we can process and respond to auth events asynchronously.

    LOG_VERBOSE_EVENT_MESSAGE(msg);

    // NOTE: It is important to process events in a timely manner.
    // The kernel will start to drop events for the client if they are not responded to in time.
    detect_and_log_dropped_events(msg);

    // Copy/Retain the event message so that we process the event asynchronously
    es_message_t *copied_msg = copy_message(msg);

    if(!copied_msg) {
    LOG_ERROR("Failed to copy message");
    return;
    }

    // Demonstrates handling events out of order, by processing 'ES_ACTION_TYPE_AUTH' events on
    // a separate thread. Sleep for 20s for 'ES_EVENT_TYPE_AUTH_EXEC' events if the result
    // is an ES_AUTH_RESULT_DENY.
    if(ES_ACTION_TYPE_AUTH == copied_msg->action_type) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){
    es_auth_result_t result = auth_event_handler(copied_msg);

    if(ES_AUTH_RESULT_DENY == result &&
    ES_EVENT_TYPE_AUTH_EXEC == copied_msg->event_type) {
    [NSThread sleepForTimeInterval:20.0];
    }

    return 1;
    // Auth events require a response sent back before the deadline expires
    respond_to_auth_event(clt, copied_msg, result);
    free_message(copied_msg);
    });

    return;
    }

    // Free/release the message
    free_message(copied_msg);
    };

    es_handler_block_t get_message_handler_from_commandline_args(int argc, const char * argv[]) {
    if (argc < 2) {
    // No command line argument was given
    return nil;
    }

    // check if verbose logging argument was given
    if (argc > 2) {
    NSString *verbose = [[NSString stringWithUTF8String:argv[2]] lowercaseString];
    g_verbose_logging = [verbose isEqualToString:@"verbose"];
    }

    // Try and find an event message handler that matches the first command line argument
    NSString *arg = [[NSString stringWithUTF8String:argv[1]] lowercaseString];

    NSDictionary *handlers = @{
    @"serial" : serial_message_handler,
    @"asynchronous" : asynchronous_message_handler
    };

    return [handlers objectForKey:arg];
    }

    bool setup_endpoint_security(void) {
    // 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, g_handler);

    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);
    }

    // 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 false;
    }

    // Explicitly clear the cache of previous cached results from this demo or other ES Clients
    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 false;
    }

    // Subscribe to the events we're interested in
    es_event_type_t events[] = {
    ES_EVENT_TYPE_AUTH_EXEC
    , ES_EVENT_TYPE_AUTH_OPEN
    , ES_EVENT_TYPE_NOTIFY_FORK
    };

    es_return_t subscribed = es_subscribe(g_client, events, sizeof events / sizeof *events);

    if(ES_RETURN_ERROR == subscribed) {
    LOG_ERROR("es_subscribe: ES_RETURN_ERROR");
    return false;
    }

    // All good
    return log_subscribed_events();
    }

    int main(int argc, const char * argv[]) {
    signal(SIGINT, &sig_handler);

    @autoreleasepool {
    // Init global vars
    g_handler = get_message_handler_from_commandline_args(argc, argv);

    if(!g_handler) {
    print_usage(argv[0]);
    return 1;
    }

    // Subscribe to the events we're interested in
    es_event_type_t events[] = {
    ES_EVENT_TYPE_AUTH_EXEC
    , ES_EVENT_TYPE_NOTIFY_FORK
    };
    g_seq_nums = [NSMutableDictionary new];

    es_return_t subscribed = es_subscribe(g_client,
    events,
    (sizeof(events) / sizeof((events)[0])) // Event count
    );
    // List of paths to be blocked.
    // For this demo we will block the top binary and Calculator app bundle.
    g_blocked_paths = [NSSet setWithObjects:
    @"/usr/bin/top",
    @"/System/Applications/Calculator.app/Contents/MacOS/Calculator",
    nil];

    if(ES_RETURN_ERROR == subscribed) {
    LOG_ERROR("es_subscribe: ES_RETURN_ERROR");
    if(!setup_endpoint_security()) {
    return 1;
    }

    // Note: Endpoint Security have a set of es_mute* functions to suppress events for a process.
    // Uncomment the 'es_mute_path_literal' line below to stop receiving events from the 'vim' binary.
    // This program will then stop receiving 'ES_EVENT_TYPE_AUTH_OPEN' events for vim and will no
    // longer be able to block vim from opening plain text files.
    // es_mute_path_literal(g_client, "/usr/bin/vim");

    // Start handling events from Endpoint Security
    dispatch_main();
    }

  6. @Omar-Ikram Omar-Ikram revised this gist Dec 1, 2019. No changes.
  7. @Omar-Ikram Omar-Ikram revised this gist Dec 1, 2019. 1 changed file with 92 additions and 31 deletions.
    123 changes: 92 additions & 31 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -4,43 +4,38 @@
    //
    // Created by Omar Ikram on 17/06/2019 - Catalina 10.15 Beta 1 (19A471t)
    // Updated by Omar Ikram on 15/08/2019 - Catalina 10.15 Beta 5 (19A526h)
    // Updated by Omar Ikram on 01/12/2019 - Catalina 10.15 (19A583)
    //

    #import <Foundation/Foundation.h>
    #import <EndpointSecurity/EndpointSecurity.h>
    #import <bsm/libbsm.h>
    #import <signal.h>
    #import <mach/mach_time.h>

    /*
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 Beta 5
    (19A526h)
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 (19A583).
    This demo is an update of a previous demo for Catalina 10.15 Beta 1, which has been updated to
    support the API changes Apple has made for Catalina 10.15 Beta 5 (Apple have been busy).
    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.
    This demo is an update of previous demos for Catalina 10.15 Beta releases, which has been updated to
    support the final API changes Apple has made for Catalina 10.15.
    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 Beta 5 (11M382q), with macOS deployment target set to 10.15.
    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 Beta 5 machine which has SIP disabled (best to use a VM).
    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).
    i) Running with no aguments will process messages serially.
    ii) Running with any arguments will delay the blocking of a matched application for 20 seconds,
    but allow other messages to be processed without being delayed.
    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_5_release_notes#3318224
    */

    #pragma mark - Logging
    @@ -52,12 +47,9 @@
    NSString* esstring_to_nsstring(const es_string_token_t *es_string_token) {
    NSString *res = @"";

    if (es_string_token && es_string_token->data && es_string_token->size > 0) {
    NSData *es_data = [NSData dataWithBytes:es_string_token->data length:es_string_token->size];

    if ([es_data length] != 0) {
    res = [NSString stringWithUTF8String:[es_data bytes]];
    }
    if (es_string_token && es_string_token->data && es_string_token->length > 0) {
    // es_string_token->data is a pointer to a null-terminated string
    res = [NSString stringWithUTF8String:es_string_token->data];
    }

    return res;
    @@ -73,10 +65,21 @@
    }

    void log_proc(const NSString* header, const es_process_t *proc) {
    if(!proc) {
    LOG_INFO("%@: (null)", header);
    return;
    }

    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.ruid: %d", audit_token_to_ruid(proc->audit_token));
    LOG_INFO(" proc.euid: %d", audit_token_to_euid(proc->audit_token));
    LOG_INFO(" proc.rgid: %d", audit_token_to_rgid(proc->audit_token));
    LOG_INFO(" proc.egid: %d", audit_token_to_egid(proc->audit_token));
    LOG_INFO(" proc.group_id: %d", proc->group_id);
    LOG_INFO(" proc.session_id: %d", proc->session_id);
    LOG_INFO(" proc.codesigning_flags: %x", proc->codesigning_flags);
    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));
    @@ -89,14 +92,31 @@ void log_proc(const NSString* header, const es_process_t *proc) {
    [hash appendFormat:@"%x", proc->cdhash[i]];
    }
    LOG_INFO(" proc.cdhash: %@", hash);
    LOG_INFO(" proc.file.path: %@", esstring_to_nsstring(&proc->executable->path));
    LOG_INFO(" proc.executable.path: %@",
    proc->executable ? esstring_to_nsstring(&proc->executable->path) : @"(null)");
    }

    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);
    // Note: Message structure could change in future versions
    LOG_INFO("version: %u", msg->version);
    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);

    // It's very important that the message is processed within the deadline:
    // https://developer.apple.com/documentation/endpointsecurity/es_message_t/3334985-deadline
    LOG_INFO("deadline: %lld", (long long) msg->deadline);

    uint64_t deadlineInterval = msg->deadline;

    if(deadlineInterval > 0) {
    deadlineInterval -= msg->mach_time;
    }

    LOG_INFO("deadline interval: %lld (%d seconds)", (long long) deadlineInterval,
    (int) (deadlineInterval / 1.0e9));

    LOG_INFO("action_type: %s", (msg->action_type == ES_ACTION_TYPE_AUTH) ? "Auth" : "Notify");
    log_proc(@"process", msg->process);

    @@ -176,10 +196,8 @@ int main(int argc, const char * argv[]) {
    @"/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) {
    // Example of a simple handler to process event messages serially from Endpoint Security
    es_handler_block_t serialHandler = ^(es_client_t *clt, const es_message_t *msg) {
    log_event_message(msg);

    // Handle subscribed AUTH events:
    @@ -197,7 +215,51 @@ int main(int argc, const char * argv[]) {
    LOG_ERROR("es_respond_auth_result: %d", res);
    }
    }
    });
    };

    // Example of a handler to process event messages out of order from Endpoint Security.
    es_handler_block_t deferedHandler = ^(es_client_t *clt, const es_message_t *msg) {
    es_message_t *copiedMsg = es_copy_message(msg);

    if(!copiedMsg) {
    LOG_ERROR("Failed to copy message");
    return;
    }

    log_event_message(copiedMsg);

    // Process 'ES_ACTION_TYPE_AUTH' events on a separate thread
    // and sleep for 20s if action is ES_AUTH_RESULT_DENY.
    // Other events will not have to wait and can will be processed out of order.
    if(ES_ACTION_TYPE_AUTH == copiedMsg->action_type) {
    dispatch_async(
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){
    es_auth_result_t authResult = auth_event_handler(copiedMsg);

    if(ES_AUTH_RESULT_DENY == authResult) {
    [NSThread sleepForTimeInterval:20.0];
    }

    es_respond_result_t res =
    es_respond_auth_result(clt, copiedMsg, authResult, false);

    if(ES_RESPOND_RESULT_SUCCESS != res) {
    LOG_ERROR("es_respond_auth_result: %d", res);
    }

    es_free_message(copiedMsg);
    }
    );
    return;
    }

    es_free_message(copiedMsg);
    };

    // 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,
    (argc > 0) ? deferedHandler : serialHandler);

    if(ES_NEW_CLIENT_RESULT_SUCCESS != res) {
    if(ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED == res) {
    @@ -221,13 +283,12 @@ int main(int argc, const char * argv[]) {
    // Subscribe to the events we're interested in
    es_event_type_t events[] = {
    ES_EVENT_TYPE_AUTH_EXEC
    , ES_EVENT_TYPE_NOTIFY_OPEN
    , ES_EVENT_TYPE_NOTIFY_FORK
    };

    es_return_t subscribed = es_subscribe(g_client,
    events,
    // Count of es_event_type_t entries stored in events[]
    sizeof(events) / sizeof(es_event_type_t)
    (sizeof(events) / sizeof((events)[0])) // Event count
    );

    if(ES_RETURN_ERROR == subscribed) {
    @@ -239,4 +300,4 @@ int main(int argc, const char * argv[]) {
    }

    return 0;
    }
    }
  8. @Omar-Ikram Omar-Ikram revised this gist Aug 15, 2019. 1 changed file with 58 additions and 42 deletions.
    100 changes: 58 additions & 42 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,8 @@
    // main.m
    // EndpointSecurityDemo
    //
    // Created by Omar Ikram on 17/06/2019.
    // Created by Omar Ikram on 17/06/2019 - Catalina 10.15 Beta 1 (19A471t)
    // Updated by Omar Ikram on 15/08/2019 - Catalina 10.15 Beta 5 (19A526h)
    //

    #import <Foundation/Foundation.h>
    @@ -11,9 +12,12 @@
    #import <signal.h>

    /*
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 Beta 1
    (19A471t)
    A demo of using Apple's new EndpointSecurity framework - tested on macOS Catalina 10.15 Beta 5
    (19A526h)
    This demo is an update of a previous demo for Catalina 10.15 Beta 1, which has been updated to
    support the API changes Apple has made for Catalina 10.15 Beta 5 (Apple have been busy).
    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.
    @@ -23,11 +27,11 @@
    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.
    1. Build on Xcode 11 Beta 5 (11M382q), 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).
    1. Test environment should be a macOS 10.15 Beta 5 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.
    @@ -36,14 +40,7 @@
    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.
    https://developer.apple.com/documentation/macos_release_notes/macos_catalina_10_15_beta_5_release_notes#3318224
    */

    #pragma mark - Logging
    @@ -52,6 +49,20 @@
    #define LOG_INFO(fmt, ...) NSLog(@#fmt, ##__VA_ARGS__)
    #define LOG_ERROR(fmt, ...) NSLog(@"ERROR: " @#fmt, ##__VA_ARGS__)

    NSString* esstring_to_nsstring(const es_string_token_t *es_string_token) {
    NSString *res = @"";

    if (es_string_token && es_string_token->data && es_string_token->size > 0) {
    NSData *es_data = [NSData dataWithBytes:es_string_token->data length:es_string_token->size];

    if ([es_data length] != 0) {
    res = [NSString stringWithUTF8String:[es_data bytes]];
    }
    }

    return res;
    }

    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) {
    @@ -66,49 +77,48 @@ void log_proc(const NSString* header, const es_process_t *proc) {
    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.codesigning_flags: %x", proc->codesigning_flags);
    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);
    LOG_INFO(" proc.signing_id: %@", esstring_to_nsstring(&proc->signing_id));
    LOG_INFO(" proc.team_id: %@", esstring_to_nsstring(&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);
    LOG_INFO(" proc.file.path: %@", esstring_to_nsstring(&proc->executable->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);
    LOG_INFO("action_type: %s", (msg->action_type == ES_ACTION_TYPE_AUTH) ? "Auth" : "Notify");
    log_proc(@"process", msg->process);

    // Type specific logging
    switch(msg->event_type) {
    case ES_EVENT_TYPE_AUTH_EXEC: {
    log_proc(@"event.exec.proc", &msg->event.exec.proc);
    log_proc(@"event.exec.target", msg->event.exec.target);

    // Log program arguments
    uint64_t argCount = es_exec_arg_count(&msg->event.exec);
    LOG_INFO("event.exec.arg_count: %llu", argCount);
    uint32_t argCount = es_exec_arg_count(&msg->event.exec);
    LOG_INFO("event.exec.arg_count: %u", 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]]);
    es_string_token_t arg = es_exec_arg(&msg->event.exec, i);
    LOG_INFO("arg %d: %@", i, esstring_to_nsstring(&arg));
    }
    }
    break;

    case ES_EVENT_TYPE_NOTIFY_FORK: {
    log_proc(@"event.fork.child", &msg->event.fork.child);
    log_proc(@"event.fork.child", msg->event.fork.child);
    }
    break;

    @@ -132,7 +142,7 @@ void sig_handler(int sig) {

    if(g_client) {
    es_unsubscribe_all(g_client);
    //es_delete_client(g_client); // CRASHES THE BOX! (´・_・`)
    es_delete_client(g_client);
    }

    LOG_INFO("Exiting");
    @@ -143,7 +153,7 @@ void sig_handler(int sig) {
    // 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];
    NSString *path = esstring_to_nsstring(&msg->event.exec.target->executable->path);

    // Block if path is in our blocked paths list
    if([g_blockedPaths containsObject:path]) {
    @@ -180,7 +190,7 @@ int main(int argc, const char * argv[]) {
    es_respond_auth_result(clt,
    msg,
    auth_event_handler(msg),
    false // Cache flag doesn't work in 10.15 Beta 1
    false
    );

    if(ES_RESPOND_RESULT_SUCCESS != res) {
    @@ -201,26 +211,32 @@ int main(int argc, const char * argv[]) {
    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;
    }

    // Subscribe to the events we're interested in
    es_event_type_t events[] = {
    ES_EVENT_TYPE_AUTH_EXEC
    , ES_EVENT_TYPE_NOTIFY_OPEN
    };

    es_return_t subscribed = es_subscribe(g_client,
    events,
    // Count of es_event_type_t entries stored in events[]
    sizeof(events) / sizeof(es_event_type_t)
    );

    if(ES_RETURN_ERROR == subscribed) {
    LOG_ERROR("es_subscribe: ES_RETURN_ERROR");
    return 1;
    }

    dispatch_main();
    }

    return 0;
    }
    }
  9. @Omar-Ikram Omar-Ikram renamed this gist Jun 21, 2019. 1 changed file with 0 additions and 0 deletions.
  10. @Omar-Ikram Omar-Ikram created this gist Jun 21, 2019.
    8 changes: 8 additions & 0 deletions EndpointSecurityDemo.entitlements
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    <?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>
    226 changes: 226 additions & 0 deletions EndpointSecurityDemo.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,226 @@
    //
    // 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;
    }