Files
FileMonitor/Library/Source/FileMonitor.m
T
Patrick Wardle c28071eed8 add codesigning
the codesigning info from the Endpoint Security Framework may be invalid :\
...as such, we manually generate it
2019-12-30 11:44:36 -10:00

235 lines
5.7 KiB
Objective-C

//
// FileMonitor.m
// FileMonitor
//
// Created by Patrick Wardle on 9/1/19.
// Copyright © 2019 Objective-See. All rights reserved.
//
// Inspired by https://gist.github.com/Omar-Ikram/8e6721d8e83a3da69b31d4c2612a68ba
// NOTE: requires a) root b) the 'com.apple.developer.endpoint-security.client' entitlement
#import "utilities.h"
#import "FileMonitor.h"
#import <Foundation/Foundation.h>
#import <EndpointSecurity/EndpointSecurity.h>
//endpoint client
es_client_t *endpointClient = nil;
@interface FileMonitor ()
//process args (via `ES_EVENT_TYPE_NOTIFY_EXEC`)
// so save, to report with all other file i/o events
@property(atomic, retain)NSMutableDictionary* arguments;
@end
@implementation FileMonitor
//args
@synthesize arguments;
//init
-(id)init
{
//init super
self = [super init];
if(nil != self)
{
//alloc agrugments dictionary
arguments = [NSMutableDictionary dictionary];
}
return self;
}
//start monitoring
// pass in events of interest, count of said events, and callback
-(BOOL)start:(es_event_type_t*)events count:(uint32_t)count csOption:(BOOL)csOption callback:(FileCallbackBlock)callback
{
//flag
BOOL started = NO;
//result
es_new_client_result_t result = 0;
//sync
@synchronized (self)
{
//create client
// callback invoked on file events
result = es_new_client(&endpointClient, ^(es_client_t *client, const es_message_t *message)
{
//new file obj
File* file = nil;
//init file obj
// then generate args, code-signing info, etc
file = [[File alloc] init:(es_message_t* _Nonnull)message csOption:csOption];
if(nil != file)
{
//extract/process args
// but don't report file event...
if( (ES_EVENT_TYPE_NOTIFY_EXEC == message->event_type) ||
(ES_EVENT_TYPE_NOTIFY_EXIT == message->event_type) )
{
//process args
[self processArgs:message file:file];
return;
}
//add args
if(nil != self.arguments[[NSNumber numberWithInt:file.process.pid]])
{
//add
file.process.arguments = self.arguments[[NSNumber numberWithInt:file.process.pid]];
}
//invoke user callback
callback(file);
}
});
//error?
if(ES_NEW_CLIENT_RESULT_SUCCESS != result)
{
//err msg
NSLog(@"ERROR: es_new_client() failed");
//provide more info
switch (result) {
//not entitled
case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED:
NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED: \"The caller is not properly entitled to connect\"");
break;
//not permitted
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED: \"The caller is not permitted to connect. They lack Transparency, Consent, and Control (TCC) approval form the user.\"");
break;
//not privileged
case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED:
NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED: \"The caller is not running as root\"");
break;
default:
break;
}
//bail
goto bail;
}
//clear cache
if(ES_CLEAR_CACHE_RESULT_SUCCESS != es_clear_cache(endpointClient))
{
//err msg
NSLog(@"ERROR: es_clear_cache() failed");
//bail
goto bail;
}
//mute self
// note: you might not want this, but for a cmdline-based filemonitor
// this ensures we don't constantly report writes to current /dev/tty
es_mute_path_literal(endpointClient, [NSProcessInfo.processInfo.arguments[0] UTF8String]);
//subscribe
if(ES_RETURN_SUCCESS != es_subscribe(endpointClient, events, count))
{
//err msg
NSLog(@"ERROR: es_subscribe() failed");
//bail
goto bail;
}
} //sync
//happy
started = YES;
bail:
return started;
}
//process args
-(void)processArgs:(const es_message_t*)message file:(File*)file
{
//process exec?
// save arguments
if( (nil != file.process.arguments) &&
(ES_EVENT_TYPE_NOTIFY_EXEC == message->event_type) )
{
//save args
self.arguments[[NSNumber numberWithInt:file.process.pid]] = file.process.arguments;
}
//process exit?
// remove saved process args
else if(ES_EVENT_TYPE_NOTIFY_EXIT == message->event_type)
{
//remove args
[self.arguments removeObjectForKey:[NSNumber numberWithInt:file.process.pid]];
}
return;
}
//stop
-(BOOL)stop
{
//flag
BOOL stopped = NO;
//sync
@synchronized (self)
{
//unsubscribe & delete
if(NULL != endpointClient)
{
//unsubscribe
if(ES_RETURN_SUCCESS != es_unsubscribe_all(endpointClient))
{
//err msg
NSLog(@"ERROR: es_unsubscribe_all() failed");
//bail
goto bail;
}
//delete
if(ES_RETURN_SUCCESS != es_delete_client(endpointClient))
{
//err msg
NSLog(@"ERROR: es_delete_client() failed");
//bail
goto bail;
}
//unset
endpointClient = NULL;
//happy
stopped = YES;
}
} //sync
bail:
return stopped;
}
@end