Files

441 lines
11 KiB
Objective-C
Executable File

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