-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathmain.m
executable file
·447 lines (356 loc) · 10.8 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
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
//
// main.m
// TaskExplorer
//
// Created by Patrick Wardle
// Copyright (c) 2015 Objective-See. All rights reserved.
//
#import "main.h"
//main interface
// contains extra logic to handle app translocation
int main(int argc, char *argv[])
{
//return
int status = -1;
//disable stderr
// sentry dumps to this, and we want only JSON to output...
disableSTDERR();
//init crash reporting
initCrashReporting();
//untranslocated URL
NSURL* untranslocatedURL = nil;
//get original url
untranslocatedURL = getUnTranslocatedURL();
if(nil != untranslocatedURL)
{
//remove quarantine attributes of original
execTask(XATTR, @[@"-cr", untranslocatedURL.path], NO);
//nap
[NSThread sleepForTimeInterval:0.5];
//relaunch
// use 'open' since allows two instances of app to be run
execTask(OPEN, @[@"-n", @"-a", untranslocatedURL.path], NO);
//happy
status = 0;
//bail
goto bail;
}
//set network connection flag
isConnected = isNetworkConnected();
//init set of (privacy) protected directories
// these will be skipped, as otherwise we will generate a privacy prompt
protectedDirectories = expandPaths(PROTECTED_DIRECTORIES, sizeof(PROTECTED_DIRECTORIES)/sizeof(PROTECTED_DIRECTORIES[0]));
//handle '-h' or '-help'
if( (YES == [[[NSProcessInfo processInfo] arguments] containsObject:@"-h"]) ||
(YES == [[[NSProcessInfo processInfo] arguments] containsObject:@"-help"]) )
{
//print usage
usage();
//done
goto bail;
}
//handle cmdline
// scan, explore, etc
if( (YES == [[[NSProcessInfo processInfo] arguments] containsObject:@"-scan"]) ||
(YES == [[[NSProcessInfo processInfo] arguments] containsObject:@"-explore"]) )
{
//first check rooot
if(0 != geteuid())
{
//err msg
printf("{\"ERROR\": \"TASKEXPLORER (cmdline) requires root\"}\n");
//bail
goto bail;
}
//set flag
cmdlineMode = YES;
//cli
cmdlineInterface();
//happy
status = 0;
//done
goto bail;
}
//otherwise
// just kick off app for UI instance
else
{
//set flag
cmdlineMode = NO;
//make foreground so it has an dock icon, etc
transformProcess(kProcessTransformToForegroundApplication);
//invoke app's main
status = NSApplicationMain(argc, (const char **)argv);
}
bail:
return status;
}
//print usage
void usage()
{
//usage
printf("\nTASKEXPLORER USAGE:\n");
printf(" -h or -help display this usage info\n");
printf(" -scan scan all tasks and dylibs \n");
printf(" -explore enumerate all tasks and dylibs\n");
printf("\noptions:\n");
printf(" -pretty json output is 'pretty-printed'\n");
printf(" -pid [pid] just scan/explore the specified task\n");
printf(" -skipVT do not query VirusTotal (when '-explore' is specified)\n");
printf(" -detailed for each task; include dylibs, files, & network connections\n\n");
return;
}
//perform a cmdline interface
void cmdlineInterface()
{
//args
NSArray* arguments = nil;
//filter obj
Filter* filter = nil;
//flag
BOOL includeApple = NO;
//flag
BOOL skipVirusTotal = NO;
//flag
BOOL prettyPrint = NO;
//flag
BOOL detailed = NO;
//output
NSMutableString* output = nil;
//formatter
NSNumberFormatter* formatter = nil;
//pid
// if single task was specified
NSNumber* pid = nil;
//grab args
arguments = [[NSProcessInfo processInfo] arguments];
//init filter obj
filter = [[Filter alloc] init];
//init task enumerator object
taskEnumerator = [[TaskEnumerator alloc] init];
//set flag
// skip virus total?
skipVirusTotal = [arguments containsObject:@"-skipVT"];
//virus total?
if(YES != skipVirusTotal)
{
//init virus total object
virusTotal = [[VirusTotal alloc] init];
}
//be nice
nice(15);
//scan just one pid?
if( (YES == [arguments containsObject:@"-pid"]) &&
(YES != [@"-pid" isEqualToString:arguments.lastObject]) )
{
//init formatter
formatter = [[NSNumberFormatter alloc] init];
//set style
formatter.numberStyle = NSNumberFormatterDecimalStyle;
//extract/convert pid
pid = [formatter numberFromString:arguments[[arguments indexOfObject:@"-pid"] + 1]];
//sanity check
if( (nil == pid) ||
(YES != isAlive(pid.intValue)) )
{
//err msg
printf("{\"ERROR\" : \"specified pid, %s, does not exist\"}\n", [arguments[[arguments indexOfObject:@"-pid"] + 1] UTF8String]);
//bail
goto bail;
}
}
//enumerate all tasks/dylibs/files/etc
[taskEnumerator enumerateTasks:pid];
//wait for items to complete processing
while(taskEnumerator.binaryQueue.itemsOut != taskEnumerator.binaryQueue.itemsOut)
{
//nap
[NSThread sleepForTimeInterval:1.0f];
}
//determine what each dylib is loaded in
// do here as all tasks and all dylibs are (now) enum'd
for(NSString* dylib in taskEnumerator.dylibs)
{
//loaded in
((Binary*)taskEnumerator.dylibs[dylib]).loadedIn = [taskEnumerator loadedIn:taskEnumerator.dylibs[dylib]];
}//sync
//wait for all VT threads to exit
if(YES != skipVirusTotal)
{
//wait
completeVTQuery();
}
//set flag
// include apple items?
includeApple = [arguments containsObject:@"-apple"];
//set flag
// pretty print json?
prettyPrint = [arguments containsObject:@"-pretty"];
//set flag
// full output?
detailed = [arguments containsObject:@"-detailed"];
//alloc output JSON
output = [NSMutableString string];
//only flagged items?
if(YES == [arguments containsObject:@"-scan"])
{
//start JSON
[output appendString:@"{\"flagged items\":["];
//add each item
for(Binary* flaggedItem in taskEnumerator.flaggedItems)
{
[output appendFormat:@"{%@},", [flaggedItem toJSON]];
}
//remove last ','
if(YES == [output hasSuffix:@","])
{
//remove
[output deleteCharactersInRange:NSMakeRange([output length]-1, 1)];
}
//terminate list/output
[output appendString:@"]}"];
}
//all items
else
{
//start JSON
[output appendString:@"{\"tasks\":["];
//get tasks
for(NSNumber* taskPid in taskEnumerator.tasks)
{
//skip apple?
// unless we're scanning a single proc
if( (YES != includeApple) &&
(1 != taskEnumerator.tasks.count) &&
(YES == [filter isApple:((Task*)taskEnumerator.tasks[taskPid]).binary]) )
{
//skip
continue;
}
//append task JSON
[output appendFormat:@"{%@},", [taskEnumerator.tasks[taskPid] toJSON:detailed]];
}
//remove last ','
if(YES == [output hasSuffix:@","])
{
//remove
[output deleteCharactersInRange:NSMakeRange([output length]-1, 1)];
}
//not detailed or not just scanning 1 task
// add separate array of for all the dylibs
if( (YES != detailed) &&
(1 != taskEnumerator.tasks.count) )
{
//append
[output appendString:@"],\"dylibs\":["];
//add each dylib
for(NSString* dylib in taskEnumerator.dylibs)
{
//add
[output appendFormat:@"{%@},", [((Binary*)taskEnumerator.dylibs[dylib]) toJSON]];
}
//remove last ','
if(YES == [output hasSuffix:@","])
{
//remove
[output deleteCharactersInRange:NSMakeRange([output length]-1, 1)];
}
}
//terminate list/output
[output appendString:@"]}"];
}
//pretty print?
if(YES == prettyPrint)
{
//make me pretty!
prettyPrintJSON(output);
}
else
{
//output
printf("%s\n", output.UTF8String);
}
bail:
return;
}
//block until vt queries are done
void completeVTQuery()
{
//flag
BOOL queryingVT = NO;
//nap
// VT threads take some time to spawn/process
[NSThread sleepForTimeInterval:5.0f];
//wait till threads are done
while(YES)
{
//reset flag
queryingVT = NO;
//wait for vt to complete
@synchronized(virusTotal.vtThreads)
{
//check all threads
for(NSThread* vtThread in virusTotal.vtThreads)
{
//check if still running?
if(YES == [vtThread isExecuting])
{
//set flag
queryingVT = YES;
//bail
break;
}
}
}
//check flag
if(YES != queryingVT)
{
//finally no active threads
break;
}
//nap
[NSThread sleepForTimeInterval:5.0f];
}
return;
}
//pretty print JSON
void prettyPrintJSON(NSString* output)
{
//data
NSData* data = nil;
//object
id object = nil;
//pretty data
NSData* prettyData = nil;
//pretty string
NSString* prettyString = nil;
//covert to data
data = [output dataUsingEncoding:NSUTF8StringEncoding];
//convert to JSON
// wrap since we are serializing JSON
@try
{
//serialize
object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//covert to pretty data
prettyData = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:nil];
}
@catch(NSException *exception)
{
;
}
//covert to pretty string
if(nil != prettyData)
{
//convert to string
prettyString = [[NSString alloc] initWithData:prettyData encoding:NSUTF8StringEncoding];
}
else
{
//error
prettyString = @"{\"ERROR\" : \"failed to covert output to JSON\"}";
}
//output
printf("%s\n", prettyString.UTF8String);
return;
}