Forked from Omar-Ikram/EndpointSecurityDemo.m
Created
September 6, 2024 02:03
Revisions
-
Omar-Ikram revised this gist
Feb 20, 2022 . 1 changed file with 34 additions and 17 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.2.1 (21D62). 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. 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 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 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 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) { 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 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. -
Omar-Ikram revised this gist
Feb 14, 2022 . 1 changed file with 94 additions and 38 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.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 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. 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; } 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", @"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 @"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 [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 (%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_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", 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: %llu", msg->deadline); uint64_t deadlineInterval = msg->deadline; if(deadlineInterval > 0) { deadlineInterval -= msg->mach_time; } 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 != 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"); } // 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 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 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"); 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 -
Omar-Ikram revised this gist
Jul 4, 2021 . 1 changed file with 162 additions and 81 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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) // /* 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 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 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 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) }; 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(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) { } } 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 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 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) { } \ } 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_muted_paths_events: ES_RETURN_ERROR"); return false; } if(NULL == muted_paths) { // There are no muted paths return true; } 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(); } } es_release_muted_paths(muted_paths); return true; } 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; 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) { // 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"]; } @@ -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 '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"); // 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; } -
Omar-Ikram revised this gist
May 13, 2021 . No changes.There are no files selected for viewing
-
Omar-Ikram revised this gist
May 13, 2021 . 1 changed file with 716 additions and 167 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,110 +2,488 @@ // main.m // EndpointSecurityDemo // // 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 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 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). 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 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 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 Globals 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 // 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]; // 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 [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]; } } // 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(uint32_t msg_version, const NSString* header, const es_process_t* proc) { if(!proc) { LOG_INFO("%@: (null)", header); return; } LOG_INFO("%@:", header); 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:@"%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(); } } 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: 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); // 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, 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(version, @"process", msg->process); // Event specific logging switch(msg->event_type) { case ES_EVENT_TYPE_AUTH_EXEC: { log_event_exec(version, &msg->event.exec); } break; case ES_EVENT_TYPE_AUTH_OPEN: { log_event_open(&msg->event.open); } break; case ES_EVENT_TYPE_NOTIFY_FORK: { 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(""); } // 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; } } } #pragma mark - Endpoint Secuirty Demo // Clean-up before exiting void sig_handler(int sig) { LOG_IMPORTANT_INFO("Tidying Up"); if(g_client) { es_unsubscribe_all(g_client); es_delete_client(g_client); } LOG_IMPORTANT_INFO("Exiting"); exit(EXIT_SUCCESS); } 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); 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); 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; } // 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); } // 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; if(ES_AUTH_RESULT_ALLOW == result) { authorized_flags = msg->event.open.fflag; } es_respond_result_t res = es_respond_flags_result(clt, msg, authorized_flags, g_cache_auth_results); 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_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]; } // 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); } 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; } g_seq_nums = [NSMutableDictionary new]; // 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(!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(); } -
Omar-Ikram revised this gist
Dec 1, 2019 . No changes.There are no files selected for viewing
-
Omar-Ikram revised this gist
Dec 1, 2019 . 1 changed file with 92 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 (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. 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). 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. */ #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->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.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]; // 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_FORK }; es_return_t subscribed = es_subscribe(g_client, events, (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; } -
Omar-Ikram revised this gist
Aug 15, 2019 . 1 changed file with 58 additions and 42 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,7 +2,8 @@ // 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) // #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 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 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 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_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: %@", 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: %@", 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_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.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); // 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)); } } break; case ES_EVENT_TYPE_NOTIFY_FORK: { 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); } 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 = 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 ); if(ES_RESPOND_RESULT_SUCCESS != res) { @@ -201,26 +211,32 @@ int main(int argc, const char * argv[]) { 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; } -
Omar-Ikram renamed this gist
Jun 21, 2019 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
Omar-Ikram created this gist
Jun 21, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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; }