-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.m
387 lines (308 loc) · 11.4 KB
/
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#import "config.h"
#import "eventdatatypes.h"
#import "shared.h"
NSFileHandle *logs_file_handle = nil;
es_client_t *g_client = nil;
NSSet *g_blocked_paths = nil;
NSFileHandle *config_file_handle = nil;
es_handler_block_t g_handler = nil;
uint64_t g_global_seq_num = 0;
NSMutableDictionary *g_seq_nums = nil;
bool g_cache_auth_results = false;
bool g_verbose_logging = false;
Config *config = nil;
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__)
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 detect_and_log_dropped_events(const es_message_t *msg) {
uint32_t version = msg->version;
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 != nil) {
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;
}
}
}
// 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);
}
[g_blocked_paths release];
[logs_file_handle closeFile];
[logs_file_handle release];
LOG_IMPORTANT_INFO("Exiting");
exit(EXIT_SUCCESS);
}
void print_usage(const char *name) {
printf("Usage: %s (verbose)\n", name);
printf("Arguments:\n");
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;
// 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;
}
LOG_IMPORTANT_INFO("BLOCKING EXEC: %@", path);
return ES_AUTH_RESULT_DENY;
}
if (ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) {
return ES_AUTH_RESULT_ALLOW;
}
// 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) {
if (ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) {
es_respond_result_t res = es_respond_flags_result(
clt, msg, UINT32_MAX, false); // 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, false);
if (ES_RESPOND_RESULT_SUCCESS != res) {
LOG_ERROR("es_respond_auth_result: %d", res);
}
}
}
// Example of an event message handler to process event messages asynchronously
// from Endpoint Security
es_handler_block_t message_handler = ^(es_client_t *clt,
const es_message_t *msg) {
detect_and_log_dropped_events(msg);
es_message_t *copied_msg = copy_message(msg);
if (!copied_msg) {
LOG_ERROR("Failed to copy message");
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(
void) {
es_auth_result_t result = auth_event_handler(copied_msg);
if (ES_ACTION_TYPE_AUTH == copied_msg->action_type) {
respond_to_auth_event(clt, copied_msg, result);
}
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSDictionary *event_dict = event_message_to_dict(copied_msg);
if ([NSJSONSerialization isValidJSONObject:event_dict]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization
dataWithJSONObject:event_dict
options:NSJSONWritingPrettyPrinted
error:&error];
if (!jsonData) {
NSLog(@"Got an error: %@", error);
} else {
NSString *jsonString =
[[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSString *jsonLine = [jsonString stringByAppendingString:@"\n"];
[logs_file_handle
writeData:[jsonLine dataUsingEncoding:NSUTF8StringEncoding]];
}
}
free_message(copied_msg);
});
});
};
// 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 __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000
result = es_mute_path(g_client, path, ES_MUTE_PATH_TYPE_LITERAL);
#else
result = es_mute_path_literal(g_client, path);
#endif
if (ES_RETURN_SUCCESS != result) {
LOG_ERROR("mute_path: ES_RETURN_ERROR");
return false;
}
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.
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;
}
// 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_OPEN, ES_EVENT_TYPE_AUTH_EXEC};
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);
NSString *logs_file_path =
[[NSFileManager defaultManager] currentDirectoryPath];
logs_file_path = [logs_file_path stringByAppendingPathComponent:@"logs"];
// Open the file for appending
// TODO: check that this does not return any errors
logs_file_handle = [NSFileHandle fileHandleForWritingAtPath:logs_file_path];
if (!logs_file_handle) {
[[NSFileManager defaultManager] createFileAtPath:logs_file_path
contents:nil
attributes:nil];
logs_file_handle = [NSFileHandle fileHandleForWritingAtPath:logs_file_path];
}
if (!logs_file_handle) {
NSLog(@"Error opening (%@)", logs_file_path);
}
[logs_file_handle retain];
config = [[Config alloc] initWithConfigPath:@"./config.json"];
NSLog(@"%@", config.config);
@autoreleasepool {
// Init global vars
g_handler = message_handler;
if (!g_handler) {
print_usage(argv[0]);
return 1;
}
init_date_formater();
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];
// had to do this otherwise it seems that the memory is free'd and this
// leads to a segfault
[g_blocked_paths retain];
if (!setup_endpoint_security()) {
return 1;
}
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000
log_muted_paths_events();
#else
mute_path("/usr/sbin/cfprefsd");
#endif
// Start handling events from Endpoint Security
dispatch_main();
}
return 0;
}